Rails: почему методы ассоциации has_one ведут себя непоследовательно?

Методы ассоциации has_one выполняются у меня непоследовательно, и я не знаю, почему.

Возьмем две простые модели, связанные друг с другом:

class Container < ActiveRecord::Base
  has_one: :super
end

class Super < ActiveRecord::Base
  belongs_to: :container
end

Следующий код работает как надо:

container = Container.create
 ...

container.build_super
 ...
 => #<Super id: nil, container_id: 1, content: nil, created_at: nil, updated_at: nil>

container.super
 => #<Super id: nil, container_id: 1, content: nil, created_at: nil, updated_at: nil>

Когда вы вызываете container.super в приведенном выше коде, он возвращает только что созданный экземпляр класса Super.

Однако следующий код не работает:

Container.create
 ...
 => #<Container id: 1, created_at: "2013-10-26 20:31:26", updated_at: "2013-10-26 20:31:26">

Container.first.build_super
 ...
 => #<Super id: nil, container_id: 1, content: nil, created_at: nil, updated_at: nil>

Container.first.super
  Container Load (0.2ms)  SELECT "containers".* FROM "containers" ORDER BY "containers"."id" ASC LIMIT 1
  Super Load (0.1ms)  SELECT "supers".* FROM "supers" WHERE "supers"."container_id" = ? ORDER BY "supers"."id" ASC LIMIT 1  [["container_id", 1]]
 => nil

Container.first.super возвращает ноль, потому что он ищет экземпляр Super в базе данных. Однако экземпляр еще не зафиксирован.

Но почему container.super и Container.first.super не дают одинакового результата, когда container == Container.first?


person earksiinni    schedule 26.10.2013    source источник
comment
Вы пытались вызвать save для экземпляра, созданного build_super? Вероятно, поэтому Container.first.super не найден. Кроме того, я знаю, что вероятно это не проблема, но слово "супер" зарезервировано и может вызвать некоторое сумасшествие.   -  person struthersneil    schedule 27.10.2013
comment
Происходит то, что container.super имеет ссылку на объект, который вы только что создали в ассоциации, даже если он не сохранен. Container.first.super должен фактически выполнить чтение.   -  person struthersneil    schedule 27.10.2013


Ответы (2)


Container.first.build_super извлекает копию записи контейнера, создает экземпляр ассоциации и кэширует этот экземпляр ассоциации в этой копии записи контейнера.

Вызов Container.first.super после этого извлекает отдельную копию записи Container и обнаруживает, что в ней нет ничего для :super.

По сути, вы делаете это:

a = Container.first    # New instance
a.build_super          # Assign to :super

b = Container.first    # Separate instance
b.super                # Nothing in :super

Что вы, вероятно, захотите сделать, так это присвоить это переменной вместо получения другой копии:

container = Container.first
container.build_super
container.super
person kristinalim    schedule 26.10.2013
comment
Отличное объяснение! Не видел, чтобы я создавал новую копию всякий раз, когда вызывал Container.first, но это имеет смысл. - person earksiinni; 27.10.2013

В вашем первом примере суперобъект создается в памяти и теряется, когда вы перезагружаете его из базы данных (как показывает ваш второй пример). Если бы вы сделали container.reload.super, чтобы загрузить его из базы данных в первом примере, объект в памяти был бы потерян, и он был бы равен нулю.

Примечание: очень плохая идея называть вашу ассоциацию «супер». Это зарезервированное ключевое слово в Ruby для вызова методов родительских классов, когда подкласс переопределяет его.

person Peter Brown    schedule 26.10.2013