Rails STI构建关系

时间:2013-04-01 16:03:52

标签: ruby-on-rails sti

我正在使用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,但在关系属于正确类型之前,不存在正确的功能。

1 个答案:

答案 0 :(得分:4)

在解决STI之前,请注意违反惯例的模型名称和关联。我知道你为了演示而选择了这些类名,但是既然你正在运行并测试这段代码,那么不稳定的行为就不足为奇了。

模型类名称应单数且合理。将超类更改为Bubble,将子类更改为引用气泡变体的内容,例如BigBubble

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

注意:当Rails遇到命名空间模型时,它期望相应的控制器和视图文件也是命名空间,嵌套目录和所有。它匆忙变得凌乱。最好避免命名空间,除非绝对必要。


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

STI超类是抽象类,永远不应该实例化。使用STI时,只能与子类进行交互。超类的所有方法都在子类中公开,因此没有理由触及超类对象......除非修改违反Rails约定的type属性。如果你绝对必须操纵超类,则不应使用STI。

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

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

harold.bubble
  # => returns instance of BigBubble

harold.bubble.tango
  # => true

虽然不如构建器方法那么优雅,但这种方式是正确的并且可行。试图解决命名空间STI关联难度的那些博客(ahem...)试图强制开始不适合STI的行为。正确使用STI涉及采用设计指南,“不要乱用超类<​​/ em>。”