Search code examples
ruby-on-railsrspecfactory-bot

FactoryBot ActiveRecord::AssociationTypeMismatch error with wrong class


Good morning,

I am working on a proof of concept Rails application after a long break from using Rails. I set up RSpec tests, as well as FactoryBot and Faker to generate test data. In my app, I have two models:

class Admin::Tenant < ApplicationRecord
  has_rich_text :description

  has_and_belongs_to_many :users,
                          association_foreign_key: :admin_user_id,
                          foreign_key: :admin_tenant_id

  has_many :tenant_groups,
           inverse_of: :tenant,
           dependent: :destroy,
           class_name: 'Tenant::Group'

  validates :name,
            presence: true,
            length: { maximum: 255 }
end

class Tenant::Group < ApplicationRecord
  has_rich_text :description

  belongs_to :tenant,
             class_name: 'Admin::Tenant',
             inverse_of: :tenant_groups

  validates :name,
            presence: true,
            length: { maximum: 255 }

  acts_as_tenant :tenant
end

I also have the two factories defined:

FactoryBot.define do
  factory :admin_tenant, class: 'Admin::Tenant' do
    name { Faker::Lorem.sentence }
  end

  factory :tenant_group, class: 'Tenant::Group' do
    association :tenant, factory: :admin_tenant
    name { Faker::Lorem.sentence }
  end
end

When utilizing the factory :admin_tenant on its own, it seems to work fine, but when I attempt to generate a :tenant_group (using create(:tenant_group)) I receive an error in rspec:

     Failure/Error: let(:tenant_group) { create(:tenant_group) }

     ActiveRecord::AssociationTypeMismatch:
       Tenant(#52813860) expected, got #<Admin::Tenant id: 295, name: "Perspiciatis sit numquam fugit.", created_at: "2020-01-03 15:08:13", updated_at: "2020-01-03 15:08:13"> which is an instance of Admin::Tenant(#55283820)

It seems that for some reason, it is assuming the class of the factory should be something else. Since I specify class_name in the association, I'd assume it would work (it does when I'm using the application itself). I saw that Spring might cause issues, so I followed the advice of the FactoryBot README and placed config.before(:suite) { FactoryBot.reload } in my rails_helper.rb file.

Update 1

Now finding out that the problem lies with acts_as_tenant. The stack trace was too short in RSpec output to realize what the issue was, but now it's showing up in regular usage, as well.

Update 2

I'm going to go ahead and mark this as solved. It doesn't appear to be an issue with FactoryBot as I initially thought, but rather an issue with my understanding of acts_as_tenant. When the class name cannot be easily inferred by the association name, you must specify the :class_name option. This became clear after browsing the source code for a bit. In retrospect, it seems obvious, since all associations seem to behave the same way...


Solution

  • The error is most likely caused by ActsAsTenant and not FactoryBot which is doing the right thing.

    When you create multiple associations with the same name the later overwrite the former. And acts_as_tenant :tenant does just that and clobbers the association you already set up. Its not very well documented but the acts_as_tenant macro takes roughly the same options as belongs_to.

    class Tenant::Group < ApplicationRecord
      has_rich_text :description
      acts_as_tenant :tenant,
        class_name: 'Admin::Tenant',
        inverse_of: :tenant_groups
    
      validates :name,
                presence: true,
                length: { maximum: 255 }
    end