環境
- Rails 7.0.5
- FactoryBot 6.2.1
はじめに
- ここに書かれている内容は、基礎の基礎ですが、ユースケースの8割はカバーできるのではないかと思います。より詳しく知りたい方は、以下のページを参照して下さい(※ここの内容の大半は、以下のページに書かれている内容です)。
https://github.com/thoughtbot/factory_bot/blob/master/GETTING_STARTED.md
- factory_bot 5 より、静的属性は廃止されました。
属性は、常にブロックを付けて宣言する必要があります。
# OK
role { "admin" }
# NG
role "admin"
Static attributes (without a block) are no longer available in factory_bot 5.
https://github.com/thoughtbot/factory_bot/blob/master/GETTING_STARTED.md#static-attributes
1. インスタンス化するのに必要最低限の属性を持ったファクトリーを、各クラスで一つ定義する
具体的には、オブジェクト作成時にバリデーションを通すために必要で、且つデフォルト値を持っていない属性を定義します。
It is recommended that you have one factory for each class that provides the simplest set of attributes necessary to create an instance of that class. If you're creating ActiveRecord objects, that means that you should only provide attributes that are required through validations and that do not have defaults. Other factories can be created through inheritance to cover common scenarios for each class.
https://github.com/thoughtbot/factory_bot/blob/master/GETTING_STARTED.md#best-practices
2. 基本的なファクトリー定義方法
以下の 3パターンを抑える。
- シーケンスを使う(※主にユニーク制約に抵触しないため)
- アソシエーションを定義する
- 値を動的に生成する
FactoryBot.define do
factory :user do
sequence(:email) { |n| "test#{n}@example.com" } # シーケンスを使う
first_name { "John" }
last_name { "Doe" }
date_of_birth { 18.years.ago } # 値を動的に生成する
organization # Organizationへのアソシエーション
end
end
尚、ハッシュを扱う場合(JSONカラムなど)は、以下のように {{ key: value }}
とする必要があります。
factory :program do
configuration { { auto_resolve: false, auto_define: true } }
end
3. build
とcreate
を使い分ける
-
build
で十分な時は、build
を使う。-
build
インスタンスを生成して返す。インスタンスはまだDBに保存されていない。 -
create
DBに保存されたインスタンスを返す。
-
# インスタンスを生成して返す。インスタンスはまだDBに保存されていない。
user = build(:user)
# DBに保存されたインスタンスを返す。
user = create(:user)
-
build_list
、create_list
で複数のインタンスを生成する。
users = build_list(:user, 2)
users = create_list(:user, 2)
- 属性を指定する事も可能。
# factoryとして定義されていなくても、テーブルに存在するカラムを指定可能(以下の例ではrole)。
user = create(:user, first_name: "Dave", role: "admin")
4. callback
をtransient
やtrait
と組み合わせて使うことで、アソシエーション先のデータを作成する
-
callback
をtransient
と組み合わせて使うことで、transient
が指定された時に関連付けられたデータを生成します(以下、pattern 1)。 -
callback
をtrait
と組み合わせて使うことで、trait
が指定された時に関連付けられたデータを生成します(以下、pattern 2)。
FactoryBot.define do
factory :user do
:
# pattern 1 (transient を使う例)
transient do
posts_count { 0 }
end
after(:create) do |user, evaluator|
create_list(:post, evaluator.posts_count, user: user) if evaluator.posts_count.positive?
end
# pattern 2 (trait を使う例)
trait :user_with_departments do
after(:create) do |user, _|
create(:department, :sales, user: user)
end
end
end
end
# pattern 1 を使う場合
# userに紐付いたpostsが、after callback(※↑の例では`after(:create)`)によって生成される。
create(:user, posts_count: 1)
# pattern 2 を使う場合
create(:user, :user_with_departments)
5. trait
を使って条件の異なるファクトリーを定義する
例 1
factory :story do
title { "My awesome story" }
trait :published do
published { true }
end
trait :week_long_publishing do
start_at { 1.week.ago }
end_at { 1.week.from_now }
end
# traits で複数の trait をまとめることが出来ます
factory :week_long_published_story, traits: [:published, :week_long_publishing]
end
-
build
やcreate
を使ってインスタンス生成する時に、trait
を指定することも可能です。
create(:story, :published, :week_long_publishing)
例 2: アソシエーション先のレコードを作る
# factory の定義
factory :story do
:
trait :with_author do
# デフォルト定義。create (or build) する時に author_email が指定されていなければこの値が使われる。
author_email { 'author@example.com' }
end
# story has_one author のアソシエーション
author { create(:author, email: author_email) }
end
# factory を利用する
let(:story) { create(:story, :with_author, author_email: 'test@example.com') }
おまけ
「たった5つ」と書いておきながら、2つだけおまけを付けさせて下さい(笑)。
不要な変数を定義しない
# OK
# organizationインスタンスを生成した後で、userに代入する。
org = create(:organization)
create(:user, organization: org)
# Better
# organizationインスタンスを別途生成せずに、user生成時に代入する。
# こちらの方がシンプルだが、状況に応じて、リーダビリティのために上記の方法を採っても良い。
create(:user, organization: create(:organization))
has_manyアソシエーションにデータを関連付ける方法(2パターン)
# 1
users = create_list(:users, 2)
create(:organization, users: users)
# 2
user1 = create(:user, role: "admin")
user2 = create(:user, role: "sales")
create(:organization, users: [user1, user2])
おまけのおまけ
フリーランスで活動しています。
開発案件(Go、Node、Rails、Blockchain、機械学習など)をお待ちしております。