我在這裏使用Rails 2.3.14,但它可能存在的問題並不侷限於此特定版本。這是Rails 3中仍然存在的所有關聯和熱切加載功能,並且在2.3.14之前已經出現。我正在從rails 2.3.8升級的過程中,我沒有遇到下面描述的問題。Rails與多態關聯的STI模型,渴望加載查詢引用錯誤的類類型
下面的代碼是一個基於更復雜的生產系統的模型。我列出的類/模塊方案是由於某種原因而設置的。實際上,我在這個模型中包含了一些比展示問題所需的更多細節,希望能夠使系統的整體結構更加清晰。
假設我有幾個可以「驅動」的域對象,包括車輛(汽車/卡車)和高爾夫球。對於這些事情,我有一個ActiveRecord類:
class Vehicle < ActiveRecord::Base
end
class Car < Vehicle
include Driveable
end
class Truck < Vehicle
include Driveable
end
class GolfBall < ActiveRecord::Base
include Driveable
end
首先要注意GolfBall
是一個頂級模型類和相應的數據庫表golf_balls
。另一方面,Car
和Truck
是Vehicle
的子類別。車輛數據庫設置爲STI,所以Cars
和Trucks
都對應於vehicles
表(具有type
列差異)。
其次,請注意,我包括在所有最下層的領域對象(Car
,Truck
,GolfBall
)一個Drivable
模塊,它看起來像這樣(在實際系統中該模塊可以做更多的也一樣,包括設置的東西了基於特定的包括域對象):
module Driven
def self.included(base)
base.class_eval do
has_one :driver, :as => :driveable, :class_name => "#{self.name}Driver", :dependent => :destroy
end
end
end
因此每這些事情可以有一個Driver
,而且它使用基於包括類名:class_name
(例如,包括類Car
導致has_one
與:class_name => "CarDriver"
),因爲每個這些引用類(CarDriver
等)包含該協會使用所需的特定業務邏輯。
有一個頂層Driver
類建立多態關聯,然後類似的子類層次結構如以上針對域對象驅動程序:
class Driver < ActiveRecord::Base
belongs_to :driveable, :polymorphic => true
end
class VehicleDriver < Driver
end
class CarDriver < VehicleDriver
end
class TruckDriver < VehicleDriver
end
class GolfBallDriver < Driver
end
這是基於單個數據庫表drivers
,使用STI適用於所有亞類。
有了這個系統的地方,我創建了一個新的Car
(存儲在下面@car
),並將其與新創建CarDriver
這樣的(它分裂成在這個模擬了這些特殊的順序步驟鏡像的方式聯繫起來實際系統的工作原理):
@car = Car.create
CarDriver.create(:driveable => @car)
此創建的數據庫行中的vehicles
表是這樣的:
id type ...
-----------------
1 Car ...
而且像drivers
表中的行這樣的:
id driveable_id driveable_type type ...
--------------------------------------------------------
1 1 Vehicle CarDriver ...
Vehicle
是driveable_type
,而不是Car
因爲車輛是STI。到現在爲止還挺好。現在,我開了一個軌道控制檯,並執行一個簡單的命令來獲取Car
實例:
>> @car = Car.find(:last)
=> #<Car id: 1, type: "Car", ...>
根據日誌,這裏是已執行的查詢:
Car Load (1.0ms)
SELECT * FROM `vehicles`
WHERE (`vehicles`.`type` = 'Car')
ORDER BY vehicles.id DESC
LIMIT 1
然後我得到的CarDriver
:
>> @car.driver
=> #<CarDriver id: 1, driveable_id: 1, driveable_type: "Vehicle", type: "CarDriver", ...>
這導致此查詢被執行。
CarDriver Load (0.7ms)
SELECT * FROM `drivers`
WHERE (`drivers`.driveable_id = 1 AND `drivers`.driveable_type = 'Vehicle') AND (`drivers`.`type` = 'CarDriver')
LIMIT 1
但是,如果我嘗試使用急切加載,我會得到不同的結果。從新的控制檯會話中,我運行:
>> @car = Car.find(:last, :include => :driveable)
=> #<Car id: 1, type: "Car", ...>
>> @car.driver
=> nil
這導致驅動程序的nil
。檢查日誌,第一條語句執行以下查詢(定期查詢和預先加載查詢):
Car Load (1.0ms)
SELECT * FROM `vehicles`
WHERE (`vehicles`.`type` = 'Car')
ORDER BY vehicles.id DESC
LIMIT 1
CarDriver Load (0.8ms)
SELECT * FROM `drivers`
WHERE (`drivers`.driveable_id = 1 AND `drivers`.driveable_type = 'Car') AND (`drivers`.`type` = 'CarDriver')
LIMIT 1
正如你所看到的,在預先加載的情況下,Car
查詢是與上述相同,但CarDriver
查詢是不同的。它幾乎是一樣的,除了drivers.driveable
類型,它正在尋找Car
它應該在那裏尋找STI基類名稱,Vehicle
,因爲它在非渴望加載的情況下。
任何想法如何解決這個問題?
是的,這似乎是由同一個錯誤引起的。據我所知,這個錯誤從來沒有被修復,驗證過,甚至沒有正式報告過,所以3.2.8版本可能仍然存在。該錯誤阻止了sti數據的正確預加載,這正是您的查詢所做的,並解釋了速度差異。我沒有看到Rails 2.3.14中的任何東西都會讓「abstract_class = true」成爲sti類的一個問題,並且我一直在運行一個生產應用程序,該應用程序可以在幾個月內實現該解決方案,而不會出現任何問題。我不能說在Rails 3.2.8中有什麼改變。 – 2012-10-22 19:42:34