fields_forで子テーブルのデータを一気に作成する（テストも書いてます）[Rails][Rspec]

## 前提
あるショップのデータを作成するときに、そのショップへのコメントも一気に作成したいと思います。前提となる環境は以下の通りです。

- Rails 5.2.4.2
- rspec-rails 4.0.1

## テーブルについて
テーブルの構造はこんな感じです。

### 親テーブル（shops）
|shops|
|:--:|
|id|
|name|

### 子テーブル（comments）
|comments|
|:--:|
|id|
|shop_id|
|content|

## モデル
親モデル、子モデルのmodelファイルはそれぞれこのようになっています。
### 親モデル（Shop）
```ruby:models/shop.rb
class Shop < ApplicationRecord
  has_many :comments
  accepts_nested_attributes_for :comments
end
```
親モデルにて`accepts_nested_attributes_for :comments`を指定し、子モデルのデータを受け入れるようにしておくことがポイントです。

### 子モデル（Comment）
子モデルの記載には特に変わったことはありません。

```ruby:models/comment.rb
class Comment < ApplicationRecord
  belongs_to :shop
end
```

## コントローラー
親モデル側のコントローラーに、工夫すべき点がいくつかあります。

```ruby:controllers/shops_controller.rb
class ShopsController < ApplicationController
  def new
    @shop = Shop.new
    @shop.comments.build # ←一つ目のポイント
  end

  def create
    @shop = Shop.create(shop_params)
    if @shop.save
      # 成功したときの処理
    else
      # 成功しなかったときの処理
    end
  end

  private

  def shop_params # ↓2つ目のポイント
    params.require(:shop).permit(:name, comments_attributes: [:content])
  end

end
```

**一つ目のポイント**は、`new`アクションのところで、`@shop.comments.build`と記載し、子テーブルである`comments`のインスタンスを`build`までしておくことです。こうすることで、親テーブルに紐づく`comments`を作成する準備ができます。

**二つ目のポイント**は、`shop_params`のところで、`comments_attributes: [:content]`と記載し、子テーブルの要素もpermitしておくことです。要素が二つ以上あるときには、`comments_attributes: [:content, :attr2, :attr3]`のように記載します。

## ビュー
ビューには、このように記載します。

```ruby:views/shops/new.html.haml
= form_with model: @shop, local: true do |f|
  = f.label :name, "店名"
  = f.text_field :name

  = f.fields_for :comments, local: true do |comments_form|
   = comments_form.label :content, "ショップへのコメント"
　 = comments_form.text_field :content

= f.submit "送信"
```
これで、子テーブルのデータも一気に作成されるはずです。
余談ですが、以前`f.fields_for`のところを`fields_for`と書いてしまい、子テーブルの要素が`shop_params`として送られずはまったことがありました。。。

`f.fields_for`と記載することで、親テーブルのフォームオブジェクトに紐づけることができます。

## テスト
テストもそれなりにコツが必要だったので記載します。ここでは、リクエストspecのみを書きます。

### factory
#### 親(shop)
```ruby:spec/factories/shops.rb
FactoryBot.define do
  factory :shop do
    name { "テストのお店" }

    trait :with_nested_instances do
      after( :create ) do |shop|
        create :comment, id: shop.id
      end
    end  
  end
end
```

`trait` といのは、FactoryBotで、状況により少しだけ違うデータを用いたい場合の設定方法です。

`after(:create)`はFactoryBotのコールバックです。この辺りは[こちらの記事](https://qiita.com/muran001/items/436fd07eba1db18ed622)が参考になりました。

#### 子(comment)
```ruby:spec/factories/comments.rb
FactoryBot.define do
  factory :comment do
    content { "コメント" }
  end
end
```

子モデルのfactoryに特に変わった点はありません。
### リクエストspec 

```ruby:spec/requests/shops_request_spec.rb
RSpec.describe "Shops", type: :request do

  describe "POST /shops" do
    context "依存関係のあるテーブルのparamsが送信されているとき" do
　　　 before do
        @comment_params = {
          comments_attributes: {
            "0": FactoryBot.attributes_for(:comment)
          }
        }
        @params_nested = { 
          shop: FactoryBot.attributes_for(:shop).merge( @comment_params )
        }
      end

      it 'リクエストが成功すること' do
        post shops_url, params: @params_nested
        expect(response.status).to eq 302
      end

      it "ショップ情報とコメントが新規作成されること" do
        expect do
          post shops_url, params: @params_nested
        end.to change(Shop, :count).by(1) and change(Comment, :count).by(1)
      end
    end
  end
end
```

`"0"`あたりがちょっと特殊な書き方に見えますが、これは、実際のデータ送信時に送られる`params`に合わせた形になります。長かったですが、テストまで頑張ったおかげで、依存関係のあるデータの作成と大分仲良くなれました。

## 参考記事
ここまでに参考にした記事はこちらです。
[RSpecにおけるFactoryGirlの使い方まとめ](https://qiita.com/muran001/items/436fd07eba1db18ed622)
[【Rails】複数のレコードを作成する。modelの関係性によって異なるform_for / fields_forの使い方](https://qiita.com/ftyabu/items/cae912b211e985d74d58)
↓特にこの記事にはお世話になりました！！
[[Rails]accepts_nested_attributes_forの使い方](https://qiita.com/seimiyajun/items/dff057b3eb40434d5c27)

## 追記（発展学習？）
結局は自分の凡ミスだったのですが、同様にネストしたテーブルについて悩んていたときにスクールの同窓生コミュニティに投稿したら、以下のことを教えていただきました。

▼よくわからないがフォームがすっきり書けるらしい（そんな理解ですみません）
[【Rails】FormObjectを使ってほしい](https://qiita.com/9ro/items/10e3676741e09ffb98bb)

▼次のフォームには使ってみたいと思っているgem, 1体多の構造を持つテーブルで、多数の子テーブルデータが一気に作成できるやつ
[ fields_forを使った子モデルへの複数レコード保存【cocoonが便利】](https://www.y-hakopro.com/entry/2019/08/03/234126)

では、今日もこれから仕事、頑張ります。アドバイスくださった皆様、ありがとうございました！！:relaxed: