リレーションの関係にある複数のモデルを連動して追加したいときの方法いろいろ.
リレーション関係なくても、インスタンス生成と同時に特定の処理を実行したいときにも使える.
呼び出し側でcreateにブロック渡す
rspecなどのテストコード側でカスタムに関連データを追加する方法.
shop has_many staffs
のリレーションができている前提で、以下のようにbuild
やcreate
にブロックを渡せば、ブロック内で生成されたインスタンスを自由に修正できる.
#
# 生成されたインスタンスの内容をブロック内で自由に修正できる
#
shop = FactoryBot.build(:shop) do |s|
s.name = "あいうえお"
end
#
# 永続化せずにインスタンス生成
# shopにひもづくstaffをブロック内で追加している
#
shop = FactoryBot.build(:shop) do |s|
s.staffs.build(FactoryBot.attributes_for(:staff))
end
#
# 永続化されたインスタンス生成
# shopにひもづくstaffをブロック内で追加している
#
shop = FactoryBot.create(:shop) do |s|
s.staffs.create(FactoryBot.attributes_for(:staff))
end
ファクトリ内でファクトリ呼び出し
ファクトリ内で別のファクトリ呼び出すと連鎖的にインスタンス生成することができる.
モデルが互いに関連していた場合はリレーションもはられた状態になる.
以下の例は、staff belongs_to shop
のリレーションができている前提で動作する.
FactoryBot.define do
factory :staff, class: Staff do
name "Isaac Newton"
role "physicist"
shop # ファクトリよびだし
end
factory :shop, class: Shop do
name "テストショップ"
end
end
staff = FactoryBot.create :staff
# staffおよびshopがinsertされる
staff.shop
# 連鎖生成されたshopをアソシエーションたどって参照できる
おそらく、↑ような別のファクトリを呼び出す記法は以下の省略形?になる.
FactoryBot.define do
factory :staff, class: Staff do
name "Isaac Newton"
role "physicist"
shop { FactoryBot.create :shop }
end
end
↑の書き方だと、ブロックを使えるので自由度の高い生成ができるが、こういったカスタマイズしたい場合は次に紹介のassociationを使うのが推奨らしい.
また、trait使えばアソシエーション先を作成するかどうか切り替えることもできる
FactoryBot.define do
factory :staff, class: Staff do
name "Isaac Newton"
role "physicist"
trait :with_shop do
shop
end
end
factory :shop, class: Shop do
name "テストショップ"
end
end
#
# アソシエーション先を作らない
#
user = FactoryBot.build :staff
user.shop
# nil
#
# アソシエーション先作る
#
user = FactoryBot.build :staff, :with_shop
user.shop
# => #<Shop id: 1, name: "テストショップ">
Association
staff belongs_to shop
の関連がある前提で以下のファクトリが動作する.
association
メソッドにアソシエーション名とファクトリ名を指定する.
任意で生成時の属性を指定することもできる.
factory :staff, class: Staff do
name "Isaac Newton"
association :shop, factory: :shop, name: "マダガスカルショップ"
end
factory :shop, class: Shop do
name "テストショップ"
end
staff = FactoryBot.create :staff
staff.shop.name # マダガスカルショップ
ただし、association
はhas_manyリレーションでは使えない.
has_manyで関連データをつっこむときは次に紹介のcallbackを使う.
これも先ほどと同様trait使えばassociation実行切り替えもできる.
FactoryBot.define do
factory :staff, class: Staff do
name "Isaac Newton"
role "physicist"
trait :with_shop do
association :shop, factory: :shop, name: "マダガスカルショップ"
end
end
factory :shop, class: Shop do
name "テストショップ"
end
end
Callback
callbackを使えば、生成したインスタンスがcreate
, build
されたイベントの直後に自由にインスタンスを修正することができる.
以下は、shop has_many staffs
の関連ができている前提で動作.
factory :shop, class: Shop do
name "テストショップ"
after(:create) do |shop|
shop.staffs << FactoryBot.create(:staff, name: "織田信長")
shop.staffs << FactoryBot.create(:staff, name: "葛飾北斎")
end
end
↑の例ではbuild
時は関連が作成されない.
なのでcallback作る際はafter create
ではなく、after build
にしておいたほうがよさそう.
after build
コールバックはFactoryBot.create
した後も実行される.
factory :shop, class: Shop do
name "テストショップ"
after(:build) do |shop|
shop.staffs << FactoryBot.build(:staff, name: "織田信長")
shop.staffs << FactoryBot.build(:staff, name: "葛飾北斎")
end
end
shop = FactoryBot.build :shop
shop.persisted? # false
shop.users.first.persisted? # false
こちらももちろんtrait使えばコールバック実行切り替えできる.
以下の例ではデフォルトでデフォルトのtraitを実行して、カスタムのtraitが指定された場合はデフォルトのデータを上書きする.
factory :shop, class: Shop do
name "テストショップ"
with_default_staffs
trait :with_default_staffs do
after(:build) do |shop|
shop.staffs << FactoryBot.build(:staff, name: "one")
shop.staffs << FactoryBot.build(:staff, name: "two")
end
end
trait :with_japanese_staffs do
after(:build) do |shop|
shop.staffs = []
shop.staffs << FactoryBot.build(:staff, name: "織田信長")
shop.staffs << FactoryBot.build(:staff, name: "葛飾北斎")
end
end
trait :with_english_staffs do
after(:build) do |shop|
shop.staffs = []
shop.staffs << FactoryBot.build(:staff, name: "Isaac Newton")
shop.staffs << FactoryBot.build(:staff, name: "Robert Hooke")
end
end
end
FactoryBot.create :shop
# アソシエーション先としてdefault staffsが作成される
FactoryBot.create :shop, :with_japanese_staffs
# アソシエーション先としてjapanese staffsが作成される
FactoryBot.create :shop, :with_english_staffs
# アソシエーション先としてenglish staffsが作成される