【发布时间】:2012-07-06 18:16:18
【问题描述】:
我在这里使用的是 Rails 2.3.14,但我遇到的问题可能并不局限于这个特定版本。这是 Rails 3 中仍然存在的所有关联和急切加载功能,并且在 2.3.14 之前就已经存在。我正在从 rails 2.3.8 升级,我没有遇到下面描述的问题。
下面的代码是基于更复杂的生产系统的模型。我概述的类/模块方案是这样设置的,这是有原因的。实际上,我在这个模型中包含了比演示问题所需的更多细节,希望使系统的整体结构更加清晰。
假设我有几个可以“驱动”的域对象,包括车辆(汽车/卡车)和高尔夫球。对于这些事情,我都有一个 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。到现在为止还挺好。现在我打开一个 Rails 控制台并执行一个简单的命令来获取一个 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,就像在非急切加载情况下一样。
知道如何解决这个问题吗?
【问题讨论】:
标签: ruby-on-rails polymorphic-associations eager-loading sti