はじめに
EverydayRailsの4章の「ファクトリで関連を扱う」という項では、belongs_to 関連付けを扱ったファクトリが紹介されています。
例えば、あなたのRailsアプリにUserモデル
とPostモデル
が用意されており、「1つの投稿は、1つのユーザーを持つ」というPost側から見た一対一(belongs_to)の関係が定義されているとします。
FactoryBot.define do
factory :post do
title { "Hello." }
association :user # <=これを記述しておく
end
end
その場合、上のファクトリを用意することで
FactoryBot.create(:post)
を実行する際に、裏でユーザーを作成し、生成されるPostインスタンスに自動で関連付けてくれます。
post = FactoryBot.create(:post)
puts post.user #=> <User:0x00007f91213a3ce8>
人の手でいちいちユーザーを作ってFactoryBot.create(:post, user: user)
とする必要がなくなるわけですから、単に適当なPostインスタンスが欲しいってときには便利ですよね。
本題
では逆に「1つのユーザーは、複数の投稿を持つ」という、User側から見た一対多(has_many)の関係を取り扱うファクトリはどう書くのでしょうか?
要は、最初から投稿を持つユーザーを生成してくれるファクトリが欲しいんです。
残念ながらEverydayRailsにはその方法が載っていません。そこで、FactoryBotの公式ドキュメントを確認したところ、ありました。
というわけで、以下にドキュメントで紹介されているそのサンプルコードを抜粋します(コメントは私が日本語に訳しました。だいぶ意訳してます。)
FactoryBot.define do
# 投稿ファクトリ(ユーザーへの"belongs_to"関連付けを持つ)
factory :post do
title { "Through the Looking Glass" }
user
end
# 投稿なしのユーザーファクトリ(投稿を持たない)
factory :user do
name { "John Doe" }
# 投稿ありのユーザーファクトリ(ユーザーのあとに投稿データを生成する)
factory :user_with_posts do
# 一時的な属性(transient attribute)として posts_count を定義しています。
# これは、オブジェクトを生成するときやファクトリのコールバックで利用できます。
transient do
posts_count { 5 }
end
# after(:create)コールバックのブロック引数(|user, evaluator|)は、
# 生成されるユーザーと"evaluator"というオブジェクトを表します。
# "evaluator"はファクトリで定義された全ての値を保持するオブジェクトであり、
# その中には一時的な属性も含まれます。"create_list"メソッドの
# 第二引数には、生成したい投稿の数を指定しています。
after(:create) do |user, evaluator|
create_list(:post, evaluator.posts_count, user: user)
end
end
end
end
このコードを参考にファクトリを定義することで、以下のように複数の投稿を持ったユーザーをたった1行で生成することができるようになります。
create(:user).posts.length #=> 0
create(:user_with_posts).posts.length #=> 5
create(:user_with_posts, posts_count: 15).posts.length #=> 15
引数に投稿の数を指定することも可能。これは便利ですね!
補足
サンプルコードを見てわかる通り、一対多(has_many)の関係を扱うファクトリは一対一(belongs_to)のものに比べると、作りが少々複雑になっています。また、transient
なる謎のブロックもありますね。これは一時的な属性(transient attribute)というものを定義しています。わからない方もいるかもしれないので最後に解説しておきます。
一時的な属性(transient attribute)
あるファクトリ内で追加的に定義することができる属性のことです。一時変数とも呼ばれます(確かに変数を定義しているみたいですよね。)尚、この属性が実際のモデルの属性として反映されることはありません。
この属性値は、オブジェクト作成時の挙動を変更するためのフラグや追加データとして利用されます。定義した一時的な属性をファクトリのコールバックで使用する場合には、"evaluator"(これについては上のサンプルコードのコメントで説明されています)から呼び出します。
transient do
posts_count { 5 } # 追加したい属性名 { 値 } の形で記述します
end