【问题标题】:AssociationTypeMismatch for the same model同一模型的 AssociationTypeMismatch
【发布时间】:2013-04-09 18:26:17
【问题描述】:

总结/错误

我在应用程序的不同位置收到此错误:

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

实际问题

我真的很想知道为什么会出现这个问题,以及是否可以通过某种方式避免它。

原则上我知道异常意味着什么。我想知道如何避免它。

特别是,我想知道我是否以某种方式引起了问题,或者它是否是 gem,在这种情况下,我是否可以通过任何方式帮助修复 gem。

提前感谢您的任何回答。

【问题讨论】:

  • ActiveRecord::AssociationTypeMismatch 出现when an object assigned to an association has an incorrect type。在您的情况下,您是associating company to classification。当您在它们之间定义关联时,这是正确的。但是您并没有在您的操作中保存对象。
  • 是的,我知道异常意味着什么。关联是双向定义的,将添加编辑以反映这一点。关于这句话:“但你并没有在你的行动中保存对象”。我不保存它,因为我不想保存它。我正在为表单准备一个分类对象。
  • 这也发生在我身上。分期和生产都很好,但在开发中它会在一段时间后中断。我们已经对此进行了几次测试(我们还尝试删除所有记录并创建新记录)
  • 新的开始工作,但过了一会儿,他们突然返回错误的类型。实际上 - 我们的模型中有一个方法可以将序列化数据转换为具有无关访问的哈希。问题是,一段时间后,检索序列化列会产生一个字符串而不是预期的哈希。同样,这不会发生在生产/登台

标签: ruby-on-rails activerecord ruby-on-rails-3.2


【解决方案1】:

问题几乎肯定是因为您将这些类的副本序列化到缓存或会话中,然后再重新构建它们。这会导致问题,因为在开发模式下,类在每个请求上都未定义和重新定义,所以如果你有一个类的旧定义的编组副本,然后在 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 时,它实际上创建了一个新对象(请记住,类是类的实例!)。即使我们知道fFoof.is_a? Foo 也会失败,因为f.classFoo 不同。 is_a? 检查给定对象的类是否与传递的类匹配,或者它是传递的类的子类——这里也不是这种情况。它们具有相同的名称,但它们是不同的类。这是您的协会中发生的事情的核心。

在某些时候,您的Classification 关联需要Company 的某个版本,而您正在分配一个不同的版本。如果我不得不猜测,我会说您将整个用户记录存储在会话中。这将封送记录,包括关联的Company 记录。在Rails 重新加载其类之前,该公司记录将被Rack 解组,因此它可能最终成为与协会预期不同的类(具有相同的名称)。流程是这样的:

  • 定义Company。我们称之为 Company-1
  • 加载用户及其关联的公司 (Company-1) 记录。
  • 将整个交易保存到会话中。
  • 刷新页面
  • 在 Rack 的设置过程中,它将在会话中找到一条公司记录(附加到用户记录)并将其解组。这会将其解组为 Company-1(因为这是 Company 当前 Object#constants 的实例)
  • Rails 将卸载所有模型常量并重新定义它们。在此过程中,它将重新定义公司(Company-2),并设置分类以期望在关联中出现 Company-2 记录。
  • 您尝试将 Company-1 对象分配给期望 Company-2 对象的关联。抛出错误,因为正如我们之前看到的,Company-1 的实例失败is_a? Company-2

解决方案是避免将整个编组的对象存储在会话或缓存中。相反,存储主键并对每个请求执行查找。这解决了这个特定问题,以及后期生产中可能不兼容的对象定义的问题(考虑在部署对对象结构进行重大更改的更改之前与编组对象存在会话的用户)。

一般来说,这可能是由任何可以在请求之间保留旧类引用的东西引起的。 Marshal 是常见的嫌疑人,但某些类变量和全局变量也可以做到这一点。

如果 gem 可能会在某个地方存储类或全局变量中的类引用列表,那么它可能会这样做,但我的直觉是它在您的会话中。

【讨论】:

  • 我认为这是对*我们的问题的一个很好的解释。不确定OP的,所以我会让他接受。但我理解了大部分内容(需要阅读其他内容),所以我对此很满意:)
  • 这正是发生的事情!很好的答案,我现在不担心在开发模式下间歇性出现的那个讨厌的错误......我使用的是 Rails.cache 并且存储的类与新的不同。
  • 很抱歉迟到了。很好的答案,感谢您抽出宝贵的时间。
【解决方案2】:

我在开发环境中有一个ActiveJobasync 模式下运行,它将为给定模型排队一堆其他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

【讨论】:

  • 这是在 Rails 5.2.0 btw 中观察到的。
猜你喜欢
  • 1970-01-01
  • 2012-06-08
  • 1970-01-01
  • 2011-06-20
  • 1970-01-01
  • 1970-01-01
  • 2020-02-04
  • 1970-01-01
  • 2017-07-08
相关资源
最近更新 更多