【Rails6】Cocoonの導入（環境構築から）

# 概要
オリジナルアプリのレシピ共有サイトを作る中で
- レシピの材料を複数カラムにまとめて保存したい
- レシピの材料を追加する動的フォームを実装したい　　

という目的にあったcocoonというgemの導入の流れを説明します。
[公式サイト](https://github.com/nathanvda/cocoon)
[参考にしたサイト](https://qiita.com/matata0623/items/8868a7fcb6ec0817d064)


# 全体の流れ
1. JQueryの導入
1. cocoon（gem）の導入
1. MVCの導入

## 1.JQueryの導入
- ターミナルで`yarn add jquery`を実行
```
ターミナル
$ yarn add jquery
```
__注意__:OSにHomebrewをインストールしていない人は`yarn init`を先に実行する。

- `environment.js`に下記記載
```javascript:config/webpack/environment.js
const { environment } = require('@rails/webpacker')
const webpack = require('webpack')

environment.plugins.prepend('Provide',
  new webpack.ProvidePlugin({
    $: 'jquery',
    jQuery: 'jquery',
    jquery: 'jquery',
  })
)

module.exports = environment
```
- `application.js`に下記を追記
```javascript:javascript/packs/application.js
require("@rails/ujs").start()
require("turbolinks").start()
require("@rails/activestorage").start()
require("channels")
// ここから
require("jquery")
//ここまで
```
- `application.html.erb`を確認
```html:views/layout/application.html.erb
<!--省略-->
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
<!--私の場合はすでに書かれていたのでこのままにしておきました-->
<!--省略-->
```
## 2.Cocoon(gem)の導入
- gemfileに記載
```:gemfile
# 私は一番下に記載しました
# bundle installからの再起動も忘れずに
gem 'cocoon'
```
- `yarn add`する
```:ターミナル
$ yarn add @nathanvda/cocoon
```
- `application.js`に記載
```javascript:javascript/packs/application.js
require("@nathanvda/cocoon")
```
（`import "cocoon"`でも動くようです）

## 3.MVCの導入
テーブルのマイグレーションファイルなどは記載していません。
`title`カラムがあるレシピテーブルと`recipe_id`と`name`カラムがあるイングレディエントテーブルを作成しました。
- ルーティング
```ruby:config/routes.rb
Rails.application.routes.draw do
  root "recipes#index"
  resources :recipes, only: [:index, :new, :create]
end
```
- 親モデル
```ruby:app/models/recipe.rb
class Recipe < ApplicationRecord
  has_many :ingredients, inverse_of: :recipe

  validates :title, presence: true

  accepts_nested_attributes_for :ingredients, allow_destroy: true, reject_if: :all_blank
end

```
`inverse_of`を記載する理由
--公式の本文--
When saving nested items, theoretically the parent is not yet saved on validation, so rails needs help to know the link between relations. There are two ways: either declare the `belongs_to` as `optional: false`, but the cleanest way is to specify the `inverse_of:` on the `has_many`. That is why we write : `has_many :tasks, inverse_of: :project`
--翻訳--
ネストされたアイテムを保存するとき、理論的には親モデル側はバリデーションで保存されない。railsでは二つのリレーションを明らかにする必要がある。これには２つのやり方があり、一つは`belongs_to`側（子モデル側）に`optional: false`を記載するやり方。もう一つは`has_many`側（親モデル側）に`inverse_of`を記載するやり方で、こちらの方がクリーンに記載できる。

`allow_destroy: true`を記載する事でフォームを削除できる

`reject_if: :all_blank`を記載する事で全て空の場合拒否する

- 子モデル
```ruby:app/models/ingredient.rb
class Ingredient < ApplicationRecord
  belongs_to :recipe

  validates :name, presence: true
end

```
- コントローラー
```ruby:app/controllers/recipes/controller.rb
class RecipesController < ApplicationController
  def new
    @recipe = Recipe.new
  end

  def create
    @recipe = Recipe.new(recipe_params)
    if @recipe.valid?
      @recipe.save
      redirect_to root_path
    else
      render :new
    end
  end

  private
  def recipe_params
    params.require(:recipe).permit(:title, ingredients_attributes: [:id, :recipe_id, :name, :_destroy])
  end
end
```
`ingredients_attributes`の中にある`:id` `:_destroy`は必須
- トップページ
まだ一覧表示やエラーハンドリングは実施してない
```erb:app/views/recipes/index.html.erb
<%= link_to "レシピの新規作成", new_recipe_path %>
```
- フォーム入力画面
```html:app/views/recipes/new.html.erb
<%= form_with model: @recipe, url: recipes_path, local: true do |f| %>
  <div class="field">
    <%= f.label :title %>
    <br/>
    <%= f.text_field :title %>
  </div> 
  <h3>材料</h3>
  
  <div id='ingredients'>
    <%= f.fields_for :ingredients do |ingredient| %>
      <%= render 'ingredient_fields', :f => ingredient %>
    <% end %>
    <div class='links'>
      <%= link_to_add_association 'add ingredient', f, :ingredients %>
    </div>
  </div>
  <%= f.submit 'Save' %>

<% end %> 
```
- 部分レイアウト
```erb:app/views/recipes/_ingredient_fields.html.erb
<div class="nested-fields">
    <div class='nested-fields'>
    <div class="field">
      <%= f.label :name %>
      <%= f.text_field :name %>
    </div>
    <%= link_to_remove_association "remove ingredient", f %>
  </div>
</div>
```

## 実装中に困ったこと
- コクーンを導入したのに動的フォームの追加がうまくいかない
｜これはJQueryが正しく導入できてないことが問題であることが多い
- 二つのテーブルに保存するのでフォームオブジェクトを使ってみたがうまくいかなかった
｜cocoonを使った実装では`@recipe.save`の時に二つのテーブルに保存できているので、フォームオブジェクトは使用しなくても良い。


以上になります。誤字脱字、情報が違うなどありましたら教えていただけると幸いです。