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