はじめに
この記事ではポートフォリオ作成中に学習したことを自分のために記録しています。
初学者のため理解が不十分なところがあるかもしれません。
その場合はご指摘いただけると幸いです。
やりたいこと
- 親子関係のあるモデルのレコードを一度に編集・削除したい
YouTubeのブックマークアプリを作成しているので、ブックマーク(親)とそれに紐づくタイムスタンプ(子)を1つのフォームで編集・削除できるようにしていきます。
ブックマークとタイムスタンプは一対多の関係です。
いざ実装
子モデルの編集
accepts_nested_attributes_forメソッドを親モデルに追加
has_many :timestamps
accepts_nested_attributes_for :timestamps #追加
編集フォーム作成
<%= form_with model: @bookmark do |f| %>
<%= f.label :url, "URL" %>
<%= f.text_field :url %>
<%= f.label :is_public, "おすすめ動画として公開する" %>
<%= f.check_box :is_public %>
<%= f.hidden_field :user_id, value: @user.id %>
<%= f.fields_for :timestamps do |timestamp| %> #ここから子モデルの編集フォーム
<%= timestamp.number_field :hour %>
<%= timestamp.label :hour, "時間" %>
<%= timestamp.number_field :minute %>
<%= timestamp.label :minute, "分" %>
<%= timestamp.number_field :second %>
<%= timestamp.label :second, "秒" %>
<%= timestamp.label :comment, "メモ" %>
<%= timestamp.text_field :comment %>
<%= timestamp.hidden_field :bookmark_id, value: @bookmark.id %>
<% end %>
<%= f.submit "登録" %>
<% end %>
fields_for内でソートしたいとき
上記フォームで入力した時間(hour)・分(minute)・秒(second)を基に作成したタイムスタンプをコントローラ側で秒に換算し、start_timeとして保存しています。
編集フォームでタイムスタンプをstart_time順にソートしたかったため、fields_forの第2引数を追加して下記のような記述にしました。
<%= f.fields_for :timestamps, @bookmark.timestamps.sort_by(&:start_time) do |timestamp| %
コントローラにストロングパラメータを設定
def bookmark_params
params.require(:bookmark).permit(
:user_id,
:url,
:description,
:is_public,
:video_id,
timestamps_attributes: [:id, :bookmark_id, :hour, :minute, :second, :start_time, :comment] #この部分を追記
)
end
ストロングパラメータを追加します。
ここで重要なのが、子モデルのパラメータに:idを入れておくことです。
これを忘れてしまうと編集する度に重複するレコードが作成され、子モデルのレコード数がねずみ算的に増えてしまいます。
パラメータにidが含まれている場合は既存のレコードだと判断されupdateが実行されますが、idが含まれていないと新しいレコードだと判断されcreateが実行される…ということのようです。
以上で子モデルのレコードが編集できるようになりました!
子モデルの削除
編集だけでなく削除もできるようにしていきます。
accepts_nested_attributes_forにallow_destroy: trueを渡す
accepts_nested_attributes_for :timestamps, allow_destroy: true
フォームに_destroyの入力欄を追加
子モデルの_destroyキーの値がtrueまたは1であればレコードが削除されます。
Railsガイドの例にならいチェックボックスを追加しました。
<%= timestamp.check_box :_destroy %>
ストロングパラメータに_destroyを追加
def bookmark_params
params.require(:bookmark).permit(
:user_id,
:url,
:description,
:is_public,
:video_id,
timestamps_attributes: [:id, :bookmark_id, :hour, :minute, :second, :start_time, :comment, :_destroy] #:_destroyを追加
)
end
以上で子モデルのレコード削除もできるようになりました!
最後に
思った通りに動いてくれないことも多く大変でしたが、binding.pryでひたすらparamsの値を見ながら進めたことで少しは理解が深まったのではないかな…という気がします。
この記事がどなたかの参考になれば幸いです。
お読みいただきありがとうございました。