accepts_nested_attributes_forの:allow_destroyオプション

accepts_nested_attributes_forメソッドはRails2.3からの新機能、nested_formを実現するためにactiverecordに追加されたメソッドです。

例えば、

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

というモデルがあった場合に、アクションに

params = { :member => {
  :name => 'joe', 
  :posts_attributes => {
    '0' =>{ :title => 'Kari, the awesome ruby documentation browser!' },
    '1' => { :title => 'The egalitarian assumption of the modern citizen' }
  }
}}

っていうようなパラメータが渡って来たときに、

member = Member.create(params['member'])

ってやると、

member.posts.length # => 2
member.posts.count # => 2
member.posts.first.title # => 'Kari, the awesome ruby documentation browser!'
member.posts.second.title # => 'The egalitarian assumption of the modern citizen'

って感じになります。


こりゃ便利ー!って訳で早速使い出しましたが、問題は更新時。これって該当のpostsを全部deleteして、insertし直したりしないよね?って不安になりましたが、こんな感じのパラメータ

params = { :member => {
  :name => 'joe', 
  :posts_attributes => {
    '0' =>{ :title => 'Kari, the awesome Ruby documentation browser!' },
    '1' => { :title => 'The egalitarian assumption of the modern citizen' }
  }
}}

っていうのを渡して、例えばupdateアクションで

member = Member.find(params[:id])
member.update_attributes(params['member'])

ってやると、

member.posts.length # => 2
member.posts.count # => 2
member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!'
member.posts.second.title # => 'The egalitarian assumption of the modern citizen'

と更新されて、postsはちゃんとupdateされます。


んで、更に気になるのは、削除はどうすんのよ?ってこと。答えはちょー簡単でした。モデルを変更して、accepts_nested_attributes_forに :allow_destroy => trueのオプションを指定して、_deleteというパラメータが渡ってくるようにビューを変更するだけ!なので、

class Member < ActiveRecord::Base
   has_many :posts
    accepts_nested_attributes_for :posts, :allow_destroy => true
end

と変更してあげて、

params = { :member => {
  :name => 'joe', 
  :posts_attributes => {
    '0' => {:id => '1', :title => 'Kari, the awesome Ruby documentation browser!' },
    '1' => {:id => '2', :title => 'The egalitarian assumption of the modern citizen', :_delete => '1' }
  }
}}

っていう風にパラメータが来るようにビューを変更してあげれば、

member = Member.find(params[:id])
member.update_attributes(params['member'])

ってやると、

member.posts.length # => 2
member.posts.count # => 1
member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!'
member.posts.second.title # => 'The egalitarian assumption of the modern citizen'

気をつけないといけないのは、update_attributes直後のココ。

member.posts.length # => 2
member.posts.count # => 1

オブジェクトを保持している数を表すlengthは2だけど、DB上の数を表すcountは1になっているということ。




それから、ちょっと気になるのがバリデーション。素晴らしいのは、ちゃんと関係するオブジェクト全てのバリデーションがパスしないとDBへの反映がされないっちゅうことですね。
なので、accepts_nested_attributes_forで指定した関連の属性を送る場合は、単独のオブジェクトの場合と違って、属性を設定された関連するオブジェクト群のバリデーションが実行されて、それぞれの登録・更新・削除が実行されます。



上の例では、has_manyの関連で説明しましたが、has_one/belongs_to/has_and_belongs_to_manyの関連でも使用可能です。すばらしー。
これは複雑なオブジェクトを操作する際には欠かせませんね!