new/create時に関連テーブルも更新する記述について、忘れていたのでまとめてみました。(意外と参考書とかにも載っておらず・・・)
今回利用しているバージョンは下記。
Ruby -> 2.1.0
rails -> 4.0.2
20140307 記述を一部修正しました
##元記事
下記エントリの転載になります。
【Rails】has_many throughな関係で、複数レコードを new/create する時の書き方 - rokuro Fire
##やりたいこと
- postを1件更新するときに関連テーブルを複数件数一気に更新する
##Model
- 各モデルの関係を定義。ここはいつものhas_many through。
- posts <-> author_posts <-> authors
- authorsは "name" カラムを持つ
- formでネストさせるので、accepts_nested_attributes_for も設定します。
class Post < ActiveRecord::Base
has_many :author_posts
has_many :authors, :through => :author_posts
accepts_nested_attributes_for :authors
end
class Author < ActiveRecord::Base
has_many :author_posts
has_many :posts, :through => :author_posts
end
class AuthorPost < ActiveRecord::Base
belongs_to :post
belongs_to :author
end
##まずはirbで動作を確認
# 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を作っていきます。
##View(フォーム)
<%= 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 でも動作しました。
###authors_attributesのパラメータ
params[:post][:authors_attributes]の中身は下記になります。
"authors_attributes"=>[{"name"=>"著者1"}, {"name"=>"著者2"}, {"name"=>"著者3"}]}
(実験)f.fields_forの引数を :authors にした場合
"authors_attributes"=>{"0"=>{"name"=>"あ"}, "1"=>{"name"=>"い"}, "2"=>{"name"=>"う"}}}
追記)こちらで正しいようです。
##Controller
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
追記)上記のようにstrong parametersに記述をすることで、余計な処理を書かずに更新出来ました。
ただし、データを取り出して何らかの加工をする場合には、Arrayで取得しても良さそうです。
##参考