Nested Attributesって素晴らしそう。
複数のモデルを同時に登録する方法を模索した。
- 1アクションで複数のモデルを同時に保存するには?(ザリガニが見ていた...。)
- Railsで複数行更新(A Day In The Life)
- text_fieldなどのフォームを配列化する(ぺんちゃん日記)
簡単にはできなさそうなオーラがある。
そんなこんなで調べるとNested AttributesとNested Model Formsを使って親子オブジェクトを一括で登録/変更するには(株式会社 万葉)が見つかる。
2.3でないとダメらしい。
もうちっと調べると、やりたかったことがそのまま書いてあるところを発見する。Nested Attributes with RJS(Do Ruby!)
さて、現状は2.2.2で2.3系にするのはまだ先になるので、併用を考える。
C:\Users\kenmituo>gem update rails Updating installed gems Updating rails Successfully installed activesupport-2.3.4 Successfully installed activerecord-2.3.4 Successfully installed rack-1.0.1 Successfully installed actionpack-2.3.4 Successfully installed actionmailer-2.3.4 Successfully installed activeresource-2.3.4 Successfully installed rails-2.3.4 Gems updated: activesupport, activerecord, rack, actionpack, actionmailer, activ eresource, rails C:\Users\kenmituo>gem list rails *** LOCAL GEMS *** rails (2.3.4, 2.2.2)
既存のプロジェクトフォルダを丸ごとコピーして別名にする。
Aptana Studioで新しいプロジェクトを作成する。(over writeを聞かれるが全てNO)
config\enviroment.rbを修正する
# Specifies gem version of Rails to use when vendor/rails is not present
RAILS_GEM_VERSION = '2.3.4' unless defined? RAILS_GEM_VERSION
何も考えずにサーバを起動する
=> Booting Mongrel => Rails 2.3.4 application starting on http://127.0.0.1:3002 C:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/base.rb:2207 :in `class_of_active_record_descendant': undefined method `abstract_class?' for Object:Class (NoMethodError) from C:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/base.rb:1462:in `base_class' from C:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/base.rb:1138:in `reset_table_name' from C:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/base.rb:1134:in `table_name' from C:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/base.rb:3106:in `quoted_table_name' from C:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/base.rb:2226:in `sanitize_sql' from C:/Users/kenmituo/Documents/Aptana Studio/roommgr10/vendor/plugins/railswhere/lib/where.rb:169:in send' from C:/Users/kenmituo/Documents/Aptana Studio/roommgr10/vendor/plugins/railswhere/lib/where.rb:169:in 'initialize' from C:/Users/kenmituo/Documents/Aptana Studio/roommgr10/vendor/plugins/railswhere/lib/where.rb:156:in `new' ... 24 levels... from C:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in `require' from ./script/server:3 from -e:2:in `load' from -e:2
あれ?エラーになる。
railswhere が Rails 2.3.3 で動かない件(milk1000cc)を見つける。
vendor\plugins\railswhere\lib\where.rb 変更前:@criteria = ActiveRecord::Base.send(:sanitize_sql, criteria) 変更後:@criteria = ActiveRecord::Base.send(:sanitize_sql, criteria, nil)
これで動いた。
NoMethodError in Support/cableset#new Showing app/views/support/cableset/_form.html.erb where line #14 raised: undefined method `support_cableset_cablesethas_path' for #<ActionView::Base:0x5ce6da0>
2.3.2から「コントローラー_ほにゃらら_path」が無くなったとか、バグがあるとか。
とりあえず、バージョンを下げてみる
C:\Users\kenmituo\>gem install rails --version 2.3.3
変化が見られない。
form_forが悪いような気がした。サンプルの例を修正してみると、こんなかんじ。
変更前:<% form_for(@album) do |f| %> 変更後:<%- form_for :album, :url=>{:action=>:create, :id=>@album} do |f| -%>
画面は出るようになった。なんでだろう?まいっか。
つづき。
画面は表示されるようになったが「link_to_remote」の部分をクリックするとコンソールにエラーがでる。
モデルをサブディレクトリに格納しているのが悪そうだけど、解決策が分からない。
ActionView::TemplateError (Support::Cableset::Cablesetbes(#57766990) expected, got Array(#33640490)) on line #1 of app/views/support/cableset/add_cablesetbe.rjs: 1: @cableset = Support::Cableset::Cablesethas.new(params[:cableset])
子供のパラメータが入っているのがNGらしく、
params[:cableset].delete_if{|key, value| key == "cablesetbes"}
こんなことをやるとエラーは出なくなる。もちろん値は入らない。
無理矢理な感じをやってみる。
temp = Hash.new temp[:cablesetbes] = params[:cableset][:cablesetbes] params[:cableset].delete_if{|key, value| key == "cablesetbes"} @cableset = Support::Cableset::Cablesethas.new(params[:cableset]) @cableset.cablesetbes.build(temp[:cablesetbes]) @cableset.cablesetbes.build
パラメータ的には値が入っているけど、ビューで表示されない。
うーん・・・どうしよう?
ちょいと考え直して、モデルの入れ子を直して試す。
ActionView::TemplateError (Cablesetbe(#56904440) expected, got Array(#33640490)) on line #14 of app/views/support/cableset/add_cablesetbe.rjs: 14: @cableset = Cablesetha.new(params[:cableset]) 15: @cableset.cablesetbes.build app/views/support/cableset/add_cablesetbe.rjs:14:in `new' app/views/support/cableset/add_cablesetbe.rjs:14:in `__instance_exec0' app/views/support/cableset/add_cablesetbe.rjs:1:in `_run_rjs_app47views47support47cableset47add_cablesetbe46rjs' -e:2:in `load' -e:2
変わらない。
ここでパラメータに再注目する。
"cablesetha"=>{"returnded_name"=>"", "returnded"=>"", " lended"=>"", "cablesetbes"=>{{"room_name"=>"a", "room_time"=>"1" }}, "memo"=>"", "item_name"=>"", "lended_name"=>"", "member_name"=>""}}
Module ActiveRecord::NestedAttributes::ClassMethodsこれを読む。
分かったこと
accepts_nested_attributes_forを使って親子関係を結んだ場合のパラメータは
親=>親パラメータ,子_attributes{子パラメータ}
これを実現するにはform_forを正しく書かないとダメ。
参考ページを修正するなら
変更前:<% form_for(@album) do |f| %> 変更後:<%- form_for @album, :url=>{:action=>:create} do |f| -%>
もう一つの注意点がパラメータ。
参考ページの「@album = Album.new」とやるなら問題なし。「@cableset = Cablesetha.new」とかやると間違える。
パラメータは「親モデルの名前」で届くのだ。
コントローラー @cableset = Cablesetha.new @cableset.cablesetbes.build RJS @cableset = Cablesetha.new(params[:cablesetha]) @cableset.cablesetbes.build
うまく動くと、_form.html.erbで描画されるHTMLソースは
<input id="cablesetha_cablesetbes_attributes_0_room_time" name="cablesetha[cablesetbes_attributes][0][room_time]" size="30" type="text" />
こんな感じになる。
後半は有給休暇中に書いた。仕事虫になっているな・・・俺。
そして、モデルを入れ子にしてみた。
class Support::Cableset::Cablesetha < ActiveRecord::Base set_table_name 'cablesethas' has_many :cablesetbes, :class_name=>'Support::Cableset::Cablesetbe' accepts_nested_attributes_for :cablesetbes ,:reject_if => proc{|attirbutes| attirbutes['member_name'].blank?} end class Support::Cableset::Cablesetbe < ActiveRecord::Base set_table_name 'cablesetbes' belongs_to :cablesetha, :class_name=>'Support::Cableset::Cablesetha' end #RJSファイル @cableset = Support::Cableset::Cablesetha.new(params[:support_cableset_cablesetha]) @cableset.cablesetbes.build page.replace_html "cableset_form", :partial => 'form'
newとかパラメータが面倒になるけど、動いた。
当初の目的は果たせたよ。久しぶりの達成感!!