【Rails】has_many throughな関係で、複数レコードを new/create する時の書き方


new/create時に関連テーブルも更新する記述について、忘れていたのでまとめてみました。（意外と参考書とかにも載っておらず・・・）

今回利用しているバージョンは下記。
Ruby -> 2.1.0
rails -> 4.0.2

###### 20140307 記述を一部修正しました

##元記事
下記エントリの転載になります。
[【Rails】has_many throughな関係で、複数レコードを new/create する時の書き方 - rokuro Fire](http://www.rokurofire.info/2014/02/26/rails_tablerelationship/)

##やりたいこと
* postを1件更新するときに関連テーブルを複数件数一気に更新する

##Model

+ 各モデルの関係を定義。ここはいつものhas_many through。
+ posts <-> author_posts <-> authors
+ authorsは "name" カラムを持つ
+ formでネストさせるので、accepts_nested_attributes_for も設定します。

```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
```

##まずはirbで動作を確認

```cmd
# 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（フォーム）
```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 %>
```

<del>
"authors_attributes[]"の部分は、普通は:authorsと書くようですが・・・
複数個更新するときには上記のように書くと、同名のパラメータを配列に格納してくれます。
また、authorsの"name"を更新したいので、text_fieldには":name"と記述します。
</del>

追記）: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"=>"う"}}}
```

<del>
keyに個数を表す数値が入ってきてしまいました。
これが気持ち悪くて配列で取得するようにしています。
</del>

追記）こちらで正しいようです。

##Controller
```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
```

<del>
本当はcreateに何も追記したくなかったのですが・・・（これ以外の方法がわかりませんでした。）
また、strong parametersのホワイトリストに ":authors_attributes => []" を追記しています。
</del>

追記）上記のようにstrong parametersに記述をすることで、余計な処理を書かずに更新出来ました。
ただし、データを取り出して何らかの加工をする場合には、Arrayで取得しても良さそうです。

##参考
* [YOMiTOKU: Railsでの似たような言葉での違いメモ](http://banker0507.blogspot.jp/2013/02/rails.html)