ruby-on-rails - 同一模型的 AssociationTypeMismatch

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

总结/错误

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

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 ,它实际上创建了一个新对象(记住,类是类的实例!)。即使我们知道 fFoo , f.is_a? Foo失败,因为 f.class不同于 Foo . is_a?检查给定对象的类是否与传递的类匹配,或者它是传递的类的子类——这里也不是这种情况。它们具有相同的名称,但属于不同的类。这是在你的协会中发生的事情的核心。

    在某些时候,您的 Classification协会期待某个版本的Company ,并且您正在分配不同的版本。如果我不得不猜测,我会说您将整个用户记录存储在 session 中。这将编码(marshal)记录,包括相关的 Company记录。在 Rails 重新加载其类之前,Rack 将对该 Company 记录进行解码,因此它最终可能成为与协会预期不同的类(具有相同的名称)。流程类似于:
  • 定义 Company .我们将称之为 Company-1
  • 加载用户及其关联的公司 (Company-1) 记录。
  • 将整个交易保存到 session 中。
  • 刷新页面
  • 在 Rack 的设置过程中,它会在 session 中找到一个公司记录(附加到用户记录)并将其解码。这会将其解码为 Company-1(因为这是 Company 当前 Object#constants 的实例)
  • 然后 Rails 将卸载所有模型常量并重新定义它们。在此过程中,它将重新定义公司(公司 2),并设置分类以期望关联中的公司 2 记录。
  • 您尝试将 Company-1 对象分配给需要 Company-2 对象的关联。错误被抛出,因为正如我们之前看到的,Company-1 的一个实例失败 is_a? Company-2 .

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

    通常,这可能是由于任何可以在请求之间保留旧类引用的原因造成的。 Marshal 是通常的嫌疑人,但某些类变量和全局变量也可以做到。

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

    关于ruby-on-rails - 同一模型的 AssociationTypeMismatch,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15909663/

    相关文章:

    ruby-on-rails - ruby::Module 或者只是 Module

    ruby-on-rails - 使用带有条件的 has_one 来查找相关对象,但是如何创建相关对象呢?

    ruby-on-rails-3.2 - AngularJS 中的嵌套模型

    ruby-on-rails - AbstractController::ActionNotFound - 奇怪的错误

    ruby-on-rails - 删除 herokuapp 子域

    ruby-on-rails - 如何在 Ruby-On-Rails 应用程序的用户模型中存储数组?

    ruby-on-rails - 自定义 Controller 操作的路由

    ruby-on-rails - 在 Ctrl+C 中断的情况下,如何在 Rake 任务中使用 ActiveRecord 事务进行回滚

    ruby-on-rails - 选择第三层 ActiveRecord_Relation - Rails、ActiveRecord、HABTM

    ruby-on-rails - 查找或创建