总结/错误
我在应用程序的不同位置收到此错误:
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'
Controller
Controller 看起来像这样,但问题可能发生在使用 Company 模型保存或更新另一个模型的任何时候:
class Settings::CompaniesController < SettingsController
def show
@company = current_user.company
@classification = Classification.new(company: @company)
end
def update
end
end
事实/观察
一些事实和观察:
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
实际问题
我真的很想知道为什么会出现这个问题以及是否可以以某种方式避免它。
我知道这个异常(exception)原则上意味着什么。我想知道如何避免它。
特别是,我想知道问题是否是我以某种方式引起的,或者它是否是 gem,在这种情况下,我是否可以以任何方式帮助修复 gem。
预先感谢您提供任何答案。
最佳答案
问题几乎肯定是因为您将这些类的副本序列化到缓存或 session 中,然后再重新构建它们。这会导致问题,因为在开发模式下,每个请求都会对类进行未定义和重新定义,因此如果您有一个类的旧定义的编码副本,然后设法在 Rails 类卸载之前对其进行解码,您将有两个具有相同名称的不同类。
从这里引发异常:https://github.com/rails/rails/blob/3-2-stable/activerecord/lib/active_record/associations/association.rb#L204-212
你可以在这里看到它正在做一些非常简单的事情——它正在测试对象是否传入 is_a?
传递给关联的类的实例。取消定义和重新定义一个类意味着如果你有一个类的旧副本,并将它与类的新版本进行比较,它不会通过集合。考虑这个例子:
class Foo; end
f = Foo.new
Object.send :remove_const, :Foo
class Foo; end
puts f.is_a? Foo
# => false
这里发生的事情是当我们取消定义和重新定义
Foo
,它实际上创建了一个新对象(记住,类是类的实例!)。即使我们知道 f
是 Foo
, f.is_a? Foo
失败,因为 f.class
不同于 Foo
. is_a?
检查给定对象的类是否与传递的类匹配,或者它是传递的类的子类——这里也不是这种情况。它们具有相同的名称,但属于不同的类。这是在你的协会中发生的事情的核心。在某些时候,您的
Classification
协会期待某个版本的Company
,并且您正在分配不同的版本。如果我不得不猜测,我会说您将整个用户记录存储在 session 中。这将编码(marshal)记录,包括相关的 Company
记录。在 Rails 重新加载其类之前,Rack 将对该 Company 记录进行解码,因此它最终可能成为与协会预期不同的类(具有相同的名称)。流程类似于:Company
.我们将称之为 Company-1 is_a? Company-2
. 解决方案是避免在 session 或缓存中存储整个编码对象。相反,存储主键并对每个请求执行查找。这解决了这个特定问题,以及在生产后期可能不兼容的对象定义的问题(考虑在部署对对象结构进行重大更改的更改之前,具有与编码对象存在 session 的用户)。
通常,这可能是由于任何可以在请求之间保留旧类引用的原因造成的。 Marshal 是通常的嫌疑人,但某些类变量和全局变量也可以做到。
如果 gem 在某个地方存储类或全局变量中的类引用列表,它可能会这样做,但我的直觉是它在您的 session 中。
关于ruby-on-rails - 同一模型的 AssociationTypeMismatch,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15909663/