модуль 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
Обратите внимание, что опция :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'
По умолчанию вы сможете только устанавливать и обновлять атрибуты связанной модели. Если вы хотите удалить связанную модель через атрибуты, вы должны сначала включить её, используя опцию :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
Обратите внимание, что модель не будет удалена до тех пор, пока родительская запись не будет сохранена.
Один-ко-многим
Рассмотрим Member, который имеет несколько записей Post:
class Member < ActiveRecord::Base has_many :posts accepts_nested_attributes_for :posts end
Теперь вы можете устанавливать или обновлять атрибуты связанных записей Post через хеш атрибутов для Member: включите ключ :posts_attributes со значением в виде массива хешей атрибутов записей Post.
Для каждого хеша, у которого нет ключа 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 proc, чтобы тихо игнорировать любые хеши новых записей, если они не пройдут ваши критерии. Например, предыдущий пример можно переписать как:
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(attributed)
attributed['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'
По умолчанию связанные записи защищены от удаления. Если вы хотите удалить какие-либо связанные записи через хеш атрибутов, вам нужно сначала включить это, используя опцию :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 для одного из таких ключей запрещено, в противном случае хеш будет обернут в массив и интерпретирован как хеш атрибутов для одной записи Post.
Передача атрибутов для связанного набора в виде хеша хешей может быть использована с хешами, сгенерированными из параметров 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
Публичные методы экземпляров
Определяет метод записи атрибутов для указанной(ых) ассоциации(й).
Поддерживаемые опции:
- :allow_destroy
-
Если true, удаляет любые элементы из хеша атрибутов с ключом
_destroyи значением, которое оценивается какtrue(например, 1, '1', true или 'true'). Эта опция отключена по умолчанию. - :reject_if
-
Позволяет указать Proc или символ, указывающий на метод, проверяющий, должна ли быть создана запись для определенного хеша атрибутов. Хеш передается в предоставленный Proc или метод, и он должен вернуть либо
true, либоfalse. Если :reject_if не указан, запись будет создана для всех хешей атрибутов, у которых нет значения_destroy, которое оценивается как true. Передача:all_blankвместо Proc создаст proc, который отклонит запись, где все атрибуты пусты, за исключением любого значения для _destroy. - :limit
-
Позволяет указать максимальное количество связанных записей, которые могут быть обработаны с помощью атрибутов вложенных записей. Limit также можно указать в виде Proc или символа, указывающего на метод, который должен возвращать число. Если размер массива атрибутов вложенных записей превышает указанный limit, генерируется исключение 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
# File activerecord/lib/active_record/nested_attributes.rb, line 301
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
© 2004–2016 David Heinemeier Hansson
Licensed under the MIT License.