FactoryBot 出现了 ActiveRecord::AssociationTypeMismatch 错误,原因是类别不匹配。

huangapple go评论63阅读模式
英文:

FactoryBot ActiveRecord::AssociationTypeMismatch error with wrong class

问题

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

英文:

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 &lt; 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: &#39;Tenant::Group&#39;

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

class Tenant::Group &lt; ApplicationRecord
  has_rich_text :description

  belongs_to :tenant,
             class_name: &#39;Admin::Tenant&#39;,
             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: &#39;Admin::Tenant&#39; do
    name { Faker::Lorem.sentence }
  end

  factory :tenant_group, class: &#39;Tenant::Group&#39; 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 #&lt;Admin::Tenant id: 295, name: &quot;Perspiciatis sit numquam fugit.&quot;, created_at: &quot;2020-01-03 15:08:13&quot;, updated_at: &quot;2020-01-03 15:08:13&quot;&gt; 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...

答案1

得分: 2

错误很可能是由ActsAsTenant引起的,而不是FactoryBot,后者正在执行正确的操作。

当您创建具有相同名称的多个关联时,后者会覆盖前者。而acts_as_tenant :tenant就是这样做的,它会破坏您已经建立的关联。虽然文档不是很详细,但acts_as_tenant大致接受与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
英文:

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 &lt; ApplicationRecord
  has_rich_text :description
  acts_as_tenant :tenant,
    class_name: &#39;Admin::Tenant&#39;,
    inverse_of: :tenant_groups

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

huangapple
  • 本文由 发表于 2020年1月3日 23:21:59
  • 转载请务必保留本文链接:https://go.coder-hub.com/59581094.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定