2

我在这里使用的是 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. 另一方面,CarTruck是 的子类Vehicle。车辆数据库是使用 STI 设置的,因此Cars两者Trucks都对应于vehicles表(带有type列微分器)。

其次,请注意,我Drivable在所有底层域对象(Car, Truck, GolfBall)上都包含了一个模块,它看起来像这样(在实际系统中,这个模块也做了很多事情,包括根据特定的设置进行设置,包括域对象):

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基于包含类名称的 a(例如,在一个with中包含类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    ...

Vehicledriveable_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,就像它在非急切加载情况下所做的那样。

知道如何解决这个问题吗?

4

1 回答 1

2

在阅读了几个小时的 rails 源代码之后,我很确定我明白了这一点(当然,我可能误读了某些东西)。它似乎是 Rails 2.3.9 中引入的 Rails 2.3.x 分支中的一个错误,并且从未修复。它从这个 Rails 2.3 错误报告开始:

为预加载嵌套在多态 belongs_to 下的关联而构建的查询错误

确实这是一个有效的错误,并且在 Rails 2.3.9 中的这个提交中修复了它:

修复嵌套的多态 has_one 关联的急切加载

但是,此提交无意中破坏了 STI 类上的非嵌套多态预加载,并且他们没有针对这种情况进行测试,因此自动化测试没有捕捉到它。相关的差异在这里:

 360    -          conditions = "#{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_id"} #{in_or_equals_for_ids(ids)} and #{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_type"} = '#{self.base_class.sti_name}'"

 360    +          parent_type = if reflection.active_record.abstract_class?
 361    +            self.base_class.sti_name
 362    +          else
 363    +            reflection.active_record.sti_name
 364    +          end
 365    +
 366    +          conditions = "#{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_id"} #{in_or_equals_for_ids(ids)} and #{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_type"} = '#{parent_type}'"

从这个差异中您可以看到,在 2.3.9 之前,对于预先加载的多态关联,它始终self.base_class.sti_name用作parent_type(即多态类型列)。按照问题中的示例,这意味着父类型列将是driveable_type,并且由于Car.base_classVehicle,它将正确匹配 on driveable_type = 'Vehicle'

但是,从上述提交开始,只有在关联持有者是abstract_class. 这似乎是一个直接的错误;应该设置它,因此如果关联持有者是抽象的sti 子类,它应该设置parent_typeself.base_class.sti_name.

幸运的是,有一种简单的方法可以解决这个问题。虽然它在任何地方的文档中都没有提到这一点,但似乎可以通过设置abstract_class = truesti 子类来解决这个问题,该子类包含多态 belongs_to 关联。在问题的示例中,这将是:

class Car < Vehicle
    abstract_class = true
    include Driveable
end

class Truck < Vehicle
    abstract_class = true
    include Driveable
end

对 rails 源进行全文搜索abstract_class,设置似乎不会产生任何意外/不希望的后果,并且在使用真实应用程序的初始测试中,它似乎已经解决了问题。

于 2012-07-06T22:53:23.277 回答