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まで一緒に登録させます。
class Member < ActiveRecord::Base
belongs_to :team
end
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を含める
ということぐらい注意すればオーケーです。
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で実装します。
.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
で消しておいて下さい。
$(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();
}
});
});