【Rails】cocoonを用いて親子孫関係のテーブルに複数のデータを同時保存する方法


#目的
gemのcocoonを使って親子孫関係のテーブルに複数のデータを同時に保存できるようにします
１つの送信ボタンで複数のモデルに保存できるため、非常に便利です
１つの投稿に対してカテゴリーやタグ付けをすることを考えるてください
そうすると、一回の投稿で複数のデータを投稿できた方がいいことがわかりますね

#前提
今回の投稿ので使用するモデル、データベースについて説明します
##モデルの関係
| 関係 | モデル名 |
|:-:|:-:|
| 親 | Parent |
| 子 | Child |
| 孫 | Grandchild |
上の表がモデル同士の関係になります
上の関係で実装を進めます
##モデルとカラム
| モデル名 | カラム名 |
|:-:|:-:|
| Parent | p_name |
| Child | c_name |
| Grandchild | g_name |
各テーブルにあるそれぞれのカラムの名前になります



#実装方法
まず、gem cocoomインストール、モデル、コントローラーを作ります
その後、モデル、コントローラー、ビューの順番で説明します
##準備
###Gemfile

gemのcocoonを利用して実装するため、まずはインストールしていきます

``` :Gemfile
gem cocoon
```



```:ターミナル
$ bundle install
```

###モデル、コントローラー作成

```:ターミナル
$ rails g model Parent p_name:string
$ rails g model Child c_name:string parent_id:integer
$ rails g model Grandchild g_name:string child_id:integer
$ rails g controller parents new create
```

```:ターミナル
$ rails db:migrate
```

##モデル
モデルの記述を説明していきます。
###モデル作成


```Ruby:parent.rb
  has_many :children, dependent: :destroy
  accepts_nested_attributes_for :children, allow: true
```
アソシエーションは上で説明したように、
ParentはChildを複数持ってるため、`has_many`で繋げます
`accepts_nested_attributes_for`はビューからデータを送る際に、
`parent`と一緒に`child`を送るための記述です
詳しくはあとで説明します、、、

```Ruby:child.rb
  belongs_to :parent
  has_many :grandchildren, dependent: :destroy
  accepts_nested_attributes_for :grandchildren, allow: true
```


```Ruby:grandchild.rb
  belongs_to :child
```

`child`,`grandchild`も`parent`と同じように記述します

##コントローラー

コントローラーに以下の記述をします

```Ruby:parents_controller.rb
  def new
    @parent = Parent.new
    @children = @parent.children.build
    @grandchildren = @children.grandchildren.build
  end

  def create
    @parent = Parent.new(parent_params)
    @parent.save
  end

private
def parent_params
      params.require(:parent).permit(:p_name, children_attributes: [:id, :c_name, :_destroy, grandchildren_attributes: [:id, :g_name, :_destroy]])
end
```
###new
`@parent`,`@children`,`@grandchildren`をそれぞれ定義します
###create
`@parent = Parent.new(parent_params)`でデータを作って、
`@parent.save`で`child`,`grandchild`にも同時に保存しています
###ストロングパラメーター
####accepts_nested_attributes_for
モデルで記述した`accepts_nested_attributes_for`ことで
`params`に`_attributes: []`を一緒に追加して送ることができています。

ストロングパラメーターは
`_attributes: []`などの配列を一番後ろに書かないといけないので注意してください

##ビュー

###new.html.erb

```HTML:new.html.erb
<%= form_with(model: @parent, local: true) do |s| %>
	<%= s.label :p_name %>
	<%= s.text_field :p_name %>
	<div class="parents">
		<%= s.fields_for :children do |t| %>
			<%= render "children_fields", f: t %>
		<% end %>
		<div class="links">
			<%= link_to_add_association "子供を追加", s, :children %>
		</div>
	</div>
	<div class="actions">
		<%= s.submit %>
	</div>
<% end %>
```

###_children_fields.html.erb

```HTML:_children_fields.html.erb
<div class="nested-fields">
    <%= f.label "c_name" %>
    <%= f.text_field :c_name %>
    <%= link_to_remove_association "remove child", f %>

    <%= f.fields_for :grandchildren do |u| %>
        <%= render "grandchild_fields", f: u %>
    <%end%>

    <%= link_to_add_association "孫を追加", f, :grandchildren %>
</div>
```

###_grandchild_fields.html.erb

```HTML:_grandchild_fields.html.erb
<div class="nested-fields">
    <%= f.text_field :g_name %>
    <%= link_to_remove_association "remove grandchild", f %>
</div>
```

部分テンプレートの名前に注意してください
名前が合っていないとrenderできなかったり、入力フォームを追加できません

#おわりに
cocoonで入力フォームを増やせるととても便利ですね
様々な場面で応用できるので習得しましょう

###質問、気になるところなどございましたらコメントよろしくお願いします