RailsでWebアプリケーションを作成するとき、入力フォームの追加を行いたかったので、以下に手順をまとめてみました。
「cocoon」というgemを使用すると、簡単に作成可能ですので、今回はそちらの方法で進めていきます。
#ゴールのイメージ
ますは、今回のゴールのイメージを見てみましょう。
#アジェンダ
- テーブル設計
- cocoon導入
- モデル作成
- コントローラー作成
- ビュー作成
#テーブル設計
「Recipe」を親テーブルとし、その子テーブルとして「RecipeIngredient」と「HowToMake」を用意する。
ここでの親テーブルと子テーブルは1対多の関係。
#cocoon導入
cocoonを導入するためには、jQueryが導入されている必要がある。
gem 'cocoon'
gem "jquery-rails"
$ bundle install
//= require jquery
//= require rails-ujs
//= require turbolinks
//= require_tree .
//= require cocoon
//= require jquery
と//= require cocoon
を追記。
上記の順番も大事らしい。
#モデル作成
$ rails g model Recipe user:references title:string catchcopy:text no_of_dish:string image:string
$ rails g model RecipeIngredient recipe:references ing_name:string quantity:string
$ rails g model HowToMake recipe:references explanation:text process_image:string order_no:integer
マイグレーションの実行
$ rails db:migrate
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
class RecipeIngredient < ApplicationRecord
belongs_to :recipe
end
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
#コントローラー作成
$ rails g controller recipes
コントローラー内編集
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
を入力することで、削除用のパラメータを受け入れられるようにする。
#ビュー作成
長くなるため今回のフォームに関係ない部分は省いております。
<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>
それでは、以下で説明していきます。
<%= f.fields_for :recipe_ingredients do |t| %>
<%= render "recipes/recipe_ingredient_fields", f: t %>
<% end %>
fields_for
によってform_with
内で異なるモデル(今回はRecipeIngredient)を編集することができるようになる。
render
でrecipe_ingredient_fields(ファイル名)
に飛び、ここで動的に追加するフォームの中身を記載する。(以下で説明します。)
<%= 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>
に代入されます。
<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
補足や修正点などありましたら、是非コメントお願いします!!