How to avoid circular creation of associated models in factory_girl?
I don't think there's a nice way for a factory to tell that it's been called by another without collaboration. (You can always inspect caller_locations
, but that's not nice.) Instead, have one factory tell the other to behave differently using a transient attribute:
FactoryGirl.define do
factory :user do
transient do
create_identity true
end
after(:create) do |user, evaluator|
if evaluator.create_identity
create(:identity, user: user)
end
end
end
factory :identity do
association :user, factory: :user, create_identity: false
end
end
Circular depenedancy in FactoryBotRails Factories
Here's the circular dependency:
FactoryBot.define do
factory :company do
...
users { [ FactoryBot.create(:user) ] } # HERE
end
end
FactoryBot.define do
factory :user do
...
association :company, factory: :company # AND HERE
end
end
When trying to create a company with FactoryGirl, it invokes FactoryBot.create
to create a new user, which at the same time is verifying the company factory.If you want to add associations, I'd personally advice you to do it in your examples, not in the factories. Since the factories are meant to be used along your whole projects, and there are tests that aren't going to share the same logic as you´re testing today, as a matter of fact, that might change sooner or later.
Factory girl, dependent factories
Your basic problem is that you have a circular dependency between your user
factory and your school
factory, caused by the fact that you create a primarycontact
(a user) when you create a school, then that user creates a school, and so on.
You can get around this by changing how you define your school
association inside the user
factory. Before doing that though, I'd suggest as a general rule using the shorthand notation for associations. So replace this:
factory :account do
association :user, factory: :user
association :role, factory: :role
end
with this:factory :account do
user
role
end
Using this simplification, the following factories will do what you want without generating any circular dependency:FactoryGirl.define do
factory :district do
name "Seattle"
end
factory :school do |school|
district
primarycontact
name "Test School"
after_build do |s|
s.primarycontact.school = s
end
end
factory :user do
email "adam@example.com"
name "Who What"
username "wwhat"
password "123456"
password_confirmation { |u| u.password }
school
end
factory :primarycontact, class: "User" do
# add any attributes you want the primarycontact user to have here
end
factory :role do
name "student"
end
factory :account do
user
role
end
end
Notice that what I have done is to create a factory for primarycontact
with the class: "User"
option. Unlike the user
factory, this factory does not create the school
by default, avoiding the circular dependency.Then in the school
factory, I use an after_build
callback to assign the school itself to the school
association on primarycontact
, rather than creating a new school (which was causing the problem in your factories).
Hope that makes sense. Note that the callback syntax has changed in the more recent version of factory_girl, see the documentation for details.
Using factory_girl in Rails with associations that have unique constraints. Getting duplicate errors
I encountered the same problem and added a lambda at the top of my factories file that implements a singleton pattern, which also regenerates the model if the db has been cleared since the last round of tests/specs:
saved_single_instances = {}
#Find or create the model instance
single_instances = lambda do |factory_key|
begin
saved_single_instances[factory_key].reload
rescue NoMethodError, ActiveRecord::RecordNotFound
#was never created (is nil) or was cleared from db
saved_single_instances[factory_key] = Factory.create(factory_key) #recreate
end
return saved_single_instances[factory_key]
end
Then, using your example factories, you can use a factory_girl lazy attribute to run the lambdaFactory.define :product do |p|
p.product_type { single_instances[:add_product_type] }
#...this block edited as per comment below
end
Voila! Avoid duplicating records in loop
I found exactly what I was looking for here: Find or create record through factory_girl association
Overriding the default build / new behaviour using the initialize_with
method seems to be the way to go:
factory :company do
initialize_with { Company.find_or_create_by_name("Foo Company") }
end
factory_girl association validations
So after hours of beating my head against this issue, I think I finally have a solution. It's pretty insane though, so hopefully someone else can still show me where I'm being stupid.
Factory.define :foo do |foo|
end
Factory.define :bar do |bar|
end
Factory.define :baz do |baz|
end
Factory.define :foo_with_baz do |foo|
foo.after_create { |foo| Factory(:baz, :foo => foo) }
end
Factory.define :bar_baz do |bar_baz|
bar_baz.bar {|bar| bar.association(:bar, :foo => Factory(:foo_with_baz))
bar_baz.after_build {| bar_baz| bar_baz.baz_id = bar_baz.foo.bars.first.id }
end
The key issue being that there needs to be a foo in the database already you can get at through the factories alone, since you can use local variables or arbitrary ruby code in factories.rb (as far as I can tell).
Related Topics
Why Should @@Class_Variables Be Avoided in Ruby
How to Properly Manage The Rails Tmp Directory
How Does The "#Map(&Proc)" Idiom Work When Introspecting Module Classes
Does Rails Support a Neat Way of Listening to a Udp Socket
Using Update_Columns in Rails 3
Why Can't Net::Ftp Connect to Server
Ruby on Rails: Pluralize for Other Languages
How to Tell a Ruby Method to Expect a Specific Parameter Type
Indent Multiline String in Erb
How to Wait for System Command to End
Need Help on Reading Emails with "Mail" Gem in Ruby
How to Get Meta Keywords Using Nokogiri
Cucumber Re-Run Failed Scenarios Automatically with a Tag
What Ruby Technique Does Rails Use to Make My Controller Methods Render Views
Ruby 1.9 How to Convert Array to String Without Brackets
Is 'Respond_To_Missing''s Second Argument Useful for Anything