LoginSignup
14
11

More than 3 years have passed since last update.

Rails、cocoonを使って多対多のフォームを作る(selectタグを使う)

Last updated at Posted at 2018-10-09

多対多の保存処理を実装する

Railsは普段使わないこともあり、いつも、多対多のフォームを作るときに、どうやったけ?となるので、メモ。
今回は、cocoonで実装してみた。

2021/01/23 追記
こちらの記事ではcocoonというgemを使って実装していますが、FormObjectを使っての実装の方をおすすめします。
https://thoughtbot.com/blog/activemodel-form-objects

gemでの実装は早くて簡単ではありますが、メンテされなくなる可能性や、他の人から見たコードの可読性などを考えると、今回のようなケースでの実装はgemに任せてしまうと危うい気がしています。

開発環境

Ruby 2.5.1
Rails 5.1

仕様

  • コースとスポットの関係は多対多 walking_course -> walking_course_spots <- walking_spots
  • コースを保存するときに、ひもづくスポットを選択して、中間テーブルを作成する
  • コースの中で、スポットナンバーをつける

まずはrelationを貼る

course.rb
has_many :walking_course_spots
has_many :walking_spots, through: :walking_course_spots
spot.rb
has_many :walking_course_spots
has_many :walking_courses, through: :walking_course_spots
course_spots.rb
belongs_to :walking_course
belongs_to :walking_spot

※注意しないといけないのは、relationの順番。throughの前に、関連付けされるテーブルをhas_manyで宣言しないとエラーになる

複数のテーブルにかかる保存は、nest属性をつける

course.rb
accepts_nested_attributes_for :walking_course_spots, allow_destroy: true

このように記述すれば、courseの保存をしたときに、同時にwalking_courseのテーブルにも保存するようにすることができる

_form_content.html.erb
<div class="form-group">
    <%= f.fields_for :walking_course_spots do |course_spot| %>
        <%= course_spot.collection_checkboxes :walking_course_id, WalkingSpot.all %>
    <%- end %>
</div>

フォームで項目を作るときは、fields_forを使うとよい。ここではチェックボックスの例。
パラメーターで取得するときは、以下のように記述。
保存するテーブル: [: カラム名]という形式

course_controller.rb
def walking_course_params
    params.require(:walking_course).permit(
         walking_course_spots_attributes: [:walking_spot_id]
    )
end

こんな感じで、使えば、チェックボックスで中間テーブルを作成することは、可能。
ただ、今回は、これをselectで作成できるようにしたい。

Gem Cocoonを使う

色々と、調べてみると、cocoonというgemを使えば簡単にselectのフォームを追加したり、削除したりして中間テーブルに保存するように処理を作ることができるらしいので、やってみた

gemfile
gem 'cocoon'
application.js
//= require cocoon

bundle installでGemを追加後、application.jsに記述

cocoonでviewを書き換える

_form_content.html.erb
<%= f.fields_for :walking_course_spots do |course_spot| %>
    <div class="field">
        <%= render 'walking_course_spot_fields', f: course_spot %>
    </div>
<%- end %>
<div class="links">
    <%= link_to_add_association 'スポットを追加', f, :walking_course_spots %>
</div>

_walking_course_spot_fields.html.erb
<div class="nested-fields field">
    <div>スポット</div>
    <div class="col-md-6">
        <label class="control-label">スポット名</label>
            <%= f.collection_select :walking_spot_id, WalkingSpot.all, :id, :name %>
    </div>
    <div class="col-md-6">
        <label class="control-label">スポットNo</label>
        <%= f.text_field :spot_number %>
    </div>
    <%= link_to_remove_association '削除', f, class: 'btn btn-danger' %>
</div>

link_to_add_associationは、フォームの追加ボタンの役割を果たす
link_to_add_remove_associationは削除ボタン

ここで注意しないといけないのは、ファイル名は関連モデル名+_fieldsという名前にしないとエラーになる点である

中間テーブルのレコードを削除できるようにする

保存したいカラム名を追記すること。ここでは、walking_spot_id、spot_number。
また、再保存の際に、画面で削除したはずのものが、テーブルに保存されてしまうという事態が発生した。どうやら、idと_destroyがないとテーブルの削除が機能しないようになっているらしい。最終的には、下記のようになった。

course_controller.rb
def walking_course_params
    params.require(:walking_course).permit(
         walking_course_spots_attributes: [:id, :walking_spot_id, :spot_number, :_destroy]
    )
end

Oct-09-2018 21-05-29.gif

参照

GitHub - nathanvda/cocoon: Dynamic nested forms using jQuery made easy; works with formtastic, simple_form or default forms
Rails4でフォーム要素を動的に追加したり削除したり〜nested_form | | Scimpr Blog
cocoon を使って、ネストしたフォームを作る - みかづきメモ

14
11
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
14
11