Ruby on Railsで動的に追加・削除可能な入力フォームの実装手順

はじめに

Ruby on Railsで作成したWebアプリで動的に入力フォームを追加したり削除したりできるようにしたのでその手順をメモします。なお、ここではテーブルの各セルが入力フォームとなっているような場合に、新しい入力フォームとなる行を動的に追加したり、削除したりできるようにしています。また、親モデルと子モデルにはhas_may関係があります。以下が動作イメージです。
add_remove_form_dynamically
「Add new」ボタンをクリックするとテーブルに新しい行が入力フォームとして追加されます。また、各行のActionカラムにある「Delete」をクリックすると、その行を削除します。

今回は以下URLの「nested_form_fields」というgemを使用しており、実装手順も以下URLに書いてある手順を参考にしています。

ncri/nested_form_fields | GitHub


環境と前提

  • Rails 4.2.0
  • Ruby 2.2.2

以降で説明に使用するWebアプリの概要としては、ホテル情報を登録、更新したり、登録したホテル情報を検索できるようなWebアプリです。そしてHotelモデルが親モデルとしてあり、RoomtypeモデルがHotelモデルの子モデル(has_many関連)となっています。各モデルは以下のようになっています。

app/models/hotel.rb
# == Schema Information
#
# Table name: hotels
#
#  id            :integer          not null, primary key
#  name          :string(50)       not null              # ホテル名
#  address       :string(50)       not null              # 住所
#  foundation    :string           not null              # 設立年月
#  tel           :string(13)       not null              # 電話番号
#  created_at    :datetime
#  updated_at    :datetime

class Hotel < ActiveRecord::Base
    has_many :roomtypes,      dependent: :destroy
end
app/models/roomtype.rb
# == Schema Information
#
# Table name: roomtypes
#
#  id                    :integer          not null, primary key
#  hotel_id              :integer          not null              # ホテルid
#  name                  :string(50)       not null              # お部屋タイプ名
#  capacity              :integer          not null              # 定員人数
#  note                  :string(100)                            # お部屋タイプに関するメモ
#  created_at            :datetime
#  updated_at            :datetime

class Roomtype < ActiveRecord::Base
    belongs_to :hotel
end


nested_form_fieldsのインストール

まずはじめに「nested_form_fields」というGemを導入します。以下をGemfileに追記して

Gemfile
gem 'nested_form_fields'

インストールします。

$ bundl

そして以下をapp/assets/javascripts/application.jsに追記します。

app/assets/javascripts/application.js
//= require nested_form_field

これで「nested_form_fields」のインストールは完了です。

Viewの編集

ここではHotelモデルの編集画面にて、子モデルであるRoomtypeモデルの入力フォームを動的に追加、削除できるようにします。そのためにedit.html.erbを以下のように編集します。

app/views/hotels/edit.html.erb
<%= form_for(@hotel, html:{class: 'form-horizontal'}) do |f| %>
...
  <table class="table table-striped table-bordered table-hover">
          <tbody>
            <tr>
               <th>お部屋タイプ名</th>
               <th>定員人数</th>
               <th>メモ</th>
               <th>Action</th>
            </tr>
            <%= f.nested_fields_for :roomtypes, wrapper_tag: :tr do |q| %>
                <td><%= q.text_field   :name,     class: 'form-control' %></td>
                <td><%= q.number_field :capacity, class: 'form-control' %></td>
                <td><%= q.text_field   :note,     class: 'form-control' %></td>
                <td><%= q.remove_nested_fields_link 'Delete', class: 'btn btn-danger', role: 'button' %></td>
            <% end %>
        </tbody>
      </table>
      <%= f.add_nested_fields_link :roomtypes, 'Add new', class: 'btn btn-primary', role: 'button' %>
...
<% end %>

上記のnested_fields_forからRoomtypeモデルのフォーム生成部分になります。そしてremove_nested_fields_linkは入力フォームを削除するためのボタン、add_nested_fields_linkは入力フォームを新規追加するためのボタンを生成します。注意点は以下の3点です。

  • 動的に追加したいフォームの要素をwrapper_tag:で指定すること。例えば上記の例ではwrapper_tag:としてtrを指定しています。これによってnested_fields_forの中にあるtd要素を含むtr要素が生成されます。
  • remove_nested_fields_linknested_fields_forの中に配置すること
  • add_nested_fields_linknested_fields_forの外に配置すること

f.nested_fields_forに対応する部分として以下のようなHTMLが生成されます。

app/views/hotels/edit.html.erb
  <tr class="nested_fields nested_hotel_roomtypes">
    <td><input class="form-control" type="text" name="hotel[roomtypes_attributes][3][name]" id="hotel_roomtypes_attributes_3_name"></td>
    <td><input class="form-control" type="number" name="hotel[roomtypes_attributes][3][capacity]" id="hotel_roomtypes_attributes_3_capacity"></td>
    <td><input class="form-control" type="text" name="hotel[roomtypes_attributes][3][note]" id="hotel_roomtypes_attributes_3_note"></td>
    <td><a class="btn btn-danger remove_nested_fields_link" data-delete-association-field-name="hotel[roomtypes_attributes][3][_destroy]" data-object-class="roomtype" role="button" href="">Delete</a></td>
  </tr>


Controllerの編集

「nested_fields_for」が動作するには、以下のようにid_destoryをそれぞれStrong Parametersで許可する必要があります。Controllerの編集はこれだけです。

app/controllers/hotels_controller.rb
def hotel_params
        params.require(:hotel).permit(
          :name, :address, :foundation, :tel,
          roomtypes_attributes: [:id, :hotel_id, :name, :capacity, :note, :_destroy]
        )
end


Modelの編集

モデルではaccepts_nested_attributes_forの部分を追記します。

app/models/hotel.rb
class Hotel < ActiveRecord::Base
  has_many :roomtypes,      dependent: :destroy
  accepts_nested_attributes_for :roomtypes, allow_destroy: true # この行を追記
end

以上で完了です。もし間違いやより良い方法などがありましたら教えて頂ければと思います。

SPONSORED LINK

この投稿へのコメント

  1. said on 2015年8月9日 at 17:41

    記事を拝見いたしました。
    自分が今つまずいているところだったので大変勉強になりました!

    一つ質問なのですが、hotelで取得したroomtypeの値をhotelのshowみたいなページに表示するにはどうすればいいのですか?

  2. w256 said on 2017年2月27日 at 03:26

    ありがとうございます。参考にさせていただきました。
    『Controllerの編集』の節に以下の誤植を見つけましたので報告いたします。

    – 誤: users_controller.rb
    – 正: hotels_controller.rb

コメントを残す

メールアドレスが公開されることはありません。

この投稿へのトラックバック

  1. […] 「Ruby on Railsで動的に追加・削除可能な入力フォームの実装手順 https://www.virment.com/add_and_remove_rails_nested_form_dynamically/ […]

  2. […] 「Ruby on Railsで動的に追加・削除可能な入力フォームの実装手順https://www.virment.com/add_and_remove_rails_nested_form_dynamically/ […]

  3. […] 具体的な使い方は下記を参考にさせて頂きました。 https://www.virment.com/add_and_remove_rails_nested_form_dynamically/ […]

トラックバック URL