11

まとめ・エラー

アプリケーションのさまざまな場所でこのエラーが発生しています。

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'

コントローラ

コントローラーは次のようになりますが、Company モデルを使用して別のモデルを保存または更新する任意の時点で問題が発生する可能性があります。

class Settings::CompaniesController < SettingsController
  def show
    @company = current_user.company
    @classification = Classification.new(company: @company)
  end

  def update
  end
end

事実/観察

いくつかの事実と観察:

  • この問題はランダムに発生しますが、通常は開発サーバーがしばらく実行された後に発生します。
  • この問題は本番環境では発生しません。
  • Companyモデルにまったく変更を加えていない場合でも、問題が発生します。
  • この問題は、サーバーを再起動することで解決されます。

理論

私が理解している限り、これはクラスの動的ロードによるものです。

どういうわけか、リロード時に Company クラスが新しいクラス識別子を取得しています。ずさんな要求が原因であるという噂を聞いたことがあります。Company モデルで独自の require を行っているわけではありませんが、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

実際の質問

この問題が発生する理由と、何らかの方法で回避できるかどうかを知りたいと思っています。

原則として、例外が何を意味するかを知っています。回避方法が知りたいです。

特に、私が何らかの形で問題を引き起こしたのか、それとも宝石が原因なのかを知りたいです。その場合、何らかの方法で宝石を修正できるかどうかを知りたいです。

ご回答ありがとうございます。

4

2 に答える 2

31

これらのクラスのコピーをキャッシュまたはセッションにシリアル化し、後でそれらを再構成しているため、問題はほぼ確実です。開発モードではリクエストごとにクラスが未定義および再定義されるため、これにより問題が発生します。そのため、クラスの古い定義のマーシャリングされたコピーがあり、Rails クラスのアンロード前にそれをアンマーシャリングすると、2 つになります。同じ名前の異なるクラス。

ここから例外が発生しています: 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

ここで何が起きているかというと、 を undefine して再定義Fooすると、実際には新しいオブジェクトが作成されるということです (クラスは Class のインスタンスであることを思い出してください)。fが であることはわかっていても、Fooはとは異なるf.is_a? Fooため失敗します。指定されたオブジェクトのクラスが渡されたクラスと一致するか、渡されたクラスのサブクラスであることを確認します - ここではどちらも当てはまりません。名前は同じですが、クラスが異なります。これは、あなたの協会で起こっていることの核心です。f.classFoois_a?

ある時点で、アソシエーションが の特定のバージョンClassification期待しているのに、別のバージョンを割り当てようとしています。推測する必要がある場合は、ユーザー レコード全体をセッションに保存していると言えます。これにより、関連付けられたレコードを含むレコードがマーシャリングされます。この Company レコードは、Rails がクラスをリロードする前にRack によってアンマーシャリングされるため、関連付けが期待するものとは異なる (同じ名前の) クラスになる可能性があります。流れは次のようなものです。CompanyCompany

  • を定義しCompanyます。これを Company-1 と呼びます
  • ユーザーとそれに関連付けられた Company (Company-1) レコードを読み込みます。
  • このdealio全体をセッションに保存します。
  • ページを更新
  • Rack のセットアップ中に、セッション内の Company レコード (User レコードに関連付けられている) が検出され、非整列化されます。これにより、Company-1 としてアンマーシャリングされます (これは現在の Company Object#constants のインスタンスであるため)。
  • その後、Rails はすべてのモデル定数をアンロードして再定義します。このプロセスでは、Company (Company-2) を再定義し、関連付けで Company-2 レコードを予期するように分類を設定します。
  • Company-1 オブジェクトを、Company-2 オブジェクトを期待する関連付けに割り当てようとしています。前に見たように、 Company-1 のインスタンスが失敗するため、エラーがスローされますis_a? Company-2

解決策は、マーシャリングされたオブジェクト全体をセッションまたはキャッシュに格納しないようにすることです。代わりに、主キーを保存し、リクエストごとにルックアップを実行します。これにより、この特定の問題だけでなく、本番環境の後半で互換性のないオブジェクト定義が発生する可能性があるという問題も解決されます (マーシャリングされたオブジェクトとのセッションが存在し、そのオブジェクトの構造に大幅な変更を加える変更をデプロイするユーザーを考えてみてください)。

一般に、これは、リクエスト間で古いクラス参照を保持できるものが原因で発生する可能性があります。通常は Marshal が疑われますが、特定のクラス変数とグローバルもそれを行うことができます。

クラスまたはグローバル変数にクラス参照のリストを格納している場所であれば、gem がそれを実行する可能性がありますが、私の推測では、それはセッション内の何かです。

于 2013-07-16T03:51:12.277 に答える