Rails、cocoonを使って多対多のフォームを作る(selectタグを使う)

## 多対多の保存処理を実装する
Railsは普段使わないこともあり、いつも、多対多のフォームを作るときに、どうやったけ？となるので、メモ。
今回は、cocoonで実装してみた。

2021/01/23 追記
こちらの記事ではcocoonというgemを使って実装していますが、FormObjectを使っての実装の方をおすすめします。
https://thoughtbot.com/blog/activemodel-form-objects

gemでの実装は早くて簡単ではありますが、メンテされなくなる可能性や、他の人から見たコードの可読性などを考えると、今回のようなケースでの実装はgemに任せてしまうと危うい気がしています。

### 開発環境
Ruby 2.5.1
Rails 5.1

### 仕様
- コースとスポットの関係は多対多
walking_course -> walking_course_spots <- walking_spots
- コースを保存するときに、ひもづくスポットを選択して、中間テーブルを作成する
- コースの中で、スポットナンバーをつける

### まずはrelationを貼る

```ruby:course.rb
has_many :walking_course_spots
has_many :walking_spots, through: :walking_course_spots
```

```ruby:spot.rb
has_many :walking_course_spots
has_many :walking_courses, through: :walking_course_spots
```

```ruby:course_spots.rb
belongs_to :walking_course
belongs_to :walking_spot
```

※注意しないといけないのは、relationの順番。throughの前に、関連付けされるテーブルをhas_manyで宣言しないとエラーになる

### 複数のテーブルにかかる保存は、nest属性をつける
```ruby:course.rb
accepts_nested_attributes_for :walking_course_spots, allow_destroy: true
```

このように記述すれば、courseの保存をしたときに、同時にwalking_courseのテーブルにも保存するようにすることができる

```erb:_form_content.html.erb
<div class="form-group">
	<%= f.fields_for :walking_course_spots do |course_spot| %>
		<%= course_spot.collection_checkboxes :walking_course_id, WalkingSpot.all %>
	<%- end %>
</div>
```

フォームで項目を作るときは、fields_forを使うとよい。ここではチェックボックスの例。
パラメーターで取得するときは、以下のように記述。
`保存するテーブル: [: カラム名]`という形式

```ruby:course_controller.rb
def walking_course_params
	params.require(:walking_course).permit(
		 walking_course_spots_attributes: [:walking_spot_id]
	)
end
```

こんな感じで、使えば、チェックボックスで中間テーブルを作成することは、可能。
ただ、今回は、これをselectで作成できるようにしたい。

### Gem Cocoonを使う
色々と、調べてみると、cocoonというgemを使えば簡単にselectのフォームを追加したり、削除したりして中間テーブルに保存するように処理を作ることができるらしいので、やってみた

```yml:gemfile
gem 'cocoon'
```

```js:application.js
//= require cocoon
```

`bundle install`でGemを追加後、application.jsに記述

#### cocoonでviewを書き換える
```erb:_form_content.html.erb
<%= f.fields_for :walking_course_spots do |course_spot| %>
	<div class="field">
		<%= render 'walking_course_spot_fields', f: course_spot %>
	</div>
<%- end %>
<div class="links">
	<%= link_to_add_association 'スポットを追加', f, :walking_course_spots %>
</div>

```

```erb:_walking_course_spot_fields.html.erb
<div class="nested-fields field">
	<div>スポット</div>
	<div class="col-md-6">
		<label class="control-label">スポット名</label>
			<%= f.collection_select :walking_spot_id, WalkingSpot.all, :id, :name %>
	</div>
	<div class="col-md-6">
		<label class="control-label">スポットNo</label>
		<%= f.text_field :spot_number %>
	</div>
	<%= link_to_remove_association '削除', f, class: 'btn btn-danger' %>
</div>
```
link_to_add_associationは、フォームの追加ボタンの役割を果たす
link_to_add_remove_associationは削除ボタン

ここで注意しないといけないのは、ファイル名は関連モデル名+_fieldsという名前にしないとエラーになる点である

#### 中間テーブルのレコードを削除できるようにする
保存したいカラム名を追記すること。ここでは、walking_spot_id、spot_number。
また、再保存の際に、画面で削除したはずのものが、テーブルに保存されてしまうという事態が発生した。どうやら、idと_destroyがないとテーブルの削除が機能しないようになっているらしい。最終的には、下記のようになった。

```ruby:course_controller.rb
def walking_course_params
	params.require(:walking_course).permit(
		 walking_course_spots_attributes: [:id, :walking_spot_id, :spot_number, :_destroy]
	)
end
```
![Oct-09-2018 21-05-29.gif](https://qiita-image-store.s3.amazonaws.com/0/148823/547c594b-a020-b1ab-4813-9933531a9bb3.gif)


### 参照
[GitHub - nathanvda/cocoon: Dynamic nested forms using jQuery made easy; works with formtastic, simple_form or default forms](https://github.com/nathanvda/cocoon#link_to_remove_association)
[Rails4でフォーム要素を動的に追加したり削除したり〜nested_form | | Scimpr Blog](https://blog.scimpr.com/2014/01/20/rails4%E3%81%A7%E3%83%95%E3%82%A9%E3%83%BC%E3%83%A0%E8%A6%81%E7%B4%A0%E3%82%92%E5%8B%95%E7%9A%84%E3%81%AB%E8%BF%BD%E5%8A%A0%E3%81%97%E3%81%9F%E3%82%8A%E5%89%8A%E9%99%A4%E3%81%97%E3%81%9F%E3%82%8A/)
[cocoon を使って、ネストしたフォームを作る - みかづきメモ](https://mikazuki.hatenablog.jp/entry/2016/03/26/040229)