Help us understand the problem. What is going on with this article?

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

More than 5 years have passed since last update.

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();
    }
  });
});
shizuma
web&DeepLearningエンジニア。 ACES.inc←東京大学大学院/東京←鹿児島/blog https://blog.seishin55.com ; Qiita https://qiita.com/shizuma ; note https://note.mu/seishin55
https://seishin55.com
aces
ACES(エーシーズ)は、 画像認識を中心としたAIアルゴリズムの力で、リアル産業のDX(デジタルトランスフォーメーション)を推進し、シンプルな社会を実現する会社です。ヒトの働き方をデジタルの力で 自動化・効率化することで、誰もが生き生きと 生きられる社会を実現していきます。
https://acesinc.co.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away