11
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-07-20

前提

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

  • Rails 5.2.4.2
  • rspec-rails 4.0.1

テーブルについて

テーブルの構造はこんな感じです。

親テーブル(shops)

shops
id
name

子テーブル(comments)

comments
id
shop_id
content

モデル

親モデル、子モデルのmodelファイルはそれぞれこのようになっています。

親モデル(Shop)

models/shop.rb
class Shop < ApplicationRecord
  has_many :comments
  accepts_nested_attributes_for :comments
end

親モデルにてaccepts_nested_attributes_for :commentsを指定し、子モデルのデータを受け入れるようにしておくことがポイントです。

子モデル(Comment)

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

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

コントローラー

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

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]のように記載します。

ビュー

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

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)

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のコールバックです。この辺りはこちらの記事が参考になりました。

子(comment)

spec/factories/comments.rb
FactoryBot.define do
  factory :comment do
    content { "コメント" }
  end
end

子モデルのfactoryに特に変わった点はありません。

リクエストspec

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の使い方まとめ
【Rails】複数のレコードを作成する。modelの関係性によって異なるform_for / fields_forの使い方
↓特にこの記事にはお世話になりました!!
[Rails]accepts_nested_attributes_forの使い方

追記(発展学習?)

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

▼よくわからないがフォームがすっきり書けるらしい(そんな理解ですみません)
【Rails】FormObjectを使ってほしい

▼次のフォームには使ってみたいと思っているgem, 1体多の構造を持つテーブルで、多数の子テーブルデータが一気に作成できるやつ
fields_forを使った子モデルへの複数レコード保存【cocoonが便利】

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

11
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?