RailsのFormで配列を扱う
使用頻度が結構多いわりに、あまり詳しく書かれている本が見当たらないので、まとめメモ。
text_field等のヘルパーを使いつつ複数の要素を配列として取得したい時がある。リレーションでいうとhas_manyな要素をまとめて作成したい時とか。Helper使わずにHTMLタグ書いちゃえって思うこともあるけども、Helper使うとやっぱり楽だ。
(Helperを使わないときは↓のようになる)
<input type="text" name="book[0][name]">
今回は特に配列な要素を新規作成したいケースで。
例えば各ユーザー(User)がお気に入りの本(Book)を3個登録したいとき。
(Userがhas_many :booksで Bookがbelongs_to :user)
(今回はUserも同時に作成したい)
Controller
def new @user = User.new @books = (1..3).map do Book.new end end def create user = User.create(params[:user]) for book in params[:book] book[1].update(:user_id => user.id) end Book.create(params[:book].values) end
View
<% form_tag :action => :create do %> あなたの名前とお気に入りの本を3冊書いてね なまえ:<%= text_field :user, :name %><br /> <% @books.each_with_index do |@book, i| %> 本<%= i %> <%= text_field "book[]", :name, :index => i %><br /> <% end %> <br /> <%= submit_tag "Create" %> <% end %>
ポイントは簡単でtext_fieldの第一引数のモデル名を各ところに[]を書くこと。
そうすると配列のIndexはモデルのIDになるんだけども、Updateではなく今回のように新規作成でNewしたばかりのActiveRecordオブジェクトではエラーになる。
そこで:indexオプションを指定することで明示的に配列のIndexを指定できる。
今回はeach_with_indexで単純に連番を振っている。
更新の場合はIndex指定の必要はない。
<%= text_field "book[]", :name, :index => i %>
それと事前にARオブジェクトを作っておく必要もある。@books = (1..3).map{Book.new}←ここは好きな数で。
作らなかったり、@モデル名(厳密にはモデル名でなくてもtext_field第一引数名とそのオブジェクトの変数名を同じ)にしておかないととこんなエラーが出る。
object[] naming but object param and @object var don't exist or don't respond to id_before_type_cast: nil
今回はbelongs_to :user_idをMySQLでNOT NULL制約をかけていて、Userのレコードも同時に作成しているので、まずUserレコードを作成したのちに、そのUserのIDをParamsにUpdateしている。
事前にuser_idが分かっている場合はHiddenで埋め込めばいいっぽい?(未検証
あるいはNOT NULL制約をかけてない場合はCreate後にUpdateすることも可能か。
そうした場合Createのコードはもう少しシンプルになって
#Params内に親のIDが記録されている場合 Book.create(params[:book].values) #Params内にはないけど、Create後にUpdateする場合(この方法ではCreate後に各要素にブロックが実行されるのでNOT NULL制約時はエラー)。 Book.create(params[:book].values) do |book| book.user_id = user.id book.save end
こんな感じになる?
作成ではなく、まとめて更新するときは同様にAR#updateで、第一引数にはIDが入ったArray(Indexを指定しないとIDがIndex=Paramsのハッシュのキー)を追加。
Book.update(params[:book].keys, params[:book].values)
とことで!
参考サイト
http://www.pen-chan.jp/~tdiary/pen-chan/20070618.html
http://leaveanotemessagebehind.blogspot.com/2007/10/textfield-on-rails.html
http://api.rubyonrails.org/classes/ActiveRecord/Base.html#M002214