在Rails中与同一个类进行多个关联的最佳实践?

我认为我的问题最好被描述为一个例子。 假设我有一个名为“Thing”的简单模型,它有一些简单数据类型的属性。 就像是…

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 

到目前为止,这一切都很好。 现在这里是我被困住的地方。 如果我有@thing,我怎么设置它来引用我的2个Whazit实例的名称(为了记录,“业务规则”是任何Thing总是有2个Whazits)? 也就是说,我需要知道我的Whazit是否基本上是foo或goo。 显然,我不能在当前的设置中做@ thing.foo,但我认为这是理想的。

我最初的想法是给Whazit添加一个“名字”属性,这样我就可以得到与我的@thing相关的Whatzits,然后通过这个名字选择我想要的Whazit。 这看起来很难看。

有没有更好的办法?

有几种方法可以做到这一点。 首先,您可以设置两个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 

另一种选择是更简洁,但在处理插件时也更痛苦,是单表inheritance。 在这种情况下,你有两个类,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有其自身的一系列问题,特别是如果您使用较旧的插件和gem。 通常,当他们真正想要的是@object.base_class时,调用@object.class的代码就会出现问题。 必要时可以轻松修补。

添加“名称”的简单解决方案不需要丑陋:

 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