Самореференциальная и двунаправленная ассоциация «один-к-одному» (на удивление) немного сложнее, чем самореференциальная и двунаправленная ассоциация «один-ко-многим» или даже «многие-ко-многим».
Просто чтобы прояснить:
- Самостоятельная ссылка: связанный элемент является экземпляром того же класса, что и его владелец.
- двунаправленный: если элемент является эквивалентом другого, то последний также является эквивалентом первого упомянутого.
Это значит:
revolver = Item.create name: 'revolver'
pistol = Item.create name: 'pistol'
revolver.equivalent = pistol
revolver.equivalent # => pistol
pistol.equivalent # => revolver
Другими словами, когда вы устанавливаете эквивалентный элемент для элемента, также должен быть установлен эквивалентный идентификатор владельца.
Ассоциация "один-к-одному" не принимает опции insert_sql
и delete_sql
. Так что это немного менее красиво. Тем не менее, самый чистый способ сделать это (IMO):
class Item < ActiveRecord::Base
has_one :equivalent, class_name: 'Item', foreign_key: 'equivalent_id'
def add_equivalent(other)
self.equivalent = other
other.equivalent = self
end
def remove_equivalent
equivalent.equivalent = nil
self.equivalent = nil
end
end
Таким образом, вы можете сделать:
revolver = Item.create name: 'revolver'
pistol = Item.create name: 'pistol'
revolver.add_equivalent(pistol)
revolver.equivalent # => pistol
pistol.equivalent # => revolver
pistol.remove_equivalent
pistol.equivalent # => nil
revolver.equivalent # => nil
редактировать
Чтобы быть в безопасности, вы должны очищать отношения каждый раз, когда добавляете эквивалент. Это значит:
revolver.equivalent # => pistol
pistol.equivalent # => revolver
revolver.add_equivalent(gun)
revolver.equivalent # => gun
pistol.equivalent # => nil
Вы можете сделать это так:
def add_equivalent(other)
remove_equivalent if equivalent
...
end