модуль ActiveRecord::NestedAttributes::ClassMethods
Active Record Вложенные Attributes
Вложенные атрибуты позволяют сохранять атрибуты связанных записей через родительскую запись. По умолчанию обновление вложенных атрибутов выключено, и вы можете включить его, используя метод класса 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
Обратите внимание, что опция :autosave автоматически включается для каждой ассоциации, для которой используется accepts_nested_attributes_for.
Один-к-одному
Рассмотрим модель Member, которая имеет один Avatar:
class Member < ActiveRecord::Base has_one :avatar accepts_nested_attributes_for :avatar end
Включение вложенных атрибутов для ассоциации один-к-одному позволяет создать member и avatar за один раз:
params = { member: { name: 'Jack', avatar_attributes: { icon: 'smiling' } } }
member = Member.create(params[:member])
member.avatar.id # => 2
member.avatar.icon # => 'smiling'
Это также позволяет обновить avatar через member:
params = { member: { avatar_attributes: { id: '2', icon: 'sad' } } }
member.update params[:member]
member.avatar.icon # => 'sad'
Если вы хотите обновить текущий avatar, не предоставляя id, вам необходимо добавить опцию :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
Обратите внимание, что модель не будет удалена до тех пор, пока родительская модель не будет сохранена.
Также обратите внимание, что модель не будет удалена, если вы также не укажете её id в обновлённом хэше.
Один-ко-многим
Рассмотрим member, у которого есть несколько постов:
class Member < ActiveRecord::Base has_many :posts accepts_nested_attributes_for :posts end
Теперь вы можете установить или обновить атрибуты связанных постов через хэш атрибутов для member: включите ключ :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'
Вы также можете установить :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.
Проверка наличия родительской модели
Если вы хотите проверить, что дочерняя запись связана с родительской записью, вы можете использовать метод validates_presence_of и ключ :inverse_of, как показано в этом примере:
class Member < ActiveRecord::Base has_many :posts, inverse_of: :member accepts_nested_attributes_for :posts end class Post < ActiveRecord::Base belongs_to :member, inverse_of: :posts validates_presence_of :member 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
Константы
- REJECT_ALL_BLANK_PROC
Открытые методы экземпляра
# File activerecord/lib/active_record/nested_attributes.rb, line 332
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'). Эта опция отключена по умолчанию. - :reject_if
-
Позволяет указать Proc или
Symbol, указывающий на метод, который проверяет, должна ли быть создана запись для определённого хэша атрибутов. Хэш передаётся в предоставленный Proc или метод, и он должен вернуть либоtrue, либоfalse. Когда:reject_ifне указан, запись будет создана для всех хэшей атрибутов, у которых нет значения_destroy, которое оценивается как true. Передача:all_blankвместо Proc создаст proc, который отклонит запись, где все атрибуты пустые, за исключением любого значения для_destroy. - :limit
-
Позволяет указать максимальное количество связанных записей, которые могут быть обработаны с вложенными атрибутами. 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–2020 David Heinemeier Hansson
Licensed under the MIT License.