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

CJ *_*J F 5 ruby activerecord ruby-on-rails

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

Thing
   - foo:string
   - goo:string
   - bar:int
Run Code Online (Sandbox Code Playgroud)

这并不难.db表将包含三个具有这三个属性的列,我可以使用@ thing.foo或@ thing.bar等访问它们.

但是我试图解决的问题是当"foo"或"goo"不再包含在简单数据类型中时会发生什么?假设foo和goo代表相同类型的对象.也就是说,它们都只是具有不同数据的"Whazit"实例.所以现在Thing可能看起来像这样......

Thing
  - bar:int
Run Code Online (Sandbox Code Playgroud)

但现在有一个名为"Whazit"的新模型看起来像这样......

Whazit
  - content:string
  - value:int
  - thing_id:int
Run Code Online (Sandbox Code Playgroud)

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

我最初的想法是给Whazit添加一个"name"属性,这样我就可以得到与我的@thing相关的Whatzits,然后通过这种方式选择我想要的Whazit.这看起来很难看.

有没有更好的办法?

Sar*_*Mei 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
Run Code Online (Sandbox Code Playgroud)

另一种选择是更简洁,但在处理插件时也更痛苦,是单表继承.在这种情况下,你有两个类,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
Run Code Online (Sandbox Code Playgroud)

在这两种情况下,你都可以做@thing.foo@thing.goo.使用第一种方法,您需要执行以下操作:

@thing.foo = Whazit.new
Run Code Online (Sandbox Code Playgroud)

而使用第二种方法,您可以执行以下操作:

@thing.foo = Foo.new
Run Code Online (Sandbox Code Playgroud)

但是,STI有其自身的一系列问题,特别是如果您使用较旧的插件和宝石.通常,@object.class当他们真正想要的是代码调用时,这是一个问题@object.base_class.必要时可以轻松修补.