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