我在我的应用程序中的不同位置出现此错误:
- 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类在重新加载时获取新的类标识符.我听说有关它的谣言是由于草率的要求.我在公司模型中没有要求我自己,但我确实使用了active-record-postgres-hstore.
模特
这是公司型号:
- 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,:company,:company_id
- has_many :ideas
- belongs_to :company
- def to_s
- name
- end
- end
实际的问题
我真的很想知道为什么会出现这个问题,以及是否可以以某种方式避免它.
我知道这个例外在原则上意味着什么.我想知道如何避免它.
特别是,我想知道我是否以某种方式引起了问题,或者它是否是宝石,在这种情况下,如果我能以任何方式帮助修复宝石.
提前感谢您的任何答案.
解决方法
你可以在这里看到它正在做一些非常简单的事情 – 它正在测试传入的对象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?检查给定对象的类是否与传递的类匹配,或者它是传递的类的子类 – 这里也不是这种情况.它们共享相同的名称,但它们是不同的类.这是您协会中发生的事情的核心.
在某些时候,您的分类协会需要某个版本的公司,并且您要分配不同的版本.如果我不得不猜测,我会说你将整个用户记录存储在会话中.这将编制记录,包括相关的公司记录.在Rails进行类重新加载之前,本公司记录将由Rack解组,因此它可能最终成为与协会期望的不同的类(具有相同的名称).流程如下:
>定义公司.我们称之为公司-1
>加载用户及其关联的公司(公司-1)记录.
>将整个交易保存到会话中.
>刷新页面
>在Rack的设置期间,它将在会话中找到公司记录(附加到用户记录)并解组.这将解组为Company-1(因为这是公司当前对象#常量的实例)
>然后Rails将卸载所有模型常量并重新定义它们.在此过程中,它将重新定义公司(公司-2),并设置分类以期望公司2在该协会中的记录.
>您尝试将Company-1对象分配给期望Company-2对象的关联.抛出错误,因为如前所述,Company-1的实例失败is_a?公司-2.
解决方案是避免将整个编组对象存储在会话或缓存中.而是存储主键并对每个请求执行查找.这解决了这个特定问题,以及稍后在生产中可能不兼容的对象定义的问题(在部署对该对象的结构进行重大更改的更改之前,请考虑具有与编组对象存在的会话的用户).
通常,这可能是由任何可以在请求之间保留旧类引用的内容引起的. Marshal是通常的嫌疑人,但某些类变量和全局变量也可以做到.
如果gem在某个类或全局变量中存储类引用列表,那么gem可能会这样做,但我的预感是它在你的会话中.