модуль ActiveRecord::AutosaveAssociation
Автоматическое сохранение ассоциаций Active Record
AutosaveAssociation — это модуль, который автоматически сохраняет связанные записи при сохранении родительской записи. Помимо сохранения, он также удаляет связанные записи, помеченные для удаления. (См. mark_for_destruction и marked_for_destruction?).
Сохранение родительской записи и её ассоциаций, а также удаление помеченных ассоциаций происходят в рамках одной транзакции. Это гарантирует, что база данных не окажется в несогласованном состоянии.
Если проверка любой из ассоциаций завершится неудачно, сообщения об ошибках будут добавлены к родительской записи.
Обратите внимание: это также означает, что ассоциации, помеченные для удаления, не удаляются сразу. Однако они всё равно будут помечены для удаления.
Обратите внимание, что autosave: false — это не то же самое, что не объявлять :autosave. Если параметр :autosave не указан, новые записи ассоциаций сохраняются, а обновлённые записи ассоциаций — нет.
Проверка
Дочерние записи проверяются, если только :validate не равно false.
Обратные вызовы
Ассоциация с параметром autosave определяет несколько обратных вызовов для вашей модели (around_save, before_save, after_create, after_update). Обратите внимание, что обратные вызовы выполняются в том порядке, в котором они определены в модели. Не следует изменять содержимое ассоциаций до выполнения обратных вызовов autosave. Обычно рекомендуется размещать обратные вызовы после объявлений ассоциаций.
Пример связи «один к одному»
class Post < ActiveRecord::Base has_one :author, autosave: true end
Теперь изменения родительской записи и связанной с ней модели можно сохранять автоматически и атомарно:
post = Post.find(1) post.title # => "The current global position of migrating ducks" post.author.name # => "alloy" post.title = "On the migration of ducks" post.author.name = "Eloy Duran" post.save post.reload post.title # => "On the migration of ducks" post.author.name # => "Eloy Duran"
Чтобы удалить связанную модель в рамках операции сохранения родительской записи, достаточно пометить её для удаления:
post.author.mark_for_destruction post.author.marked_for_destruction? # => true
Обратите внимание: модель пока не удалена из базы данных:
id = post.author.id Author.find_by(id: id).nil? # => false post.save post.reload.author # => nil
Теперь она уже удалена из базы данных:
Author.find_by(id: id).nil? # => true
Пример связи «один ко многим»
Если :autosave не объявлен, новые дочерние записи сохраняются при сохранении родительской записи:
class Post < ActiveRecord::Base has_many :comments # :autosave option is not declared end post = Post.new(title: 'ruby rocks') post.comments.build(body: 'hello world') post.save # => saves both post and comment post = Post.create(title: 'ruby rocks') post.comments.build(body: 'hello world') post.save # => saves both post and comment post = Post.create(title: 'ruby rocks') comment = post.comments.create(body: 'hello world') comment.body = 'hi everyone' post.save # => saves post, but not comment
Если :autosave имеет значение true, сохраняются все дочерние записи, независимо от того, новые они или нет:
class Post < ActiveRecord::Base has_many :comments, autosave: true end post = Post.create(title: 'ruby rocks') comment = post.comments.create(body: 'hello world') comment.body = 'hi everyone' post.comments.build(body: "good morning.") post.save # => saves post and both comments.
Чтобы удалить одну из связанных моделей в рамках операции сохранения родительской записи, достаточно пометить её для удаления:
post.comments # => [#<Comment id: 1, ...>, #<Comment id: 2, ...]> post.comments[1].mark_for_destruction post.comments[1].marked_for_destruction? # => true post.comments.length # => 2
Обратите внимание: модель пока не удалена из базы данных:
id = post.comments.last.id Comment.find_by(id: id).nil? # => false post.save post.reload.comments.length # => 1
Теперь она уже удалена из базы данных:
Comment.find_by(id: id).nil? # => true
Ограничения
Обратите внимание, что autosave срабатывает для уже сохранённых записей ассоциаций только в том случае, если сами эти записи были изменены. Это предотвращает SystemStackError, вызванные циклическими проверками ассоциаций. Исключение составляет использование пользовательского контекста проверки: в этом случае проверки связанных записей выполняются всегда.
Публичные методы экземпляра
# File activerecord/lib/active_record/autosave_association.rb, line 284
def autosaving_belongs_to_for?(association)
@autosaving_belongs_to_for ||= {}
@autosaving_belongs_to_for[association]
end # File activerecord/lib/active_record/autosave_association.rb, line 275 def changed_for_autosave? new_record? || has_changes_to_save? || marked_for_destruction? || nested_records_changed_for_autosave? end
Возвращает значение, указывающее, изменялась ли эта запись каким-либо образом (в том числе изменились ли какие-либо её вложенные ассоциации autosave).
# File activerecord/lib/active_record/autosave_association.rb, line 269 def destroyed_by_association @destroyed_by_association end
Возвращает ассоциацию для удаляемой родительской записи.
Используется, чтобы избежать ненужного обновления счётчика кэша.
# File activerecord/lib/active_record/autosave_association.rb, line 262 def destroyed_by_association=(reflection) @destroyed_by_association = reflection end
Записывает ассоциацию, которая удаляется, в процессе чего удаляется и эта запись.
# File activerecord/lib/active_record/autosave_association.rb, line 249 def mark_for_destruction @marked_for_destruction = true end
Помечает эту запись для удаления в рамках транзакции сохранения родительской записи. Запись не удаляется немедленно: дочерняя запись будет удалена при вызове parent.save.
Имеет смысл, только если для этой связанной модели включён параметр :autosave родительской записи.
# File activerecord/lib/active_record/autosave_association.rb, line 256 def marked_for_destruction? @marked_for_destruction end
Возвращает значение, указывающее, будет ли эта запись удалена в рамках транзакции сохранения родительской записи.
Имеет смысл, только если для этой связанной модели включён параметр :autosave родительской записи.
# File activerecord/lib/active_record/autosave_association.rb, line 238 def reload(options = nil) @marked_for_destruction = false @destroyed_by_association = nil super end
Как обычно, перезагружает атрибуты объекта и сбрасывает флаг marked_for_destruction.
# File activerecord/lib/active_record/autosave_association.rb, line 279
def validating_belongs_to_for?(association)
@validating_belongs_to_for ||= {}
@validating_belongs_to_for[association]
end
© 2004–2021 David Heinemeier Hansson
Licensed under the MIT License.