2009-04-16 4 views
5

Я думаю, что мой вопрос лучше всего описывается в качестве примера. Предположим, у меня есть простая модель под названием «Вещь», и у нее есть несколько атрибутов, которые являются простыми типами данных. Что-то вроде ...Рекомендации по нескольким ассоциациям с одним классом в 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 

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

Моя первоначальная мысль заключается в том, чтобы добавить атрибут «имя» в Whazit, чтобы я мог получить Whatzits, связанный с моим @thing, а затем выбрать 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.class, когда то, что они действительно хотят, это @object.base_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 

На самом деле, это очень похоже на то, как работает ИППП, за исключением того, что вам не нужно отдельный класс.

Единственное, что вам нужно будет отслеживать, это установить это имя, когда вы связываете whazit. Это может быть так же просто:

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