我在应用程序的不同位置出现此错误:
ActiveRecord::AssociationTypeMismatch in Settings::CompaniesController#show
Company(#70257861502120) expected, got Company(#70257861787700)
activerecord (3.2.11) lib/active_record/associations/association.rb:204:in `raise_on_type_mismatch'
activerecord (3.2.11) lib/active_record/associations/belongs_to_association.rb:6:in `replace'
activerecord (3.2.11) lib/active_record/associations/singular_association.rb:17:in `writer'
activerecord (3.2.11) lib/active_record/associations/builder/association.rb:51:in `block in define_writers'
activerecord (3.2.11) lib/active_record/attribute_assignment.rb:85:in `block in assign_attributes'
activerecord (3.2.11) lib/active_record/attribute_assignment.rb:78:in `each'
activerecord (3.2.11) lib/active_record/attribute_assignment.rb:78:in `assign_attributes'
activerecord (3.2.11) lib/active_record/base.rb:497:in `initialize'
app/controllers/settings/companies_controller.rb:4:in `new'
app/controllers/settings/companies_controller.rb:4:in `show'
控制器看起来像这样,但问题可能发生在使用公司模型保存或更新其他模型的任何地方:
class Settings::CompaniesController < SettingsController
def show
@company = current_user.company
@classification = Classification.new(company: @company)
end
def update
end
end
一些事实和观察:
Company
模型没有任何更改,也会出现问题。据我所知,这是由于动态加载类。
不知何故,Company类在重新加载时获取新的类标识符。我听说有关它是由于草率要求的谣言。我在公司模型中没有要求我自己,但我确实使用active-record-postgres-hstore。
这是Company
型号:
class Company < ActiveRecord::Base
serialize :preferences, ActiveRecord::Coders::Hstore
DEFAULT_PREFERENCES = {
require_review: false
}
has_many :users
has_many :challenges
has_many :ideas
has_many :criteria
has_many :classifications
attr_accessible :contact_email, :contact_name, :contact_phone, :email, :logotype_id, :name, :phone, :classifications_attributes, :criteria_attributes, :preferences
accepts_nested_attributes_for :criteria
accepts_nested_attributes_for :classifications
after_create :setup
before_save :set_slug
# Enables us to fetch the data from the preferences hash directly on the instance
# Example:
# company = Company.first
# company.preferences[:foo] = "bar"
# company.foo
# > "bar"
def method_missing(id, *args, &block)
indifferent_prefs = HashWithIndifferentAccess.new(preferences)
indifferent_defaults = HashWithIndifferentAccess.new(DEFAULT_PREFERENCES)
if indifferent_prefs.has_key? id.to_s
indifferent_prefs.fetch(id.to_s)
elsif indifferent_defaults.has_key? id.to_s
indifferent_defaults.fetch(id.to_s)
else
super
end
end
private
def setup
DefaultClassification.find_each do |c|
Classification.create_from_default(c, self)
end
DefaultCriterion.find_each do |c|
Criterion.create_from_default(c, self)
end
end
def set_slug
self.slug = self.name.parameterize
end
end
分类模型:
class Classification < ActiveRecord::Base
attr_accessible :description, :name, :company, :company_id
has_many :ideas
belongs_to :company
def to_s
name
end
end
我真的很想知道为什么会出现这个问题以及是否可以以某种方式避免这种情况。
我知道异常在原则上意味着什么。我想知道如何避免它。
特别是,我想知道我是否以某种方式引起了问题,或者它是否是宝石,在这种情况下,如果我能以任何方式帮助修复宝石。
提前感谢您的任何答案。
答案 0 :(得分:22)
问题几乎肯定是因为您将这些类的副本序列化到缓存或会话中,然后重新构建它们。这会导致问题,因为类在开发模式下的每个请求都未定义和重新定义,所以如果你有一个类的旧定义的编组副本,然后在Rails类卸载之前设法解组它,你将会有两个具有相同名称的不同类。
你可以在这里看到它正在做一些非常简单的事情 - 它正在测试传递给该关联的类is_a?
实例中传递的对象。取消定义和重新定义一个类意味着如果你有一个类的旧副本,并将它与该类的新版本进行比较,那么它就不会通过集合。考虑这个例子:
class Foo; end
f = Foo.new
Object.send :remove_const, :Foo
class Foo; end
puts f.is_a? Foo
# => false
这里发生的事情是,当我们取消定义并重新定义Foo
时,它实际上会创建一个新对象(记住,类是Class的实例!)。即使我们知道f
是Foo
,f.is_a? Foo
也会失败,因为f.class
与Foo
不同。 is_a?
检查给定对象的类是否与传递的类匹配,或者它是传递的类的子类 - 这里也不是这种情况。它们共享相同的名称,但它们是不同的类。这是您协会中发生的事情的核心。
在某些时候,您的Classification
关联需要Company
的某个版本,并且您正在分配不同的版本。如果我不得不猜测,我会说你在会话中存储了整个用户记录。这将编组记录,包括相关的Company
记录。在 Rails重新加载类之前,此公司记录将由Rack 解组,因此它可能最终成为与协会期望的不同的类(具有相同名称)。流程类似于:
Company
。我们称之为公司-1 is_a? Company-2
。解决方案是避免将整个编组对象存储在会话或缓存中。而是存储主键并对每个请求执行查找。这解决了这个特殊问题,以及稍后在生产中可能不兼容的对象定义的问题(在部署对该对象的结构进行重大更改的更改之前,请考虑具有与编组对象存在的会话的用户)。 p>
通常,这可能是由任何可以在请求之间保留旧类引用的内容引起的。 Marshal是通常的嫌疑人,但某些类变量和全局变量也可以做到。
如果gem存在于类或全局变量中的类引用列表中,那么gem可能会这样做,但我的预感是它在你的会话中是什么。
答案 1 :(得分:2)
我在开发环境中以ActiveJob
模式运行的async
在给定模型中会排队其他ActiveJob。
因此,基本上FirstJob
将开始运行,并且对其处理的每个记录都将开始SecondJob
,从而导致多达25个作业在同一进程中异步运行。这会迅速导致ActiveRecord::AssociationTypeMismatch
甚至A copy of Klass has been removed from the module tree but is still active
错误。
通过在开发中将ActiveJob队列适配器切换为:inline
,我消除了该问题。
我创建了一个带有以下内容的初始化程序:
if Rails.env.test?
ActiveJob::Base.queue_adapter = :test
elsif Rails.env.development?
ActiveJob::Base.queue_adapter = :inline
else
ActiveJob::Base.queue_adapter = :sidekiq # or your preferred choice
end