fields_forを使って親子関係にあるモデルのレコードを一度に編集・削除する

# はじめに
この記事ではポートフォリオ作成中に学習したことを自分のために記録しています。
初学者のため理解が不十分なところがあるかもしれません。
その場合はご指摘いただけると幸いです。

# やりたいこと
- 親子関係のあるモデルのレコードを一度に編集・削除したい

YouTubeのブックマークアプリを作成しているので、ブックマーク（親）とそれに紐づくタイムスタンプ（子）を1つのフォームで編集・削除できるようにしていきます。
ブックマークとタイムスタンプは一対多の関係です。

完成イメージはこんな感じ
![BF1615E1-419E-4E9B-9051-03853492B71B.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3671013/0d7f635e-1dac-b613-4c33-475f6f319f3d.png)

# いざ実装
## 子モデルの編集
### accepts_nested_attributes_for⁠メソッドを親モデルに追加
```app/models/bookmark.rb
  has_many :timestamps
  accepts_nested_attributes_for :timestamps #追加
```

### 編集フォーム作成
 ```app/views/bookmarks/edit.html.erb
 <%= 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| %
```

### コントローラにストロングパラメータを設定
```app/controllers/bookmarks_controller.rb
  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を渡す
```app/models/bookmark.rb
accepts_nested_attributes_for :timestamps, allow_destroy: true
```

### フォームに_destroyの入力欄を追加

子モデルの_destroyキーの値がtrueまたは1であればレコードが削除されます。
[Railsガイド](https://railsguides.jp/form_helpers.html#%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%82%92%E5%89%8A%E9%99%A4%E3%81%99%E3%82%8B)の例にならいチェックボックスを追加しました。
```app/views/bookmarks/edit.html.erb
<%= timestamp.check_box :_destroy %>
```

### ストロングパラメータに_destroyを追加
```app/controllers/bookmarks_controller.rb
 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の値を見ながら進めたことで少しは理解が深まったのではないかな…という気がします。

この記事がどなたかの参考になれば幸いです。
お読みいただきありがとうございました。


# 参考
- [Railsガイド Action View フォームヘルパー](https://railsguides.jp/form_helpers.html)
- [Rails fields_forについて](https://zenn.dev/goldsaya/articles/06d7e685a66431)
- [accepts_nested_attributes_forを理解する](https://qiita.com/kazyam/items/4fd10396532c254d21b1)
- [fields_for内で並び替えをする](https://ken73g.blogspot.com/2010/09/fieldsfor.html)