ネストされたフォームにより関連付けられたモデルにデータを同時保存する方法（Rails 6.0.0）

##結論　親モデルにaccepts_nested_attributes_forを追加する
###実証環境
・Cloud9 Ubuntu Server
・Rails 6.0.0
・Ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-linux]
###前提条件
次のような一対多のリレーションを持つParentモデルとKidモデルがあるとします。
※1：混乱を避けるため、「Parentって父親？母親？」とかは考えないものとします。
※2：モデル名に不規則変化の名詞を用いるとややこしいので、childではなくkidを用いています。
<img width="503" alt="ペアレントモデルとキッドモデル" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/598695/f18fc24e-1211-889b-7115-de8f6abaa079.png">



```rb:models/parent.rb
class Parent < ApplicationRecord
  has_many :kids, dependent: :destroy
end
```
```rb:models/kid.rb
class Kid < ApplicationRecord
  belongs_to :parent
end
```
###ネストしたフォームの実装
親モデルのデータ保存時に子モデルのデータも同時に保存するため、```accepts_nested_attributes_for```
をParentモデルに追加します。

```rb:models/parent.rb
class Parent < ApplicationRecord
  has_many :kids, dependent: :destroy
  accepts_nested_attributes_for :kids
end
```
これによって関連付けたデータを1つのフォームで登録できるネストしたフォームを使用できるようになります。

次に、親モデルのParentsコントローラーにて、フォームのページから送られてくるparamsを受け取る空のインスタンスを作ります。

その際、関連付けられた子モデルの空のインスタンスも作成します。
また、子モデルのpramsを受け取るために、ストロングパラメーターに
```kids_attributes: [:name, :age, :toy]```
を渡しています。

```rb:controllers/parents_controller.rb
class ParentsController < ApplicationController

#（中略）
  
  def new
    @parent = Parent.new
    @parent.kids.build #子モデルの空のインスタンスを作成
  end
  
  def create
    @parent = Parent.new(parent_params)
    if @parent.save
      redirect_to root_url
    else
      render :new
    end
  end

#(中略)
  
  private
  
    def parent_params
      #子モデルのパラメーターを受け取れるようにする
      params.require(:parent).permit(
        :name, :age, kids_attributes: [:name, :age, :toy] 
      )
    end

end
```

```erb:new.html.erb
<div class="container">
  <div class="col-sm-10 col-sm-offset-1">
    <h1 class="text-center">親登録</h1>
    <%= form_with(model: @parent, local: true) do |f| %>
      <div class="field form-group">
        <%= f.label :name %>
        <%= f.text_field :name, class: "form-control" %>
      </div>
      <div class="field form-group">
        <%= f.label :age %>
        <%= f.number_field :age, class: "form-control" %>
      </div>

      <!-- 子モデルのデータを受け取るためのネストされたフォーム -->

      <%= f.fields_for :kids do |kf| %>
      <h1 class="text-center">子登録</h1>
        <div class="field form-group">
          <%= kf.label :name %>
          <%= kf.text_field :name, class: "form-control" %>
        </div>
        <div class="field form-group">
          <%= kf.label :age %>
          <%= kf.number_field :age, class: "form-control" %>
        </div>
        <div class="field form-group">
          <%= kf.label :toy %>
          <%= kf.text_field :toy, class: "form-control" %>
        </div>
      <% end %>

      <div class="field form-group">
        <%= f.submit "上記内容で登録する", class: "btn btn-primary btn-lg btn-block" %>
      </div>
    <% end %>
  </div>
</div>
```
Bootstrapのクラスが入っており見にくいですが、上記の```f.fields_for```が子モデルのデータを受け取るフォームになります。
<img width="654" alt="ネストされたフォーム" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/598695/8a7e1eb6-b14b-9fbf-912b-4c77635ffef0.png">
###データ更新（update）時に新たな関連付けられたデータを保存できるようにする
子モデル用の入力フォームですが、空のインスタンスを複数個作成することで、view自体のフォームは増やさずに入力欄を増やすことができます。
例えば、Parentsコントローラーを次のように変更することで、新規登録時には1組の子モデル入力フォーム、更新時には２組の子モデル入力フォームを表示させることができます。

```rb:controllers/parents_controller.rb
class ParentsController < ApplicationController

#(中略)

  def new
    @parent = Parent.new
    @parent.kids.build #子モデルの空のインスタンスを作成
  end
  
  def create
    @parent = Parent.new(parent_params)
    if @parent.save
      redirect_to root_url
    else
      render :new
    end
  end

#(中略)

  def edit
    @parent = Parent.find(params[:id])
    @parent.kids.build #子モデルの空のインスタンスを作成
  end
  
  def update
    @parent = Parent.find(params[:id])
    if @parent.update(parent_params)
      redirect_to root_url
    else
      render :edit
    end
  end
  
  def destroy
    @parent = Parent.find(params[:id])
    @parent.destroy
    redirect_to root_url
  end
  
  private
  
    def parent_params
      params.require(:parent).permit(
        :name, :age, kids_attributes: [:name, :age, :toy]
      )
    end
    
end
```
editアクションでも子モデルの空のインスタンスを作成することで、更新（update）時に「登録されている子モデルのデータ」+1個の入力フォームが作成されます。

これにより、JSなどで動的に追加するよりも簡便に入力フォームを増やすことができます。（個数が限定されてしまうのがネックですが...）

追加されるフォームをさらに増やしたい場合は、生成する空のインスタンスの数を
```@parent.kids.build```
から
```n.times { @parent.kids.build }```
とすることで、n個の入力フォームを生成できます。
###入力されなかった場合の空データを保存されないようにする
しかし、このままでは追加されたフォームに入力されなかった場合に空のデータが保存されてしまいます。
それを防ぐためにはParentモデルに追加した```accepts_nested_attributes_for```の第2引数に次のようなProcを渡します。

```rb:models/parent.rb
class Parent < ApplicationRecord
  has_many :kids, dependent: :destroy
  accepts_nested_attributes_for :kids, reject_if: lambda {|attributes| attributes['name'].blank?}
end
```
上記の場合、子モデル入力フォームのnameが空の場合は他の属性（ここでは、age、toy）が入力されていても全て保存されません。

nameまたはageが入力されていない場合に登録させたくない場合は次のように変更します。

```rb:models/parent.rb
class Parent < ApplicationRecord
  has_many :kids, dependent: :destroy
  accepts_nested_attributes_for :kids, reject_if: lambda {|attributes| attributes['name'].blank? || attributes['age'].blank?}
end
```
上記に加え、子モデルへのバリデーションも変更すれば、保存される値を自由に制御できます。

この他、関連付けた子モデルのデータのみを削除したい場合などもあると思いますが、詳しくはRailsガイドの「[Action View フォームヘルパー](https://railsguides.jp/form_helpers.html)」がとても参考になります。
###あとがき
現在Ruby on Rails学習中の初学者・タツロンです。
自作アプリ作成中に数日間に渡って詰まった機能に関して投稿させていただきました。
間違っている点やアドバイス等ございましたらコメント頂けば幸いです。