LoginSignup
7
10

More than 3 years have passed since last update.

【Rails】フォームの追加・削除(cocoon)

Posted at

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

ゴールのイメージ

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

Image from Gyazo

アジェンダ

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

テーブル設計

「Recipe」を親テーブルとし、その子テーブルとして「RecipeIngredient」と「HowToMake」を用意する。
ここでの親テーブルと子テーブルは1対多の関係。
image.png

cocoon導入

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

Gemfile
gem 'cocoon'
gem "jquery-rails"
$ bundle install
application.js
//= 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
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
recipe_ingredient.rb
class RecipeIngredient < ApplicationRecord
  belongs_to :recipe
end
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が削除された場合、RecipeIngredientsHowToMakeのインスタンスも削除される。

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

コントローラー作成

$ rails g controller recipes

コントローラー内編集

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を入力することで、削除用のパラメータを受け入れられるようにする。

ビュー作成

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

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>

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

<%= f.fields_for :recipe_ingredients do |t| %>
  <%= render "recipes/recipe_ingredient_fields", f: t %>
<% end %>

fields_forによってform_with内で異なるモデル(今回はRecipeIngredient)を編集することができるようになる。
renderrecipe_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>に代入されます。

_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

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

7
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
10