Factory_bot: Problems with factory_girl and has_many / belongs_to associations

Created on 29 Jul 2013  路  38Comments  路  Source: thoughtbot/factory_bot

Good morning everyone,
Yesterday I started to use factory_girl with Rails. My setup features two classes with has_many / belongs_to relation between them. I literally followed the instructions for the has_many case here. My current code features this:

class User < ActiveRecord::Base
  has_many :assistants, dependent: :destroy
end

class Assistant < ActiveRecord::Base
  belongs_to :user
end

FactoryGirl.define do
  factory :assistant do
    name { Faker::Name.name }
    user
  end

  factory :user do
    name { Faker::Name.name }
    email { Faker::Internet.email }

    factory :user_with_assistants do
      ignore do
        assistants_count 5
      end

      after(:create) do |user, evaluator|
        FactoryGirl.create_list(:assistant, evaluator.assistants_count, user: user)
      end
    end
  end
end

My current problem is that the same kind of expression you use in the example does not work for me in the same way:

# I get this:
FactoryGirl.create(:user_with_assistants).assistants.length # => 0

I tried to dig a bit further and discovered the following:

# In Rails console
user = FactoryGirl.create(:user_with_assistants)
user.assistants.length # => 0
Assistant.where(user_id: user.id).all.length # => 5

As you can see, the list of assistants gets created correctly, but it is not available through the ActiveRecord association, and I need it to be available through the association to be able to test the relation between my two classes.

Obviously, I checked if the association works at all, and it does:

user = User.create(name: "Jony Ive", email: "[email protected]")
user.assistants.create(name: "Design Intern")
user.assistants.length # => 1

I will be very very thankful for any hints on what I am doing wrong.

Most helpful comment

I'm hesitant to add noise here, but I just wasted ~1 hour because of this issue. Perhaps, if nothing is done on the library side, the associations documentation could have a note added that mentions this behavior.

All 38 comments

@appplemac if you put a user.reload in the after(:create) callback:

after(:create) do |user, evaluator|
  FactoryGirl.create_list(:assistant, evaluator.assistants_count, user: user)
  user.reload
end

Does that fix things?

@joshuaclayton Yes it does! Thank you!

P.S. Why does

user.reload

fix all this?

@appplemac what happens is when the assistants get created, they associate themselves to the user but user doesn't know about the changes to its internal state. Calling user.reload refreshes itself, pulling in newly created data that it didn't know about before (in this case, the assistants).

@joshuaclayton Alright, thanks a lot!

Is there a way to get FG do this internally when there's an associations instead of calling reload every time there are assignments. When your test base grows, it's hard and hacky to call @object.reload in different hooks.

Thanks

I second that, how about a reload of every object which is referenced in another let block

I just wanted to follow up here - because it's something I run into a lot. Is this being worked on to reload internally?

I ran into this issue last night. I agree with the comments above-- internally reloading the parent object, whenever the associations gets created, would be nice to eliminate this pain point.

another vote here for auto reload on an associated parent object please. just spent way too long tracking this one down.

+1, this is really annoying

+1

+1

+1

+1

+1

+1

+1

+1

+1

+1

:+1:

+1

+1

+1

+1

Looks like there's a lot of interest here; does someone have a proposal as to how to do this?

A couple things to consider:

  1. Not everyone is using ActiveRecord, so there's not guarantee {Thing}#reload is the correct method to call
  2. Associated records could be created with FG directly (e.g. with create_list like in the example above) or with a service object/test harness

Perhaps a callback where people could call whatever they want at the very end of object creation?

Honestly, I feel like populating has_many relationships with an after(:create) hook is inherently flawed and the culprit for this behaviour.

What is the status of this issue? Was it fixed?
According to the documentation, using create_list in after(:create) should properly update both ends of the has_many relation. However, using factory_girl (4.5.0) I can still experience the issue.

same same in 4.7 馃槙

If current object runs save validations that depend on the relationship with its has_many objects, the workaround of calling reload in after(:create) won't work.

This is annoying!!!! sucks feature

What about traits? Not sure on the internals of this vs the factory method, or how it differs, but we use this quite a bit:

FactoryGirl.define do
  factory :assistant do
    name { Faker::Name.name }
    user
  end

  factory :user do
    name { Faker::Name.name }
    email { Faker::Internet.email }

    trait :with_assistants do
      ignore do
        assistants_count 5
      end

      after(:create) do |user, evaluator|
        FactoryGirl.create_list(:assistant, evaluator.assistants_count, user: user)
      end
    end
  end
end

user = FactoryGirl.create(:user, :with_assistants, assistants_count: 3)
user.assistants.count

Also have done traits this way:

trait :with_assistants do
  transient do
    assistants_count 1
  end

  after(:create) do |user, evaluator|
    evaluator.assistants_count.times do
      user.assistants << create(:assistant)
    end
  end
end

I'm hesitant to add noise here, but I just wasted ~1 hour because of this issue. Perhaps, if nothing is done on the library side, the associations documentation could have a note added that mentions this behavior.

Even the documentation examples doesn't show this behaviour (It works without reload), so is this fixed now?

2019 and still running into this issue. it only took me 5 mins to realise it's because the association wasn't reloading..but would be really nice to come up with a solution.
I create a model at the beginning of my tests and then i keep adding associated models to it to test different scenarios, and it's really unfeasible to keep doing the reload

+1

up!
v5.1.1 still have the problem

I think it gets real bad when your User model has the following validation: validates :assistant, presence: true. Basically you can't do after(:create) because the creation failed in the first place (there's no related assistant).

I don't think you can even build both objects before saving them to circumvent this. AFAIK the parent must be saved before the other object in the relationship can be built as explained here.

Unless I'm understanding something wrong if your users must have at least one assistant there seems to be no way out of this.

Was this page helpful?
0 / 5 - 0 ratings