How to Avoid Circular Creation of Associated Models in Factory_Girl

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 lambda

Factory.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



Leave a reply



Submit