2013-04-01 129 views
0

我使用STI(正确的,我保证!)为对象的一个​​关系:Rails的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

你可能需要拼出来,因为我在这里看不到一丝性病。我看到三个没有继承的裸类。 Walrus是否应该是超类? – Substantial

+0

添加了继承 – Chris

+0

什么是build_bubbles方法? –

回答

4

在解决STI问题之前,请注意型号名称和关联中断约定。我明白,为了演示,您已经选择了这些类名,但是由于您正在运行和测试此代码,因此不稳定的行为并不意外。

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

has_one协会必须使用单数模型名称以及:has_one :bubble

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


建设者的方法是滥用性传播疾病。构建器方法尝试实例化超类并手动为其分配类型。这与Rails内置的STI类管理冲突,因此不支持它。

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

得当,你应该直接创建一个子类对象与手册关联:

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

harold.bubble 
    # => returns instance of BigBubble 

harold.bubble.tango 
    # => true 

虽然不如优雅作为一个建设者的方法,这种方法是正确的,它的作品。那些试图解决命名空间性传播疾病协会尴尬(ahem...)的博客试图强迫不适合性传播感染的行为开始。正确使用STI涉及采用设计指南,“不要与超类混淆。”

+0

如果需要计算子类,该怎么办?所以你有BigBubble和SmallBubble,哪一个与给定的海象关联是基于Walrus当前的年龄?我想首先计算并实例化正确的子类? – Chris

+0

没错。首先确定适当的子句,然后实例化它。临时阶段没有中间阶层。另一种看待它的方法是将BigBubble和SmallBubble换成其他类,比如Integer和Hash。 – Substantial