LoginSignup
3
3

More than 3 years have passed since last update.

【FactoryBot】投稿を持ったユーザーを生成するファクトリ

Last updated at Posted at 2019-10-06

はじめに

EverydayRailsの4章の「ファクトリで関連を扱う」という項では、belongs_to 関連付けを扱ったファクトリが紹介されています。

例えば、あなたのRailsアプリにUserモデルPostモデルが用意されており、「1つの投稿は、1つのユーザーを持つ」というPost側から見た一対一(belongs_to)の関係が定義されているとします。

spec/factories/posts.rb
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
3
3
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
3
3