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

[Active Record Nested Attributes](http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html)の記事の抜粋です。

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

# 1対1の時

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

## create

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

## update

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

## destroy

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

```rb
class Member < ActiveRecord::Base
  has_one :avatar
  accepts_nested_attributes_for :avatar, allow_destroy: true
end
```

```rb
member.avatar_attributes = { id: '2', _destroy: '1' }
member.avatar.marked_for_destruction? # => true
member.save
member.reload.avatar # => nil
```

# １対多

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

## create

```rb
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

```rb
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

```rb
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が、既に存在する場合、そのレコードを更新する。
（無い場合は要確認。新規に作る・・・？）

```rb
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

```rb
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された子レコードのパラメータの書式

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

```rb
#ハッシュ
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ってなにするの?)

```rb
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)

```rb
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
１対多関連において、子レコードの上限を設定する

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

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

# 余談

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

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