Gem
Rails5
cocoon

Cocoonで親子(孫)関係で、動的にフォームを追加削除する

前提

親(Project)-子(task)-孫(item) 関係
子を消すと孫も消える

環境

rails 5.1.5

 Cocoonのインストール

gem入れてjavascript.rbにcocoonの呼び出し文追加
(ここあとで加筆)

 設定

model

project.rb
class Project < ApplicationRecord
  has_many :tasks, inverse_of: :project
  accepts_nested_attributes_for :tasks, reject_if: :all_blank, allow_destroy: true
end
task.rb
class Task < ApplicationRecord
  belongs_to :project
  has_many :items, inverse_of: :task
  accepts_nested_attributes_for :items, reject_if: :all_blank, allow_destroy: true
end
item.rb
class Item < ApplicationRecord
  belongs_to :task
end
  • allow_destroy: true としていないと子項目の削除ができない

controller

projects_controller.rb
  def new
    @project = Project.new
    @task = @project.tasks.build
    @item = @task.items.build
  end

  :

private
    def project_params
    params.require(:project).permit(:name, :description, tasks_attributes: [:id, :description, :done, :_destroy,
                                                         items_attributes: [:id, :description, :_destroy]])
    end
  • 合わせてストロングパラメーターの _destroy も入れておく必要がある(入れないと削除できない)
  • 子要素は親要素のストロングパラメーターに含めて記述できる
  • インスタンスは最初に表示した時のフォームに対応するので、子用のフォームが最初になくても良ければ@project = Project.newだけでよさそう

View

new.html.erb
<h1>New Project</h1>
<%= render 'form', project: @project %>
<%= link_to 'Back', projects_path %>
_form.html.erb
<%= form_for @project do |f| %>
  <div class="field">
    <%= f.label :name, "name" %>
    <%= f.text_field :name %>
  </div>
  <div class="field">
    <%= f.label :description, "description" %>
    <%= f.text_field :description %>
  </div>
  <h3>Tasks</h3>
  <div id="tasks">
    <%= f.fields_for :tasks do |task| %>
      <%= render 'task_fields', f: task %>
    <% end %>
    <div id="links">

      <%= link_to_add_association 'add task',f, :tasks %>
    </div>
    <%= f.submit %>
  </div>
<% end %>
_task_fields.html.erb
<div class="nested-fields well well-compact">
  <div class="form-group">
    <%= f.label :description %>
    <%= f.number_field :description %><br>
    <%= link_to_remove_association "remove task", f %>
  </div>

  <div class="items">
    <%= f.fields_for :items do |item| %>
      <%= render 'item_fields', f: item %>
    <%end%>
    <div class="links">
      <%= link_to_add_association "add item", f, :items %>
    </div>
  </div>
</div>

_item_fields.html.erb
<div class="nested-fields form-inline">
  <div class="form-group">
    <%= f.label :itemdescription %>
    <%= f.number_field :description %>
  </div>
</div>
  • _{model}_fields.html.erbというパーシャル名が固定(拡張子はいくつかフォローされてるので要公式確認)
  • 上記パーシャルへnested-fieldsというclassを設定する
  • =link_to_add_attributesの前にlinksというclassを設定する

ハマリポイント:

rails5からはjQueryのサポートがないので、gemのインストールが必要
gem "jquery-rails"

以下の追記も必要

application.js
//= require jquery

それでも動かない場合、Appication.jsへの”述の順番見るといいかも
//= require jquery ←
//= require rails-ujs
//= require turbolinks
//= require_tree .
//= require cocoon ←

cocoonはjqueryを前提にしているので、先に記述がないとだめみたい
エラーになった順番大事…

感想

  • 公式が上げてるデモサンプルコード見て実装するのいいかも…!(その通りにやったらできた)https://github.com/nathanvda/cocoon_simple_form_demo

  • フォームパーシャルで切ってると、editとnewを全然いじらなくていいのでめっちゃ楽

  • サンプルコードがhtm.erbじゃなくてhamlという書式?を使っていたので読解に手間取った

 参考

cocoonをRails5.1で使用しようと思った時にハマる罠
http://toyokappa.hatenablog.com/entry/2017/09/15/001537

【jQuery】プログラム動かない時に確認すること
http://mikaduki.info/webprogram/js/jquery/1192/

cocoon を使って、ネストしたフォームを作る
http://mikazuki.hatenablog.jp/entry/2016/03/26/040229

マークアッパー的 Haml入門21の手引き
http://fukuyama.co/haml2

Hamlでクラスを複数指定する。
https://qiita.com/s_osa/items/82afd6e543fe4298a2d8