コード日進月歩

しんくうの技術的な小話、メモ、つれづれ、など

factory bot の trait内に引数のように値を渡したい場合はtransientを使う

N:Nの関連性のレコードをサッとつくるためのtraitを作りたい場合のtips

環境

Gemfile.lockは以下のような環境

factory_bot (6.4.4)
  activesupport (>= 5.0.0)
factory_bot_rails (6.4.2)
  factory_bot (~> 6.4)
  railties (>= 5.0.0)

使い方

factory_botでは一時的な属性情報として transient という機構を用意している。公式ドキュメントとしては以下のように紹介されている。

factory :user do
  transient do
    rockstar { true }
  end

  name { "John Doe#{" - Rockstar" if rockstar}" }
end

create(:user).name
#=> "John Doe - ROCKSTAR"

create(:user, rockstar: false).name
#=> "John Doe"

Transient Attributesより引用

これをtraitと組み合わせることにより、trait側に値を渡す事ができる。

利用例

たとえば以下のようなN:Nを表現するテーブルがあるとする。

booksテーブルとshopsテーブルをつなぐ「本屋さんがおすすめする本」という関連性

この場合にbooksのレコードを作るときにshopsのレコードを指定したい場合、以下のようなtraitを作成する。

# 前提としてshopとbook_recommend_shopのFactoryBotのファイルはあること
FactoryBot.define do
  factory :book do
    title { "BookTitle#{rand(100)}" }
    author
  end
  trait :recommend_shop do
    # 意図したものをセットできるようにする
    transient do
      related_recommend_shop_record { nil }
    end
    after(:create) do |self_object, evaluator|
      shop = evaluator.related_recommend_shop_record.nil? ? create(:shop) : evaluator.related_recommend_shop_record
      create(:book_recommend_shop, book: self_object, shop: shop)
    end
  end
end

このように作成すれば、以下のようにshopを簡潔にしていしてつくることができる。

choice_shop = create(:shop)
create(:book,:recommend_shop,related_recommend_shop_record: choice_shop)

こうすることで間の交差テーブルを意識することなくレコードをつくる仕組みを用意することができる。

参考サイト