LoginSignup
184
194

More than 5 years have passed since last update.

Rails 複数の子レコードの作成・更新を自在に扱う (accepts_nested_attributes_for)

Posted at

accepts_nested_attributes_for

ひとつのフォームの中で、associationで関連づいた子レコードまで保存させたいときにaccepts_nested_attributes_forは非常に便利です。

以前、以下のようにまとめました。
Rails ネストした関連先のテーブルもまとめて保存する (accepts_nested_attributes_for、fields_for)

ただし、子レコードの更新(削除)や、子レコードの数の変更まで行いたかったのでやり方をまとめておきます。(メモ程度なので詳しくは説明書きませんでしたが、参考になれば。)

以下の解説でnew(create)とedit(update)のみにフォーカスして説明します。
簡単なCRUDを作ったrepositoryを用意してありますので、もし詳細が気になった方は参照されて下さい。
https://github.com/kuboshizuma/accepts_attributes_team/
*Viewは適当で、大して綺麗にしてないですのでご了承を。

実装

Model

以下のModelのように一つの「team」に複数の「member」が属する形です。子レコードは「member」のレコードにあたります。teams#newでteamを新規登録するときにmemberまで一緒に登録させます。

member.rb
class Member < ActiveRecord::Base
  belongs_to :team
end
team.rb
class Team < ActiveRecord::Base
  has_many :members
  accepts_nested_attributes_for :members, allow_destroy: true
end

accepts_nested_attributes_for :members, allow_destroy: trueの部分はaccepts_nested_attributes_forで登録する子Modelを指定します。allow_destroyというオプションがポイントだったりするのですが、このオプションのおかげでeditするときに子レコードを削除出来るようになります。

Controller

次に、コントローラーですが以下見て頂ければ何しているか察してもらえると思います。注意する必要があるのはストロングパラメーターの設定の箇所で

  • attributesを許可する
  • updateでは_destoryとidを含める

ということぐらい注意すればオーケーです。

teams_controller.rb
class TeamsController < ApplicationController
  def new
    @team = Team.new
    @team.members.build
  end

  def create
    Team.create(team_params)
    redirect_to root_path
  end

  def edit
    @team = Team.find(params[:id])
  end

  def update
    @team = Team.find(params[:id])
    if @team.update(update_team_params)
      redirect_to root_path
    else
      render :edit
    end
  end

  private
  def team_params
    params.require(:team).permit(:name, :description, members_attributes: [:name, :grade])
  end

  def update_team_params
    params.require(:team).permit(:name, :description, members_attributes: [:name, :grade, :_destroy, :id])
  end

end

View

そしてViewですが、newもeditも一緒です。下のテンプレートを読み込んで下さい。
子レコードを作成するときはfields_forを使います。また、注意することは= m.check_box:_destroyです。これにチェックを入れると子レコードが削除されます。
また、要素を追加するために%p#add_item_button 追加するのボタンを用意していますが、この次にjQueryで実装します。

_form.html.haml
.container.row.form
  %p チームの作成
  .col-lg-8
    = form_for @team do |f|
      = f.text_field :name, placeholder: 'チーム名を入力して下さい', cols: '30', rows: '10', autofocus: 'true', class: 'form-control', required: true
      = f.text_area :description, placeholder: 'チーム紹介をして下さい', autofocus: 'true', class: 'form-control', required: true
      #team_members_box
        %p メンバー
        = f.fields_for :members do |m|
          .js-team_member{ id: "add_member_#{m.index}" }
            = m.text_field :name, placeholder: 'メンバーの名前を入力して下さい', class: 'form-control', required: true
            = m.text_field :grade, placeholder: 'メンバーの学年を入力して下さい', class: 'form-control', required: true
            = m.check_box:_destroy
            %span.member_delete{ data: { id: m.index, default: 'default' } }
              Delete
      %p#add_item_button 追加する
      = f.submit '作成する', class: 'btn btn-primary'
  = link_to '一覧に戻る', root_path

jQuery

ここで、子レコードのための要素の追加・削除が出来るようにしています。「Delete」ボタンを押すと子レコード削除のチェックボックスにチェックが入るようにもしています。チェックボックスはcssでdisplay: noneで消しておいて下さい。

teams.js
$(function() {
  var member_num = $('.js-team_member').length;
  $('#add_item_button').on('click', function() {
    var input =
        '<div class="js-team_member" id="add_member_' + member_num + '">'
        + '<input class="form-control" placeholder="メンバーの名前を入力して下さい" type="text" name="team[members_attributes][' + member_num + '][name]" id="team_members_attributes_' + member_num + '_name" required>'
        + '<input class="form-control" placeholder="メンバーの年齢を入力して下さい" type="text" name="team[members_attributes][' + member_num + '][grade]" id="team_members_attributes_' + member_num + '_grade" required>'
        + '<span class="member_delete" data-id="' + member_num + '">'
        +   'Delete'
        + '</span>'
        +'</div>'
    $('#team_members_box').append(input);
    member_num ++;
  });

  $('#team_members_box').on('click', '.member_delete', function() {
    var inputId = $(this).data('id');
    var defaultData = $(this).data('default');
    if (defaultData == 'default') {
      $(this).prev().prop('checked', true);
      $('#add_member_' + inputId).hide();
    }else{
      $('#add_member_' + inputId).remove();
    }
  });
});
184
194
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
184
194