Spec-Zone.ru › Ruby on Rails 7.1

модуль 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, у которого есть несколько постов:

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'

Вы также можете установить 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

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

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 или метод, проверяющий, должна ли быть создана запись для определенного хеша атрибутов. Хеш передается в указанный Proc или метод, и он должен возвращать либо true, либо false. Когда не указан :reject_if, запись будет создана для всех хешей атрибутов, которые не имеют значение _destroy, которое вычисляется как true. Передача :all_blank вместо Proc создаст proc, который отклонит запись, где все атрибуты пусты, за исключением любого значения для _destroy.

:limit

Позволяет указать максимальное количество связанных записей, которые могут быть обработаны с помощью вложенных атрибутов. Предел также может быть указан в виде Proc или метода, который должен возвращать число. Если размер массива вложенных атрибутов превышает указанный предел, возникает исключение 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