Spec-Zone.ru › Ruby on Rails 7.2

модуль 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'

Если вы хотите обновить текущий 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, у которого есть несколько записей поста (posts):

class Member < ActiveRecord::Base
  has_many :posts
  accepts_nested_attributes_for :posts
end

Теперь вы можете устанавливать или обновлять атрибуты связанных записей posts через хеш атрибутов для 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 и обновить member одновременно, это вызовет ошибку 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

Методы публичного экземпляра

accepts_nested_attributes_for(*attr_names) Показать исходный код
# 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. Передача :all_blank вместо Proc создаёт 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.

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API