Spec-Zone.ru › Ruby on Rails 6.0

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

Включение вложенных атрибутов для ассоциации один-к-одному позволяет создать запись пользователя и аватара за один раз:

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'

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

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

Проверка наличия родительской модели

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

Открытые методы экземпляров

accepts_nested_attributes_for(*attr_names) Показать исходный код
# File activerecord/lib/active_record/nested_attributes.rb, line 333
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

Позволяет указать процедуру или символ, указывающий на метод, который проверяет, должна ли быть создана запись для определенного хеша атрибутов. Хеш передается в указанную процедуру или метод, и он должен вернуть либо true либо false. Если :reject_if не указан, запись будет создана для всех хешей атрибутов, которые не имеют значения _destroy, равного true. Передача :all_blank вместо процедуры создаст процедуру, которая отклонит запись, где все атрибуты пусты, за исключением любого значения для _destroy.

:limit

Позволяет указать максимальное количество связанных записей, которые могут быть обработаны с вложенными атрибутами. Ограничение также может быть задано как процедура или символ, указывающий на метод, который должен вернуть число. Если размер массива вложенных атрибутов превышает указанное ограничение, генерируется исключение NestedAttributes::TooManyRecords. Если ограничение опущено, может быть обработано любое количество ассоциаций. Обратите внимание, что опция :limit применима только к ассоциациям один-ко-многим.

:update_only

Для ассоциации один-к-одному эта опция позволяет указать, как будут использоваться вложенные атрибуты, когда связанная запись уже существует. В общем случае существующая запись может быть обновлена новым набором значений атрибутов или заменена полностью новой записью, содержащей эти значения. По умолчанию опция :update_only имеет значение false и вложенные атрибуты используются для обновления существующей записи только в том случае, если они содержат значение идентификатора записи. В противном случае будет создана новая запись и использована для замены существующей. Однако, если опция :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–2019 David Heinemeier Hansson
Licensed under the MIT License.

Spec-Zone.ru

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