2013-04-09 30 views
6

Tóm tắt/lỗi

Tôi nhận được lỗi này ở nhiều nơi khác nhau trong ứng dụng của tôi:AssociationTypeMismatch cho cùng một mô hình

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' 

khiển

Bộ điều khiển trông như thế này, nhưng vấn đề có thể xảy ra tại bất kỳ thời điểm nào mà mô hình Công ty được sử dụng để lưu hoặc cập nhật một mô hình khác:

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

    def update 
    end 
end 

Sự kiện/quan sát

Một số sự kiện và quan sát:

  • Vấn đề xảy ra một cách ngẫu nhiên, nhưng thông thường sau khi máy chủ phát triển đã được chạy trong một thời gian.
  • Sự cố không xảy ra trong quá trình sản xuất.
  • Sự cố xảy ra ngay cả khi tôi hoàn toàn không thay đổi đối với mô hình Company.
  • Sự cố được giải quyết bằng cách khởi động lại máy chủ.

Lý thuyết

Theo như tôi hiểu được điều này là do nạp năng động của các lớp học.

Bằng cách nào đó lớp Công ty đang nhận mã định danh lớp mới khi tải lại. Tôi đã nghe những tin đồn về nó là do yêu cầu cẩu thả. Tôi không yêu cầu của riêng tôi trong mô hình Công ty, nhưng tôi sử dụng active-record-postgres-hstore.

Các mô hình

Đây là mô hình 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 

Mô hình Phân loại:

class Classification < ActiveRecord::Base 
    attr_accessible :description, :name, :company, :company_id 
    has_many :ideas 
    belongs_to :company 

    def to_s 
    name 
    end 
end 

Câu hỏi thực tế

Tôi muốn được thực sự muốn biết lý do tại sao vấn đề này xảy ra và nếu nó có thể tránh được bằng cách nào đó.

Tôi biết ý nghĩa của ngoại lệ. Tôi muốn biết cách tránh nó.

Đặc biệt, tôi muốn biết nếu tôi gây ra vấn đề bằng cách nào đó hoặc nếu nó là đá quý, và trong trường hợp đó nếu tôi có thể giúp sửa chữa đá quý bằng bất kỳ cách nào.

Cảm ơn bạn trước vì bất kỳ câu trả lời nào.

+0

'ActiveRecord :: AssociationTypeMismatch' xảy ra' khi đối tượng được gán cho liên kết có loại không đúng'. Trong trường hợp của bạn, bạn đang 'liên kết công ty để phân loại'. Đó là chính xác khi bạn có liên kết được xác định giữa những người. Nhưng bạn không lưu đối tượng trong hành động của bạn. – codeit

+0

Xem phần này: https://github.com/rails/rails/blob/master/activerecord/lib/active_record/errors.rb – codeit

+0

Có, tôi biết ý nghĩa của ngoại lệ. Hiệp hội được xác định theo cả hai cách, sẽ thêm chỉnh sửa để phản ánh điều này. Về câu này: "Nhưng bạn không lưu đối tượng trong hành động của bạn". Tôi không cứu nó vì tôi không muốn cứu nó. Tôi đang chuẩn bị một đối tượng Phân loại cho một biểu mẫu. – Jesper

Trả lời

18

Vấn đề là gần như chắc chắn vì bạn đang sắp xếp các bản sao của các lớp này vào bộ nhớ cache hoặc phiên, sau đó tái tạo lại chúng. Điều này gây ra vấn đề bởi vì các lớp nhận được undefined và redefined trên mỗi yêu cầu trong chế độ phát triển, vì vậy nếu bạn có một bản sao marshalled của một định nghĩa cũ của một lớp, và sau đó quản lý để unmarshal nó trước khi Rails lớp dỡ, bạn sẽ có hai các lớp khác nhau có cùng tên.

Trường hợp ngoại lệ đã được huy động từ ở đây: https://github.com/rails/rails/blob/3-2-stable/activerecord/lib/active_record/associations/association.rb#L204-212

Bạn có thể thấy ở đây là nó đang làm cái gì đó rất đơn giản - đó là thử nghiệm mà các đối tượng thông qua trong is_a? thể hiện của lớp truyền cho hiệp hội. Việc định nghĩa lại và định nghĩa lại một lớp có nghĩa là nếu bạn có một bản sao cũ của một lớp, và so sánh nó với phiên bản mới của lớp, nó sẽ không đi qua cơ sở. Hãy xem xét ví dụ sau:

class Foo; end 
f = Foo.new 

Object.send :remove_const, :Foo 
class Foo; end 

puts f.is_a? Foo 
# => false 

gì đang xảy ra ở đây là khi chúng ta undefine và xác định lại Foo, nó thực sự tạo một đối tượng mới (hãy nhớ, lớp học là trường hợp của Class!). Mặc dù chúng tôi biết rằng fFoo, f.is_a? Foo không thành công vì f.class khác với Foo. is_a? kiểm tra xem lớp của đối tượng đã cho có khớp với lớp được truyền hay là lớp con của lớp được truyền - không phải là trường hợp ở đây. Họ chia sẻ cùng một tên, nhưng họ là các lớp khác nhau. Đây là cốt lõi của những gì đang xảy ra trong các hiệp hội của bạn.

Tại một số thời điểm, liên kết Classification của bạn mong đợi một số phiên bản nhất định của Company và bạn đang chỉ định một phiên bản khác. Nếu tôi phải đoán, tôi muốn nói rằng bạn đang lưu trữ toàn bộ hồ sơ người dùng trong phiên. Điều này sẽ sắp xếp bản ghi, bao gồm bản ghi Company được liên kết. Hồ sơ của Công ty này sẽ không bị so sánh bởi Rack trước khi Rails thực hiện việc nạp lại lớp, vì vậy nó có thể sẽ là một lớp khác (có cùng tên) so với những gì hiệp hội mong đợi. Dòng chảy giống như sau:

  • Xác định Company. Chúng tôi sẽ gọi cho công ty này-1
  • Tải người dùng và bản ghi Công ty được liên kết (Công ty-1).
  • Lưu toàn bộ giao dịch này vào phiên.
  • Làm mới trang
  • Trong khi thiết lập Rack, nó sẽ tìm thấy Hồ sơ công ty trong phiên (được đính kèm với Hồ sơ người dùng) và thay đổi thứ tự. Điều này sẽ unmarshal nó như Công ty-1 (vì đây là trường hợp của công ty hiện Object # hằng số)
  • Rails sau đó sẽ dỡ bỏ tất cả các hằng số mô hình của bạn và xác định lại chúng. Trong quá trình này, nó sẽ xác định lại Công ty (Company-2) và thiết lập Phân loại để mong đợi hồ sơ Công ty-2 trong hiệp hội.
  • Bạn cố gắng gán đối tượng Công ty-1 cho một liên kết đang mong đợi đối tượng Công ty-2. Lỗi được ném, vì như chúng ta đã thấy trước đó, một thể hiện của Công ty-1 không thành công is_a? Company-2.

Giải pháp là tránh lưu trữ toàn bộ các đối tượng được đầm lầy trong phiên hoặc bộ nhớ cache. Thay vào đó, lưu trữ khóa chính và thực hiện tra cứu trên mỗi yêu cầu. Điều này giải quyết vấn đề cụ thể này, cũng như vấn đề của các định nghĩa đối tượng có khả năng không tương thích sau này trong sản xuất (xem xét một người dùng có một phiên tồn tại với một đối tượng marshalled trước khi bạn triển khai một thay đổi làm thay đổi đáng kể cấu trúc của đối tượng đó).

Nói chung, điều này có thể do bất kỳ điều gì có thể tồn tại giữa các tham chiếu lớp cũ giữa các yêu cầu. Nguyên soái là nghi phạm thông thường, nhưng một số biến lớp và hình cầu nhất định cũng có thể làm điều đó.

Đá quý có thể làm điều đó nếu nó ở đâu đó để lưu trữ danh sách tham chiếu lớp trong lớp hoặc biến toàn cầu, nhưng linh cảm của tôi là nó có gì đó trong phiên của bạn.

+1

Tôi nghĩ đây là lời giải thích rất tốt cho vấn đề của chúng tôi. Không chắc chắn về OP, vì vậy tôi sẽ để nó cho anh ta chấp nhận. Nhưng tôi hiểu hầu hết các công cụ (sẽ cần phải đọc lên những người khác) vì vậy tôi hài lòng với điều này :) – corroded

+0

Đó là chính xác những gì đã xảy ra! Câu trả lời tuyệt vời, tôi bây giờ không lo lắng về lỗi pesky mà giữ popping liên tục vào chế độ phát triển ... Tôi đã sử dụng Rails.cache và lớp được lưu trữ là khác nhau hơn mới. – iwiznia

+0

Tôi xin lỗi về việc chấp nhận muộn. Câu trả lời rất hay, đánh giá cao việc bạn dành thời gian. – Jesper

Các vấn đề liên quan