カテゴリー:
Rails
タグ:
 Rails accepts_nested_attributes_for

このエントリーをはてなブックマークに追加
更新日時:
2013年10月24日(木)
作成日時:
2013年02月06日(水)

前の記事 / 次の記事

Railsでモデルを保存する時に、
accepts_nested_attributes_for を使ってアソシエーション先のモデルも含めて一発で保存する。

目次

やりたいこと

例えば、次のようなモデルがあって、

class Book < ActiveRecord::Base
  attr_accessible :author_id, :title
  belongs_to :author
end

class Author < ActiveRecord::Base
  attr_accessible :name
  has_many :books
end

BookとAuthorを同時に保存したい時、こう書いて一発で保存したい。

class BooksController < ApplicationController

  def create
    @book = Book.new(params[:book])

    respond_to do |format|
      if @book.save
        format.html { redirect_to @book }
       else
        format.html { render action: "new"}
       end
    end
  end

end

やること

  1. モデルに accepts_nested_attributes_for を定義する。
  2. モデルに attr_accessible を定義する。

1. accepts_nested_attributes_for の定義

指定したアソシエーションをネストされた状態で受け取ってそのまま保存出来るようになる。

2. モデルに attr_accessible を定義する

※ Rails4で使う場合は attr_accesible を指定するのではなく、対応する StrongParameters を有効にする必要がある。

accepts_nested_attributes_for を指定した時、
キー xxxxx_attributes に格納された値がネストされたモデルの値となる(xxxxxはアソシエーション名)。
従ってこの値にアクセス出来るようにしておかないと保存出来ない。

以上2点を踏まえて、モデルを次に次のように定義する。

class Book < ActiveRecord::Base
  attr_accessible :author_id, :title, :author_attributes
  belongs_to :author
  accepts_nested_attributes_for :author
end

これで次のようなハッシュを受けた取った時に、
ネストされたモデルも含めて一括して保存されるようになる。

{
  :book => {
    :title             => "吾輩は猫である",
    :author_attributes => { :name => "夏目漱石" }
  }
}

次のようなコントローラーとビューを書くと、Railsが適切にキーを割り振ってくれる。

コントローラー

class BooksController < ApplicationController

  def new
    @book = Book.new
    @book.build_author

    respond_to do |format|
      format.html
    end
  end

end

ここで、 @book.build_author としているのは重要で、
ビューで表示する時点で accepts_nested_attributes_for で指定した
ネスト対象のモデルを保持していないとネストすることが出来ない。

ビュー

-# haml
=form_for @book do |f|
  = f.label :title
  = f.text_field :title

  = f.fields_for :author |f_author|
    = f_author.label :name
    = f_author.text_field :name

この時ネスト対象のフォームを作成する fields_for で指定するのは
アソシエーション名の シンボルか文字列 であり、例えば次のように指定してはならない。

-# haml
=form_for @book do |f|
  = f.label :title
  = f.text_field :title

  = f.fields_for @author |f_author|
    = f_author.label :name
    = f_author.text_field :name

付随する機能

  1. allow_destroy
  2. reject_if

1. allow_destroy

ネストしたモデルを同時に保存する場合は上記のやり方で出来るが、
同時に削除 する場合はどうしたらいいのか?

allow_destroy を有効にすることで、trueである"_destroy"パラメーターを渡した時に削除されるようになる。
具体的には次のようにして使う。

モデル

class Book < ActiveRecord::Base
  attr_accessible :author_id, :title, :author_attributes
  belongs_to :author
  accepts_nested_attributes_for :author,
                                :allow_destroy => true
end

ビュー

-# haml
=form_for @book do |f|
  = f.label :title
  = f.text_field :title

  = f.fields_for :author |f_author|
    = f_author.label :name
    = f_author.text_field :name
    = f_author.check_box :_destroy

この時に、:_destroy がチェックされていたらネストしたモデルが削除されるようになる。
パラメーター名は "destroy" ではなく "_destroy" なので注意。

2. reject_if

必ずしもネストしたモデルを保存したいとは限らない、
今回の例で言えば、新しいBookのAuthorが今までに登録されていない場合に限り、同時に保存したい。
既に登録済みのAuthorであれば、既存のレコードを使うので新たに登録する必要は無い。

reject_if を使うことで、保存が不要な場合は、そもそもネストされていなかったことに出来る。
逆に言うと、reject_if を指定しておかないと、保存する必要があってもなくても必ず保存される。
具体的には次のように使う。

モデル

class Book < ActiveRecord::Base
  attr_accessible :author_id, :title, :author_attributes
  belongs_to :author
  accepts_nested_attributes_for :author,
                                :allow_destroy => true
                                :reject_if     => :reject_author

  def reject_author(attributed)
    attributed['name'].blank?
  end
end

ビュー (変更は無し)

-# haml
=form_for @book do |f|
  = f.label :title
  = f.text_field :title

  = f.fields_for :author |f_author|
    = f_author.label :name
    = f_author.text_field :name
    = f_author.check_box :_destroy

reject_if で指定したメソッドが 真の時
そもそもネストされていなかったことになり、ネストしたモデルは保存されない。

また、 reject_ifを指定する時、 :all_blank を指定することで、
全ての属性が blank?(nil or blank) の時 reject されるようになる。

accepts_nested_attributes_for :author,
                              :allow_destroy => true
                              :reject_if     => :all_blank

参考ページ

ネストされたモデル
ActiveRecord::NestedAttributes::ClassMethods