【Rails】フォームの追加・削除（cocoon）

RailsでWebアプリケーションを作成するとき、入力フォームの追加を行いたかったので、以下に手順をまとめてみました。
「cocoon」というgemを使用すると、簡単に作成可能ですので、今回はそちらの方法で進めていきます。

#ゴールのイメージ
ますは、今回のゴールのイメージを見てみましょう。

[![Image from Gyazo](https://i.gyazo.com/f507ff9f5a0fc7c188c2014efdca39ed.gif)](https://gyazo.com/f507ff9f5a0fc7c188c2014efdca39ed)

#アジェンダ
1. テーブル設計
2. cocoon導入
3. モデル作成
4. コントローラー作成
5. ビュー作成

#テーブル設計
「Recipe」を親テーブルとし、その子テーブルとして「RecipeIngredient」と「HowToMake」を用意する。
ここでの親テーブルと子テーブルは1対多の関係。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/998342/c9dbe763-ba31-4434-0de7-688217274643.png)

#cocoon導入
cocoonを導入するためには、jQueryが導入されている必要がある。

```ruby:Gemfile
gem 'cocoon'
gem "jquery-rails"
```
```terminal
$ bundle install
```
```ruby:application.js
//= require jquery
//= require rails-ujs
//= require turbolinks
//= require_tree .
//= require cocoon
```
`//= require jquery`と`//= require cocoon`を追記。
上記の順番も大事らしい。

#モデル作成
```terminal
$ rails g model Recipe user:references title:string catchcopy:text no_of_dish:string image:string
```
```terminal
$ rails g model RecipeIngredient recipe:references ing_name:string quantity:string
```
```terminal
$ rails g model HowToMake recipe:references explanation:text process_image:string order_no:integer
```

マイグレーションの実行

```terminal
$ rails db:migrate
```

```ruby:recipe.rb
class Recipe < ApplicationRecord
  belongs_to :user
  has_many :recipe_ingredients, dependent: :destroy
  has_many :how_to_makes, dependent: :destroy
  accepts_nested_attributes_for :recipe_ingredients, :how_to_makes, allow_destroy: true
end
```
```ruby:recipe_ingredient.rb
class RecipeIngredient < ApplicationRecord
  belongs_to :recipe
end
```
```ruby:how_to_make.rb
class HowToMake < ApplicationRecord
  belongs_to :recipe
end
```
「recipe」と「recipe_ingredients」「how_to_makes」は一対多の関係なので、`has_many`を使用

####accepts_nested_attributes_for
`accepts_nested_attributes_for`を使用することで、指定したモデルのデータを配列としてパラメーターに含めることができる。（以下でも説明します。）
つまり、「recipe」と「recipe_ingredients」「how_to_makes」モデルのデータをまとめて保存できるようになる。

参考：https://qiita.com/seimiyajun/items/dff057b3eb40434d5c27

####dependent: :destroy
`dependent: :destroy`を指定したクラスが削除された場合、`dependent: :destroy`を設定したモデルのインスタンスも削除される。

今回の場合、
`Recipe`が削除された場合、`RecipeIngredients`と`HowToMake`のインスタンスも削除される。

参考：https://qiita.com/eitches/items/1ad419dc705f807735e0

#コントローラー作成
```terminal
$ rails g controller recipes
```
コントローラー内編集

```ruby:recipes_controller.rb
class RecipesController < ApplicationController

  def new
    @recipe = Recipe.new
    @recipe_ingredients = @recipe.recipe_ingredients.build ##親モデル.子モデル.buildで子モデルのインスタンス作成
    @how_to_makes = @recipe.how_to_makes.build
  end

  def create
    @recipe = Recipe.new(recipe_params)
    if @recipe.save
      redirect_to root_path
    else
      render :new
    end
  end
  
  private
  
  def recipe_params
    params.require(:recipe).permit(:title, :catchcopy, :no_of_dish, :image, 
                                  recipe_ingredients_attributes:[:ing_name, :quantity, :_destroy], 
                                  how_to_makes_attributes:[:explanation, :process_image, :order_no, :_destroy])
  end
end
```
`accepts_nested_attributes_for`で指定したrecipe_ingredientsモデルを
`recipe_ingredients_attributes: []`として一緒に追加して送ることができる。
`how_to_makes_attributes: []`も同様。

`_destroy`を入力することで、削除用のパラメータを受け入れられるようにする。

#ビュー作成
長くなるため今回のフォームに関係ない部分は省いております。

```ruby:recipes/new.html.erb
<div class="recipe-post">
  <%= form_with(model: @recipe, local: true) do |f| %>
     
    <div class="recipe-ingredients">
      <div class="mx-auto">
        <%= f.fields_for :recipe_ingredients do |t| %>
          <%= render "recipes/recipe_ingredient_fields", f: t %>
        <% end %>
      </div>
      
      <div id="detail-association-insertion-point"></div>
      
      <div class="col-10 mx-auto mt-2">
        <%= link_to_add_association "+フォームを追加", f, :recipe_ingredients,
        class: "btn btn-secondary btn-block",
        data: {
        association_insertion_node: '#detail-association-insertion-point',
        association_insertion_method: 'after'
        }%>
      </div>
    </div>

  <% end %>
</div>
```

それでは、以下で説明していきます。

```ruby
<%= f.fields_for :recipe_ingredients do |t| %>
  <%= render "recipes/recipe_ingredient_fields", f: t %>
<% end %>
```
`fields_for`によって`form_with`内で異なるモデル（今回はRecipeIngredient）を編集することができるようになる。
`render`で`recipe_ingredient_fields(ファイル名)`に飛び、ここで動的に追加するフォームの中身を記載する。（以下で説明します。）

```ruby
<%= link_to_add_association "+フォームを追加", f, :recipe_ingredients,
  class: "btn btn-secondary btn-block",
  data: {
  association_insertion_node: '#detail-association-insertion-point',
  association_insertion_method: 'after'
  }%>
```
`link_to_add_association`によってフォームが追加される。

`association_insertion_node: '#detail-association-insertion-point'`
`association_insertion_method: 'after'`
によってフォームの表示位置を指定。
`<div id="detail-association-insertion-point"></div>`に代入されます。


```ruby:_recipe_ingredient_fields.html.erb
<div class="nested-fields">
  <div class="row mx-auto">
    <div class="col-5"><%= f.text_field :ing_name, class: "form-control", placeholder: "材料" %></div>
    <div class="offset-1 col-2"><%= f.text_field :quantity, class: "form-control", placeholder: "分量" %></div>
    <div class="offset-1 col-1 px-0 w-auto">
      <%= link_to_remove_association "削除", f, class: "btn btn-secondary btn-block" %>
    </div>
  </div>
</div>
```
ファイル名は`_モデル名_fields.html.erb`で固定。
このパーシャルに`nested_fields`というクラスを設定してフォーム内容を囲む必要あり。
`link_to_remove_association`によってフォームの削除ができる。

参考：
https://qiita.com/matata0623/items/8868a7fcb6ec0817d064
https://qiita.com/obmshk/items/0e942177d8a44091bf09



補足や修正点などありましたら、是非コメントお願いします！！