Help us understand the problem. What is going on with this article?

複数の子レコードを作成・更新する. accepts_nested_attributes_for

More than 5 years have passed since last update.

Active Record Nested Attributesの記事の抜粋です。

accepts_nested_attributes_forは、親子関係のある関連モデル(Project has_many :tasks や Enquate has_many :questionsなど)で、親から子を作成したり保存したりするときに使える。

1対1の時

class Member < ActiveRecord::Base
  has_one :avatar
  accepts_nested_attributes_for :avatar
end

create

params = { member: { name: 'Jack', avatar_attributes: { icon: 'smiling' } } }
member = Member.create(params[:member])
member.avatar.id # => 2
member.avatar.icon # => 'smiling'

update

params = { member: { avatar_attributes: { id: '2', icon: 'sad' } } }
member.update params[:member]
member.avatar.icon # => 'sad'

destroy

allow_destroyで削除可能を明示する.

class Member < ActiveRecord::Base
  has_one :avatar
  accepts_nested_attributes_for :avatar, allow_destroy: true
end
member.avatar_attributes = { id: '2', _destroy: '1' }
member.avatar.marked_for_destruction? # => true
member.save
member.reload.avatar # => nil

1対多

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

create

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とmethodのシンボルで指定可能

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'

method

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

update

パラメータの子の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'

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

nestされた子レコードのパラメータの書式

ハッシュでも配列でもどちらも同じ

#ハッシュ
Member.create(name:             'joe',
              posts_attributes: { first:  { title: 'Foo' },
                                  second: { title: 'Bar' } })
#配列(ハッシュの場合と同じ意味.)
Member.create(name:             'joe',
              posts_attributes: [ { title: 'Foo' },
                                  { title: 'Bar' } ])

save

親を保存すると、自動的に子レコードも保存する。

validation

親子関係が存在することをチェック.validates_presence_of and inverse_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

親から子レコードをメモリ上に生成する際に,初期値を指定する(1対1)

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

その他のオプション

:limit

1対多関連において、子レコードの上限を設定する

:update_only(デフォルトではfalse)

1対1関連において、このオプションがfalseの場合、パラメータに子レコードのidがある場合は既存の子レコードを更新するけど、無い場合は新しい子レコードを作る.らしい。オプションがtrueの場合は、idの有無に関わらず既存の子レコードを更新する。

余談

railsって、やりたいっていう機能を調べる事が多くてちょっと面倒。(まぁその分、”共通知識と楽さ”を提供してくれてるのだろうけど...)

これが複合キーでも問題なく使えれば、既存DBを使用したrailsも少し楽になりそう。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away