※ 20140307 一部追記修正しました
new/create時に関連テーブルも更新する記述について、忘れていたのでまとめてみました。
(意外と参考書とかにも載っておらず・・・)
今回利用しているバージョンは下記。
こんなイメージです。postを1件更新するときに関連テーブル(authors)と中間テーブル(author_posts)を複数件数一気に更新する感じです。
# app/models/post.rb class Post < ActiveRecord::Base has_many :author_posts has_many :authors, :through => :author_posts accepts_nested_attributes_for :authors end
# app/models/author.rb class Author < ActiveRecord::Base has_many :author_posts has_many :posts, :through => :author_posts end
# app/models/author_post.rb class AuthorPost < ActiveRecord::Base belongs_to :post belongs_to :author end
# postのインスタンスを作成 irb > post = Post.new => #<Post id: nil, title: nil, created_at: nil, updated_at: nil> # 空のインスタンスを確認 irb > post => #<Post id: nil, title: nil, created_at: nil, updated_at: nil> # post.author_postsの確認 # 空のArray irb > post.author_posts => #<ActiveRecord::Associations::CollectionProxy []> # post.authorsの確認 # 空のArray irb > post.authors => #<ActiveRecord::Associations::CollectionProxy []> # buildで、postの関連テーブルauthorのインスタンスを作成 irb > post.authors.build => #<Author id: nil, name: nil, created_at: nil, updated_at: nil> # post.authorsのArrayに、作成したインスタンスが格納されている irb > post.authors => #<ActiveRecord::Associations::CollectionProxy [#<Author id: nil, name: nil, created_at: nil, updated_at: nil>]> # 試しにもう一回buildしてみる irb > post.authors.build => #<Author id: nil, name: nil, created_at: nil, updated_at: nil> # post.authorsのArrayに、2個目のインスタンスがpushされている irb > post.author => #<ActiveRecord::Associations::CollectionProxy [#<Author id: nil, name: nil, created_at: nil, updated_at: nil>, #<Author id: nil, name: nil, created_at: nil, updated_at: nil>]> # post.author_posts のArrayはこの時点では空 irb > post.author_posts => #<ActiveRecord::Associations::CollectionProxy []> # saveしてみる # 関連テーブルにもINSERTされている irb > post.save (0.5ms) BEGIN SQL (4.4ms) INSERT INTO `posts` (`created_at`, `updated_at`) VALUES ('2014-02-23 08:20:58', '2014-02-23 08:20:58') SQL (0.2ms) INSERT INTO `authors` (`created_at`, `updated_at`) VALUES ('2014-02-23 08:20:58', '2014-02-23 08:20:58') SQL (0.3ms) INSERT INTO `author_posts` (`author_id`, `created_at`, `post_id`, `updated_at`) VALUES (3, '2014-02-23 08:20:58', 10, '2014-02-23 08:20:58') SQL (0.2ms) INSERT INTO `authors` (`created_at`, `updated_at`) VALUES ('2014-02-23 08:20:58', '2014-02-23 08:20:58') SQL (0.2ms) INSERT INTO `author_posts` (`author_id`, `created_at`, `post_id`, `updated_at`) VALUES (4, '2014-02-23 08:20:58', 10, '2014-02-23 08:20:58') (2.2ms) COMMIT => true # save後に post.author_posts を再確認 # Arrayに値が入っている irb > post.author_posts => #<ActiveRecord::Associations::CollectionProxy [#<Author id: 1, name: nil, created_at: "2014-02-23 08:41:41", updated_at: "2014-02-23 08:41:41">, #<Author id: 2, name: nil, created_at: "2014-02-23 08:41:41", updated_at: "2014-02-23 08:41:41">]>
関連オブジェクトの値を更新する際は、post.authors.build のようにする必要があります。
複数個の場合は複数回buildしてあげればよさそうです。
上記を元に、View、Controllerを作っていきます。
# app/views/posts/_form.html.erb <%= form_for(@post) do |f| %> <% @post.authors.each do |author| %> <%= f.fields_for :authors, author do |author_field| %> <%= author_field.text_field :name %> <% end %> <% end %> <% end %>
“authors_attributes[]”の部分は、普通は:authorsと書くようですが・・・
複数個更新するときには上記のように書くと、同名のパラメータを配列に格納してくれます。
また、authorsのnameカラムを更新したいので、text_fieldには :name と記述します。
追記):authors でも動作しました。
ポストされるパラメータ
params[:post][:authors_attributes]の中身は下記になります。
"authors_attributes"=>[{"name"=>"著者1"}, {"name"=>"著者2"}, {"name"=>"著者3"}]}
(実験)f.fields_forの引数を :authors にした場合のパラメータ
"authors_attributes"=>{"0"=>{"name"=>"あ"}, "1"=>{"name"=>"い"}, "2"=>{"name"=>"う"}}}
keyに個数を表す数値が入ってきてしまいました。
これが気持ち悪くて配列で取得するようにしています。
追記)ハッシュ形式の方が正しいようです。
# app/controllers/posts_controller.rb def new @post = Post.new # 今回は分かりやすく、authorは固定で3枠作成 # 1postで最大3author追加出来る 3.times { # 関連オブジェクトをbuild @post.authors.build } end def create @post = Post.new(post_params); @post.save end # strong parameters private def post_params params.require(:post).permit(:title, authors_attributes: [:name]) end
本当はcreateに何も追記したくなかったのですが・・・(これ以外の方法がわかりませんでした。)
また、strong parametersのホワイトリストに :authors_attributes => [] を追記しています。
追記)上記のようにstrong parametersに記述をすることで、余計な処理を書かずに更新出来ました。
ただし、データを取り出して何らかの加工をする場合には、Arrayで取得しても良さそうです。