модуль ActiveRecord::NestedAttributes::ClassMethods
Вложенные атрибуты Active Record
Вложенные атрибуты позволяют сохранять атрибуты связанных записей через родительскую запись. По умолчанию обновление вложенных атрибутов отключено; его можно включить с помощью метода класса accepts_nested_attributes_for. При включении вложенных атрибутов в модели определяется метод записи атрибутов.
Метод записи атрибутов называется по имени ассоциации. Это означает, что в следующем примере в модель добавляются два новых метода:
author_attributes=(attributes) и pages_attributes=(attributes).
class Book < ActiveRecord::Base has_one :author has_many :pages accepts_nested_attributes_for :author, :pages end
Обратите внимание: для каждой ассоциации, к которой применяется accepts_nested_attributes_for, параметр :autosave включается автоматически.
Один к одному
Рассмотрим модель Member, у которой есть один Avatar:
class Member < ActiveRecord::Base has_one :avatar accepts_nested_attributes_for :avatar end
Включение вложенных атрибутов для ассоциации «один к одному» позволяет создать участника и аватар за один раз:
params = { member: { name: 'Jack', avatar_attributes: { icon: 'smiling' } } }
member = Member.create(params[:member])
member.avatar.id # => 2
member.avatar.icon # => 'smiling'
Это также позволяет обновить аватар через участника:
params = { member: { avatar_attributes: { id: '2', icon: 'sad' } } }
member.update params[:member]
member.avatar.icon # => 'sad'
Чтобы обновить текущий аватар без указания идентификатора, необходимо добавить параметр :update_only.
class Member < ActiveRecord::Base
has_one :avatar
accepts_nested_attributes_for :avatar, update_only: true
end
params = { member: { avatar_attributes: { icon: 'sad' } } }
member.update params[:member]
member.avatar.id # => 2
member.avatar.icon # => 'sad'
По умолчанию можно задавать и обновлять только атрибуты связанной модели. Чтобы уничтожить связанную модель через хеш атрибутов, сначала необходимо включить эту возможность с помощью параметра :allow_destroy.
class Member < ActiveRecord::Base has_one :avatar accepts_nested_attributes_for :avatar, allow_destroy: true end
Теперь, если добавить в хеш атрибутов ключ _destroy со значением, которое при вычислении дает true, связанная модель будет уничтожена:
member.avatar_attributes = { id: '2', _destroy: '1' }
member.avatar.marked_for_destruction? # => true
member.save
member.reload.avatar # => nil
Обратите внимание: модель не будет уничтожена, пока не будет сохранена родительская модель.
Также обратите внимание: модель не будет уничтожена, если в обновленном хеше не указан ее идентификатор.
Один ко многим
Рассмотрим участника, у которого есть несколько публикаций:
class Member < ActiveRecord::Base has_many :posts accepts_nested_attributes_for :posts end
Теперь можно задавать или обновлять атрибуты связанных публикаций через хеш атрибутов участника: добавьте ключ :posts_attributes со значением в виде массива хешей атрибутов публикаций.
Для каждого хеша, в котором нет ключа id, будет создана новая запись, если только хеш не содержит ключ _destroy, значение которого при вычислении дает true.
params = { member: {
name: 'joe', posts_attributes: [
{ title: 'Kari, the awesome Ruby documentation browser!' },
{ title: 'The egalitarian assumption of the modern citizen' },
{ title: '', _destroy: '1' } # this will be ignored
]
}}
member = Member.create(params[:member])
member.posts.length # => 2
member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!'
member.posts.second.title # => 'The egalitarian assumption of the modern citizen'
Также можно задать Proc :reject_if, чтобы без уведомления игнорировать хеши новых записей, не соответствующие вашим критериям. Например, предыдущий пример можно переписать так:
class Member < ActiveRecord::Base
has_many :posts
accepts_nested_attributes_for :posts, reject_if: proc { |attributes| attributes['title'].blank? }
end
params = { member: {
name: 'joe', posts_attributes: [
{ title: 'Kari, the awesome Ruby documentation browser!' },
{ title: 'The egalitarian assumption of the modern citizen' },
{ title: '' } # this will be ignored because of the :reject_if proc
]
}}
member = Member.create(params[:member])
member.posts.length # => 2
member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!'
member.posts.second.title # => 'The egalitarian assumption of the modern citizen'
В качестве альтернативы, :reject_if также принимает символ, указывающий на метод:
class Member < ActiveRecord::Base
has_many :posts
accepts_nested_attributes_for :posts, reject_if: :new_record?
end
class Member < ActiveRecord::Base
has_many :posts
accepts_nested_attributes_for :posts, reject_if: :reject_posts
def reject_posts(attributes)
attributes['title'].blank?
end
end
Если хеш содержит ключ id, соответствующий уже связанной записи, эта запись будет изменена:
member.attributes = {
name: 'Joe',
posts_attributes: [
{ id: 1, title: '[UPDATED] An, as of yet, undisclosed awesome Ruby documentation browser!' },
{ id: 2, title: '[UPDATED] other post' }
]
}
member.posts.first.title # => '[UPDATED] An, as of yet, undisclosed awesome Ruby documentation browser!'
member.posts.second.title # => '[UPDATED] other post'
Однако это работает, если родительская модель тоже обновляется. Например, если нужно создать member с именем joe и одновременно обновить posts, возникнет ошибка ActiveRecord::RecordNotFound.
По умолчанию связанные записи защищены от уничтожения. Чтобы уничтожать связанные записи через хеш атрибутов, сначала необходимо включить эту возможность с помощью параметра :allow_destroy. Это также позволит использовать ключ _destroy для уничтожения существующих записей:
class Member < ActiveRecord::Base
has_many :posts
accepts_nested_attributes_for :posts, allow_destroy: true
end
params = { member: {
posts_attributes: [{ id: '2', _destroy: '1' }]
}}
member.attributes = params[:member]
member.posts.detect { |p| p.id == 2 }.marked_for_destruction? # => true
member.posts.length # => 2
member.save
member.reload.posts.length # => 1
Вложенные атрибуты для связанной коллекции также можно передать в виде хеша хешей, а не массива хешей:
Member.create(
name: 'joe',
posts_attributes: {
first: { title: 'Foo' },
second: { title: 'Bar' }
}
)
что дает тот же результат, что и
Member.create(
name: 'joe',
posts_attributes: [
{ title: 'Foo' },
{ title: 'Bar' }
]
)
В этом случае ключи хеша, являющегося значением :posts_attributes, игнорируются. Однако нельзя использовать 'id' или :id в качестве одного из таких ключей: в противном случае хеш будет обернут в массив и интерпретирован как хеш атрибутов одной публикации.
Передача атрибутов связанной коллекции в виде хеша хешей подходит для хешей, созданных из параметров HTTP/HTML, когда естественного способа отправить массив хешей может не быть.
Сохранение
Все изменения моделей, включая уничтожение записей, помеченных для уничтожения, автоматически и атомарно сохраняются и применяются при сохранении родительской модели. Это происходит внутри транзакции, запускаемой методом сохранения родительской модели. См. ActiveRecord::AutosaveAssociation.
Проверка наличия родительской модели
Ассоциация belongs_to по умолчанию проверяет наличие родительской модели. Это поведение можно отключить, указав optional: true. Например, это можно использовать для условной проверки наличия родительской модели:
class Veterinarian < ActiveRecord::Base
has_many :patients, inverse_of: :veterinarian
accepts_nested_attributes_for :patients
end
class Patient < ActiveRecord::Base
belongs_to :veterinarian, inverse_of: :patients, optional: true
validates :veterinarian, presence: true, unless: -> { awaiting_intake }
end
Обратите внимание: если не указать параметр :inverse_of, Active Record попытается автоматически определить обратную ассоциацию на основе эвристик.
Для вложенных ассоциаций «один к одному», если перед назначением самостоятельно создать новый объект дочерней модели в памяти, этот модуль не будет его перезаписывать. Например:
class Member < ActiveRecord::Base
has_one :avatar
accepts_nested_attributes_for :avatar
def avatar
super || build_avatar(width: 200)
end
end
member = Member.new
member.avatar_attributes = {icon: 'sad'}
member.avatar.width # => 200
Создание форм с вложенными атрибутами
Используйте ActionView::Helpers::FormHelper#fields_for, чтобы создавать элементы формы для вложенных атрибутов.
Параметры теста Integration должны отражать структуру формы. Например:
post members_path, params: {
member: {
name: 'joe',
posts_attributes: {
'0' => { title: 'Foo' },
'1' => { title: 'Bar' }
}
}
}
Константы
- REJECT_ALL_BLANK_PROC
Открытые методы экземпляра
# File activerecord/lib/active_record/nested_attributes.rb, line 351
def accepts_nested_attributes_for(*attr_names)
options = { allow_destroy: false, update_only: false }
options.update(attr_names.extract_options!)
options.assert_valid_keys(:allow_destroy, :reject_if, :limit, :update_only)
options[:reject_if] = REJECT_ALL_BLANK_PROC if options[:reject_if] == :all_blank
attr_names.each do |association_name|
if reflection = _reflect_on_association(association_name)
reflection.autosave = true
define_autosave_validation_callbacks(reflection)
nested_attributes_options = self.nested_attributes_options.dup
nested_attributes_options[association_name.to_sym] = options
self.nested_attributes_options = nested_attributes_options
type = (reflection.collection? ? :collection : :one_to_one)
generate_association_writer(association_name, type)
else
raise ArgumentError, "No association found for name `#{association_name}'. Has it been defined yet?"
end
end
end Определяет метод записи атрибутов для указанных ассоциаций.
Поддерживаемые параметры:
- :allow_destroy
-
Если значение равно true, уничтожает записи из хеша атрибутов, у которых есть ключ
_destroyсо значением, которое при вычислении даетtrue(например, 1, ‘1’, true или ‘true’). По умолчанию этот параметр имеет значение false. - :reject_if
-
Позволяет указать Proc или
Symbol, ссылающийся на метод, который проверяет, следует ли создавать запись для заданного хеша атрибутов. Хеш передается указанному Proc или методу, который должен вернутьtrueлибоfalse. Если:reject_ifне указан, запись будет создана для всех хешей атрибутов, в которых значение_destroyне дает true при вычислении. Если вместо Proc передать:all_blank, будет создан Proc, отклоняющий запись, у которой все атрибуты пусты, за исключением значения_destroy. - :limit
-
Позволяет указать максимальное количество связанных записей, которые можно обработать с помощью вложенных атрибутов. В качестве ограничения также можно указать Proc или
Symbol, ссылающийся на метод, который должен возвращать число. Если размер массива вложенных атрибутов превышает указанное ограничение, возникает исключениеNestedAttributes::TooManyRecords. Если параметр не указан, можно обработать любое количество ассоциаций. Обратите внимание: параметр:limitприменим только к ассоциациям «один ко многим». - :update_only
-
Для ассоциации «один к одному» этот параметр позволяет указать, как использовать вложенные атрибуты, если связанная запись уже существует. Обычно существующую запись можно либо обновить новым набором значений атрибутов, либо заменить совершенно новой записью с этими значениями. По умолчанию параметр
:update_onlyимеет значение false, и вложенные атрибуты используются для обновления существующей записи, только если содержат значение:idэтой записи. В противном случае будет создана новая запись, которая заменит существующую. Однако если параметр:update_onlyимеет значение true, вложенные атрибуты всегда используются для обновления атрибутов записи независимо от наличия:id. Для ассоциаций с коллекцией этот параметр игнорируется.
Примеры:
# creates avatar_attributes=
accepts_nested_attributes_for :avatar, reject_if: proc { |attributes| attributes['name'].blank? }
# creates avatar_attributes=
accepts_nested_attributes_for :avatar, reject_if: :all_blank
# creates avatar_attributes= and posts_attributes=
accepts_nested_attributes_for :avatar, :posts, allow_destroy: true
© 2004–2021 David Heinemeier Hansson
Licensed under the MIT License.