概要
オリジナルアプリのレシピ共有サイトを作る中で
- レシピの材料を複数カラムにまとめて保存したい
- レシピの材料を追加する動的フォームを実装したい
という目的にあったcocoonというgemの導入の流れを説明します。
公式サイト
参考にしたサイト
全体の流れ
- JQueryの導入
- cocoon(gem)の導入
- MVCの導入
1.JQueryの導入
- ターミナルで
yarn add jquery
を実行
ターミナル
$ yarn add jquery
注意:OSにHomebrewをインストールしていない人はyarn init
を先に実行する。
-
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
に下記を追記
require("@rails/ujs").start()
require("turbolinks").start()
require("@rails/activestorage").start()
require("channels")
// ここから
require("jquery")
//ここまで
-
application.html.erb
を確認
<!--省略-->
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
<!--私の場合はすでに書かれていたのでこのままにしておきました-->
<!--省略-->
2.Cocoon(gem)の導入
- gemfileに記載
# 私は一番下に記載しました
# bundle installからの再起動も忘れずに
gem 'cocoon'
-
yarn add
する
$ yarn add @nathanvda/cocoon
-
application.js
に記載
require("@nathanvda/cocoon")
(import "cocoon"
でも動くようです)
3.MVCの導入
テーブルのマイグレーションファイルなどは記載していません。
title
カラムがあるレシピテーブルとrecipe_id
とname
カラムがあるイングレディエントテーブルを作成しました。
- ルーティング
Rails.application.routes.draw do
root "recipes#index"
resources :recipes, only: [:index, :new, :create]
end
- 親モデル
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では二つのリレーションを明らかにする必要がある。これには2つのやり方があり、一つはbelongs_to
側(子モデル側)にoptional: false
を記載するやり方。もう一つはhas_many
側(親モデル側)にinverse_of
を記載するやり方で、こちらの方がクリーンに記載できる。
allow_destroy: true
を記載する事でフォームを削除できる
reject_if: :all_blank
を記載する事で全て空の場合拒否する
- 子モデル
class Ingredient < ApplicationRecord
belongs_to :recipe
validates :name, presence: true
end
- コントローラー
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
は必須
- トップページ
まだ一覧表示やエラーハンドリングは実施してない
<%= link_to "レシピの新規作成", new_recipe_path %>
- フォーム入力画面
<%= 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 %>
- 部分レイアウト
<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
の時に二つのテーブルに保存できているので、フォームオブジェクトは使用しなくても良い。
以上になります。誤字脱字、情報が違うなどありましたら教えていただけると幸いです。