Ассоциации автосохранения Ruby on Rails

У меня есть три связанных класса в Rails v. 3.2.15, с Ruby 2.1.1 и классом таблицы соединений между двумя из них:

class Grandarent < ActiveRecord::Base
  has_many :parents, autosave: true
end

class Parent
  belongs_to :grandparent
  has_many :children, :through => :parent_children, autosave: true
end

class ParentChild
  belongs_to :parent
  belongs_to :child
end

class Child
  has_many :parent_children
  has_many :parents, :through => :parent_children
end

Если я выполню следующее, изменения в дочернем элементе не будут сохранены:

gp = Grandparent.find(1)
gp.parents.first.children.first.first_name = "Bob"
gp.save
gp.parents.first.children.first.first_name  ## -> Whatever name was to begin with (i.e. NOT Bob)

Но если я заставлю Rails оценивать и возвращать данные из каждого соединения, то сохранение пройдет успешно.

gp = Grandparent.find(1)
gp.parents
gp.parents.first
gp.parents.first.children
gp.parents.first.children.first
gp.parents.first.children.first.first_name = "Bob"
gp.save
gp.parents.first.children.first.first_name  ## -> "Bob"

Если я впоследствии снова выполню gp = Grandparent.find(1), то я сбросил все это и должен снова принудительно оценить ассоциации.

Это преднамеренное поведение или я что-то не так сделал? Нужно ли мне вешать автосохранение на подключения к таблице соединений, а также (или вместо) соединения has_many :through?

Из документации я вижу, что "загруженные" элементы будут сохранены. Это то, что необходимо для их загрузки? Может ли кто-нибудь точно определить, что такое «загружено» и как достичь этого состояния?


person whognu    schedule 12.08.2014    source источник


Ответы (1)


Это происходит потому, что gp.parents кэширует parents в результатах Array, а затем parents.first фактически вызывает Array.first. Однако gp.parents.first каждый раз выполняет запрос с LIMIT 1 и каждый раз возвращает новый объект.

Вы можете подтвердить так:

gp.parents.first.object_id # performs new query (LIMIT 1)
=> 1

gp.parents.first.object_id # performs new query (LIMIT 1)
=> 2

gp.parents                 # performs and caches query for parents
gp.parents.first.object_id # returns first result from parents array
=> 1

gp.parents.first.object_id # returns first result from parents array
=> 1

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

gp.parents.first.children.first.update_attributes(first_name: "Bob")

person Damien Roche    schedule 13.08.2014
comment
Спасибо большое. Это странно. Вывод, который я получаю, заключается в том, чтобы не использовать .first, .last и т. д. в производственном коде. Боюсь, будет нереалистично выполнять поиск по всему нашему приложению, заменяя свойство, установленное знаком равенства, на update_attributes(). Есть ли что-то, что я должен был сделать по-другому, чтобы избежать этого? Лучше всего использовать update_attributes вместо object.property = value или вообще не полагаться на автосохранение? Думаю, я просто хочу знать, как лучше всего не допустить, чтобы это стало проблемой в будущем. - person whognu; 13.08.2014
comment
Кроме того, безопасно ли перебирать дочерние элементы родителя, обновляя каждого, а затем сохраняя родителя? - person whognu; 13.08.2014
comment
@whognu трудно сказать, как избежать этого в будущем, кроме как знать, когда вы имеете дело с кэшированными результатами или каждый раз с новым набором результатов. object.property = подходит, как и использование first/last. Но тогда ожидается, что object.save сохранится. Он будет сохранен как ассоциация только в том случае, если он принадлежит массиву дочерних элементов, которые уже загружены. - person Damien Roche; 13.08.2014
comment
Причина @whognu в том, что когда вы выполняете gp.parents.first самостоятельно, он выполняет запрос и возвращает первый объект. Но тогда, если вы выполните gp.parents, будет выполнен совершенно новый запрос, и поэтому вызов gp.parents.save сохранит изменения только в этом новом наборе результатов. Что вы делаете в этом случае, так это загружаете gp.parents и сразу же сохраняете в неизменном наборе результатов. Он ничего не знает о gp.parents.first. - person Damien Roche; 13.08.2014