2009-04-16 28 views
5

我認爲我的問題是最好的例子。比方說,我有一個簡單的模型叫做「Thing」,它有一些簡單數據類型的屬性。像...在Rails中使用相同類的多個關聯的最佳實踐?

Thing 
    - foo:string 
    - goo:string 
    - bar:int 

這並不難。 db表將包含三個具有這三個屬性的列,我可以通過@ thing.foo或@ thing.bar來訪問它們。

但我想解決的問題是,當「foo」或「goo」不能再包含在簡單數據類型中時會發生什麼?假設foo和goo表示相同類型的對象。也就是說,他們都是「Whazit」的實例,只是數據不同而已。所以現在的事情可能是這樣的......

Thing 
    - bar:int 

但現在有一個名爲「Whazit」看起來像這樣的新模式......

Whazit 
    - content:string 
    - value:int 
    - thing_id:int 

到目前爲止,這是所有的好。現在,這裏是我卡住的地方。如果我有@東西,我該如何設置它來引用我的2個Whazit實例(記錄中,「業務規則」是任何東西總是有2個Whazits)?也就是說,我需要知道我的Whazit基本上是foo還是goo。顯然,我不能在當前的設置中執行@ thing.foo,但我認爲這很理想。

我最初的想法是添加一個「名」屬性Whazit,所以我可以用我的@thing相關的Whatzits,然後按名稱選擇我想要的Whazit的方式。這看起來很難看。

有沒有更好的方法?

回答

8

有幾種方法可以做到這一點。首先,你可以設置兩個belongs_to/has_one關係:

things 
    - bar:int 
    - foo_id:int 
    - goo_id:int 

whazits 
    - content:string 
    - value:int 

class Thing < ActiveRecord::Base 
    belongs_to :foo, :class_name => "whazit" 
    belongs_to :goo, :class_name => "whazit" 
end 

class Whazit < ActiveRecord::Base 
    has_one :foo_owner, class_name => "thing", foreign_key => "foo_id" 
    has_one :goo_owner, class_name => "thing", foreign_key => "goo_id" 

    # Perhaps some before save logic to make sure that either foo_owner 
    # or goo_owner are non-nil, but not both. 
end 

另一種選擇是乾淨了一點,但與插件等打交道時,也更多的是痛苦的,是單表繼承。在這種情況下,你有兩個類,Foo和Goo,但它們都保存在whazits表中,並且有一個類型列來區分它們。

things 
    - bar:int 

whazits 
    - content:string 
    - value:int 
    - thing_id:int 
    - type:string 

class Thing < ActiveRecord::Base 
    belongs_to :foo 
    belongs_to :goo 
end 

class Whazit < ActiveRecord::Base 
    # .. whatever methods they have in common .. 
end 

class Foo < Whazit 
    has_one :thing 
end 

class Goo < Whazit 
    has_one :thing 
end 

在這兩種情況下,你可以做這樣的事情@thing.foo@thing.goo。與第一種方法,你需要做的事情一樣:

@thing.foo = Whazit.new 

而用第二種方法,你可以做這樣的事情:

@thing.foo = Foo.new 

STI有其自身的問題,不過,特別是如果你使用的是舊插件和寶石。當他們真正想要的是@object.base_class時,通常這是代碼調用@object.class的問題。必要時很容易進行修補。

2

你用加入了「名」簡單的解決方案並不需要是難看:

class Thing < ActiveRecord::Base 
    has_one :foo, :class_name => "whazit", :conditions => { :name => "foo" } 
    has_one :goo, :class_name => "whazit", :conditions => { :name => "goo" } 
end 

事實上,這是相當類似STI是如何工作的,但你並不需要一個單獨的類。

您需要注意的唯一事情是在關聯whazit時設置此名稱。這可以如此簡單:

def foo=(assoc) 
    assos.name = 'foo' 
    super(assoc) 
end