FactoryGirlでインスタンスの作成時にアソシエーションを設定する方法は色々ありそうですが、一番簡単だと思った方法を書いておきます。
前提条件
バージョン
- Rails 5.0.2
- Rspec 3.5.4
- factory_girl_rails > 4.4.1
アソシエーション
今回設定するアソシエーションは以下の通り。
class User < ApplicationRecord
has_many :groups, through: :group_users
has_many :group_users
has_many :messages
end
class Group < ApplicationRecord
has_many :group_users
has_many :users, through: :group_users
has_many :messages
end
class GroupUser < ApplicationRecord
belongs_to :group
belongs_to :user
end
class Message < ApplicationRecord
belongs_to :group
belongs_to :user
end
userとgroupがgroup_userを介して多対多の関係、
user,groupはそれぞれmessageと1対多の関係です。
factory_girlの記法の省略
spec/rails_helper.rbに下記を追加し、インスタンス作成時にFactoryGirlという記述を省略しています。
config.include FactoryGirl::Syntax::Methods
「1対多(has_many)」の設定
「多対多」の設定をする前に「1対多」の場合のhas_manyの設定をみておきます。
例えば、user has_many messagesの場合、userインスタンス作成時にafter(:create)を用いて関連したmessagesを作成することができます。
FactoryGirl.define do
factory :user do
name "komekami"
email "komekami@mail"
password "12345678"
password_confirmation "12345678"
after(:create) do |user|
create_list(:message, 3, user: user)
end
end
end
これでuser_idに作成されたuserインスタンスのidを持つmessageインスタンスが3つ作成できます。create_listは第一引数に作成したいインスタンス名、第二引数に作成したい個数、それ以降に「カラム名: 値」の形で値の上書きができるので、作成したuserのidをmessageのuser_idカラムに上書きしますよー、ってことですね。
「多対多」の設定
続いて「多対多」の設定です。
例えば、user has_many groupsの場合、userインスタンス作成時に関連したgroupsインスタンスを作成します。
FactoryGirl.define do
factory :user do
name "komekami"
email "komekami@mail"
password "12345678"
password_confirmation "12345678"
after(:create) do |user|
create_list(:group, 3, users: [user])
end
end
end
「1対多」のhas_manyの場合とほとんど同じです。create_listのuserの受け渡しが複数形と配列の形になっているのがポイント。user_ids[]に入るからですね。中間テーブル(group_users)を用いずに設定できました、すっきり。
「複数の1対多」の場合
これでuserとgroupの多対多の関係が設定できました。しかし、今回はさらに、userとgroupがそれぞれ多数のmessagesを持っている、という関係があります。これをafter(:create)で全て紐付けようとすると、少し複雑そうな気がします。
そこで、Rspec側で値をセットするやり方を考えます。
let(:user) { create(:user) }
let(:message) { create(:message, user: user, group: user.groups.first) }
例えば、messages_controllerのテストの際、let(:user)を使ってuserを定義し、前述したafter(:create)で同時にgroupを作成します。その後、let(:message)でmessageを定義する際に、createの引数にuserとgroupを記述することにより、let(:user)が読み込まれて上書きされるようになります。
FactoryGirl.define do
factory :message do
body "hello world!"
image "sample.jpg"
user
group
end
end
messageのFactoryGirlにはuserとgroupを置いておけばよいですね。
以上で、アソシエーションの設定ができました。
ご指摘等ありましたら、コメントお願いします!
参考にしたサイト
factory_girl/GETTING_STARTED.md
FactoryGirlで関連データを同時に生成する方法いろいろ
factory_girl で最低限知っておきたい4つの使い方