0

我正在使用 STI(正确,我保证!)作为对象的一个​​关系:

class Walrus < ActiveRecord::Base
  has_one :bubbles
end

class Bubbles < ActiveRecord::Base
  belongs_to :walrus
  before_save :set_origin

  private

  def set_origin
    self.type = walrus.state ? "Bubbles::#{walrus.state}" : 'Bubbles'
  end
end

class Bubbles::OfMind < Bubbles
  def tango
  end
end

现在,如果我建立一个新关系,则该类设置不正确:

harold = Walrus.new(state: 'OfMind')
harold.build_bubbles.save!
harold.bubbles
  # => returns instance of Bubbles, not Bubbles::OfMind
harold.bubbles.tango
  # NoMethodError

Bubbles 对象不能神奇地变成 Bubbles::OfMind,但在关系类型正确之前,不存在正确的功能。

4

1 回答 1

4

在处理 STI 之前,请注意违反约定的模型名称和关联。我知道您选择这些类名是为了演示,但是由于您正在运行和测试此代码,因此出现异常行为也就不足为奇了。

模型类名称应该是单一且合理的。将超类更改为Bubble,将子类更改为引用气泡变体的东西,例如BigBubble

has_one关联也必须使用单数模型名称:has_one :bubble.

注意:当 Rails 遇到命名空间模型时,它期望相应的控制器和视图文件也被命名空间,嵌套目录和所有。一会就乱了。除非绝对必要,否则最好避免命名空间。


生成器方法是对 STI 的滥用。构建器方法尝试实例化超类并手动为其分配类型。这与 Rails 对 STI 类的内置管理相冲突,因此不支持它。

STI 超类是不应该被实例化的抽象类。使用 STI 时,您只能与子类交互。超类的所有方法都暴露在子类中,因此没有理由接触超类对象……除非修改type违反 Rails 约定的属性。如果您绝对必须操作超类,则不应使用 STI。

正确完成后,您应该直接使用手动关联创建子类对象:

harold = Walrus.create!
BigBubble.create!(:walrus_id => harold.id)

harold.bubble
  # => returns instance of BigBubble

harold.bubble.tango
  # => true

虽然不如构建器方法优雅,但这种方法是正确的并且有效。那些试图解决命名空间 STI 关联问题 ( ahem... ) 的博客正试图强制执行不适合 STI 开始的行为。正确使用 STI 涉及采用设计准则,“不要与超类混淆”。

于 2013-04-10T21:44:58.283 回答