Лучшие практики для нескольких ассоциаций с одним и тем же классом в Rails? - PullRequest
5 голосов
/ 16 апреля 2009

Я думаю, что мой вопрос лучше всего описать в качестве примера. Допустим, у меня есть простая модель под названием «Вещи», и у нее есть несколько атрибутов, которые являются простыми типами данных. Что-то вроде ...

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

Это не сложно. Таблица БД будет содержать три столбца с этими тремя атрибутами, и я могу получить к ним доступ, например, @ thing.foo или @ thing.bar.

Но проблема, которую я пытаюсь решить, состоит в том, что происходит, когда "foo" или "goo" больше не могут содержаться в простом типе данных? Предположим, что foo и goo представляют один и тот же тип объекта. То есть оба они являются экземплярами Whazit только с разными данными. Так что теперь все может выглядеть так ...

Thing
  - bar:int

Но теперь появилась новая модель под названием "Whazit", которая выглядит следующим образом ...

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

Пока это все хорошо. Теперь вот где я застрял. Если у меня есть @thing, как я могу настроить его так, чтобы он ссылался на мои 2 экземпляра Whazit по имени (для записи «бизнес-правило» состоит в том, что любая вещь всегда будет иметь ровно 2 Whazit)? То есть мне нужно знать, является ли мой Whazit в основном foo или goo. Очевидно, я не могу сделать @ thing.foo в текущей настройке, но я бы сказал, что это идеально.

Моя первоначальная мысль - добавить атрибут «name» в Whazit, чтобы я мог получить Whatzits, связанный с моим @thing, а затем выбрать Whazit, который я хочу, по имени таким образом. Хотя это кажется уродливым.

Есть ли лучший способ?

Ответы [ 2 ]

8 голосов
/ 16 апреля 2009

Есть несколько способов сделать это. Во-первых, вы можете установить два отношения 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.class, когда они действительно хотят @object.base_class. Патч достаточно просто при необходимости.

2 голосов
/ 17 апреля 2009

Ваше простое решение с добавлением "имени" не должно быть безобразным:

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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...