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

公開日:2015/07/03 更新日:2015/07/03
Ruby on Railsで動的に追加・削除可能な入力フォームの実装手順のサムネイル

はじめに

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

add_remove_form_dynamically.gif 「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

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

関連記事

開発アプリ

nanolog.app

毎日の小さな出来事をなんでも記録して、ログとして残すためのライフログアプリです。