HTML
CSS
JavaScript
RubyOnRails

railsのgemであるcocoonを用いて1対nのネストした投稿フォームを作る

はじめに

一つのフォームで親子関係のある複数モデルのレコード作成かつ子モデルのレコードを複数挿入したい場合にgemのcocoonを使うと簡単に実装できます。

今回はタイトルしか挿入しない投稿フォームに子モデルのタグを複数挿入する機能を実装します。
(タイトルカラムのあるPostモデルとタグカラムのあるTagモデルは1対Nの関連付けがあることが前提で進めます。)

実装

シンプルな投稿フォームの作成

scaffoldを使い、親モデルのレコードを作成できるようにします。

$ rails new cocoon_sample

$ cd cocoon_sample
$ rails g scaffold Post title:string

$ rails db:migrate

子モデルを作成します。
今回は子モデル名をTagにします。(カラムはタグを格納するためのnameカラムpost_idが必要)

$ rails g model Tag name:string post_id:integer

$ rails db:migrate

PostモデルとTagモデルに関連付けを行います。

post.rb
  ...(追加)
  has_many :tags, dependent: :destroy
  accepts_nested_attributes_for :tags, allow: true
tag.rb
  belongs_to :post

ネストしたフォームを作成するためにposts_controller.rbのnewアクションと_form.html.erbを編集します。

posts_controller.rb
  def new
    @post = Post.new
    @post.tags.build
  end
  ...(省略)

  def post_params
    params.require(:post).permit(:title, tags_attributes: [:id, :name, :_destroy])
  end
_form.html.erb
  ...(省略)
  <%= form.fields_for :tags do |f| %>
    <%= f.label :name %>
    <%= f.text_field :name %>
  <% end %>

  <div class="actions">
    <%= form.submit %>
  </div>

ここまでの記述でPostモデルとその子モデルのTagモデルのレコードの挿入を一つのフォームでできるはずです。

次に一つの親モデルに対し複数の子モデルのレコードを挿入できるように編集していきます。

_form.html.erb
  <div class="tags">
    <%= form.fields_for :tags do |f| %>
      <%= f.label "タグ" %>
      <%= render "tag_fields", f: f %>
    <% end %>
    <div class="links">
      <%= link_to_add_association "タグを追加", form, :tags %>
    </div>
  </div>

タグのフォームを部分テンプレートにするためファイルを作成します

views/posts/_tag_fields.html.erb
  <div class="nested-fields">
    <%= f.text_field :name %>
    <%= link_to_remove_association "remove task", f %>
  </div>

動的に要素を増やすためjsも変更します。

application.js
  ...(省略)
  //= require rails-ujs
  //= require activestorage
  //= require jquery ...(追加)
  //= require cocoon ...(追加)
  //= require turbolinks
  //= require_tree .

最後に

cocoonを使うと簡単にネストしたフォームを作成できます。
便利ですが正しい記述をしないと投稿や要素の追加や削除ができない場合があります。
要素(DOM)の操作はjqueryが便利です。
cocoonを使わずjqueryで実装するのも良いと思います。