Factory_bot: ActiveRecord::AssociationTypeMismatch same class but different object_ids

Created on 24 Aug 2019  路  7Comments  路  Source: thoughtbot/factory_bot

Steps to reproduce

Following up with @hwhelchel on #1029

Isolated the issue here:

When calling FactoryBot.create(:story), the activerecord-6.0.0/lib/active_record/associations/association.rb#raise_on_type_mismatch! method is called. Inside, record.is_a?(reflection.klass) is called which compares the class of record to reflection.klass. When you put a pry statement there, you'll see that both classes are Account, that said, they have different object_ids causing the is_a? equality check to fail and then causing the AssociationTypeMismatch error to be raised. After the error is raised, if you call FactoryBot.reload, and then run FactoryBot.create(:story) again it works.

Reproduction Script

git clone https://github.com/zminot/factory_bot_association_mismatch
bundle install
rails db:create && rails db:migrate
rails c
> FactoryBot.create(:story)
> # ERROR

Expected behavior

> FactoryBot.create(:story)
# => works with no error

Actual behavior

> FactoryBot.create(:story)
# ActiveRecord::AssociationTypeMismatch: Account(#70116037268360) expected, got #<Account id: 1, created_at: "2019-08-24 19:13:54", updated_at: "2019-08-24 19:13:54"> which is an instance of Account(#70116063567540)
from /Users/zachminot/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/activerecord-6.0.0/lib/active_record/associations/association.rb:279:in `raise_on_type_mismatch!'
> FactoryBot.reload
> FactoryBot.create(:story)
# => works with no error

System configuration

factory_bot version: 5.0.2
rails version: 6.0
ruby version: 2.6.3

bug

Most helpful comment

I dug a bit further into this and it appears to be an interaction between spring, the EventedFileUpdateChecker, and the Active Job inline queue adapter. If you remove any one of these things the problem goes away:

  • Set the DISABLE_SPRING=1 environment variable
  • Remove the line config.active_job.queue_adapter = :inline
  • Remove the line config.file_watcher = ActiveSupport::EventedFileUpdateChecker (which will default to the old non-listen file checker)

I will see if I can get this to the point where I can open an issue on Rails.

All 7 comments

/cc @composerinteralia

Key lines that cause the error to trigger are:

config.active_job.queue_adapter = :inline in config/environments/development.rb and the has_one_attached :profile_photo in the Account.

Comment either of those out and it runs cleanly.

So maybe when ActiveStorage analyzes the profile photo in the inline job queue a new Account class object is created and some how shares scope with a previously loaded Account class object.

Eep! I was hoping I had seen the last of that error. It comes up every so often, and we have a workaround in the README.

I appreciate your detailed report and reproduction instructions. This seems like a fun one to dig into. I won't have time right away, so feel free to keep digging if you have any further ideas about why this might be happening.

I was hoping we might get lucky and https://github.com/thoughtbot/factory_bot_rails/pull/343 would fix it, but alas it did not help matters.

I also wondered if the caching of the class in https://github.com/thoughtbot/factory_bot/blob/master/lib/factory_bot/factory.rb#L22-L28 might be causing problems, but I didn't have much luck fiddling with that either.

@composerinteralia thanks for looking into this. In development if I run:

rails c
> FactoryBot.reload
> FactoryBot.create(:story)

I still get an error.

Screen Shot 2019-08-26 at 10 15 59 PM

Hope that shines some helpful light on the problem.

Turning off spring does fix the issue in development.

When you can revisit, let me know what you find!

Some, but not all, of us are getting the ActiveRecord::AssociationTypeMismatch in seeds.rb when a model is created which references a model created by FactoryBot, e.g.

account = FactoryBot.create(:account)
product = Product.create!(account: account, ....)

I noticed while the class has the same name, it is a different object:

account.class.object_id
=> 69890016840780
Account.object_id
=> 69889857397040

account.class == Account
=> false

ObjectSpace._id2ref(account.class.object_id) == ObjectSpace._id2ref(Account.object_id)
=> false

Doing FactoryBot.reload does not help. Thanks!

Update: disabling spring fixes this, e.g. env env DISABLE_SPRING=1 rake db:seed

I finally did some deeper digging into this, and it turns out it is not a factory_bot problem after all. Although we had seen ActiveRecord::AssociationTypeMismatch in factory_bot in the past, where calling FactoryBot.reload seemed to fix the problem, this seems to be a separate issue.

To reproduce this problem without factory_bot, I removed the factory_bot_rails gem from the Gemfile of the provided repo, then ran:

story_type = StoryType.new 
account = Account.new
account.profile_photo = Rack::Test::UploadedFile.new(Rails.root.join("spec", "factories", "test-image.jpeg"), "image/jpeg")
account.save!
story_type.account = account
#=> ActiveRecord::AssociationTypeMismatch

Since this is not coming from factory_bot, I am going to close it. I will probably keep poking around in Rails and spring to see if I can figure out what exactly is going on. For some reason the Account class is getting reloaded when running story_type.account = account.

I dug a bit further into this and it appears to be an interaction between spring, the EventedFileUpdateChecker, and the Active Job inline queue adapter. If you remove any one of these things the problem goes away:

  • Set the DISABLE_SPRING=1 environment variable
  • Remove the line config.active_job.queue_adapter = :inline
  • Remove the line config.file_watcher = ActiveSupport::EventedFileUpdateChecker (which will default to the old non-listen file checker)

I will see if I can get this to the point where I can open an issue on Rails.

Was this page helpful?
0 / 5 - 0 ratings