Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Ticket #8246 (closed defect: untested)

Opened 2 years ago

Last modified 1 year ago

[PATCH] raise_on_type_mismatch on has_many :through

Reported by: jbwiv Assigned to: core
Priority: high Milestone: 1.2.7
Component: ActiveRecord Version: 1.2.3
Severity: major Keywords: verified, test, patch, raise_on_type_mismatch has_many :through
Cc: mudiarto@gmail.com, tarmo

Description

Having a strange error with the latest version of Rails (1.2.3). This did not exist in older versions. I have searched the lists and verified that this is happening to others, so I thought it prudent to report.

I have user class defined as such:

class User < ActiveRecord::Base

before_create :make_activation_code

has_many :taggings has_many :tags, :through => :taggings, :select => "DISTINCT tags.*"

<snip/> end

It uses DHH's acts_as_taggable (not the other one).

Anyway, tags are identified as:

class Tag < ActiveRecord::Base

# use for sorting...so we don't have to select each time # this is solely used in tag_cloud now attr_accessor :count_cache has_many :taggings

<snip/> end

and Taggings: class Tagging < ActiveRecord::Base

belongs_to :tag belongs_to :taggable, :polymorphic => true belongs_to :user

<snip/> end

Now, this used to work just fine, but if I now try to tag something I get the following:

ActiveRecord::AssociationTypeMismatch (User expected, got User):

/usr/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/associations/association_proxy.rb:148:in `raise_on_type_mismatch' /usr/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/associations/belongs_to_association.rb:22:in `replace' /usr/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/associations.rb:908:in `user=' /usr/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/base.rb:1672:in `send' /usr/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/base.rb:1672:in `attributes=' /usr/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/base.rb:1671:in `each' /usr/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/base.rb:1671:in `attributes=' /usr/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/base.rb:1505:in `initialize_without_callbacks' /usr/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/callbacks.rb:225:in `initialize' /usr/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/associations/has_many_association.rb:13:in `new' /usr/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/associations/has_many_association.rb:13:in `build' /usr/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/associations/association_collection.rb:93:in `create' .//vendor/plugins/acts_as_taggable/lib/tag.rb:56:in `on' .//vendor/plugins/acts_as_taggable/lib/acts_as_taggable.rb:46:in `_tag_with' .//vendor/plugins/acts_as_taggable/lib/acts_as_taggable.rb:42:in `each' .//vendor/plugins/acts_as_taggable/lib/acts_as_taggable.rb:42:in `_tag_with' /usr/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/connection_adapters/abstract/database_statements.rb:59:in `transaction' /usr/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/transactions.rb:95:in `transaction' .//vendor/plugins/acts_as_taggable/lib/acts_as_taggable.rb:40:in `_tag_with' .//lib/acts_as_taggable_extension.rb:11:in `tag_with' .//app/controllers/task_controller.rb:24:in `add_task' /usr/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/connection_adapters/abstract/database_statements.rb:59:in `transaction' /usr/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/transactions.rb:95:in `transaction' .//app/controllers/task_controller.rb:21:in `add_task' /usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/base.rb:1095:in `send' /usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/base.rb:1095:in `perform_action_without_filters' /usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:632:in `call_filter' /usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:638:in `call_filter' /usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:438:in `call' /usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:637:in `call_filter' /usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:619:in `perform_action_without_benchmark' /usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/benchmarking.rb:66:in `perform_action_without_rescue' /usr/lib/ruby/1.8/benchmark.rb:293:in `measure' /usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/benchmarking.rb:66:in `perform_action_without_rescue' /usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/rescue.rb:83:in `perform_action' /usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/base.rb:430:in `send' /usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/base.rb:430:in `process_without_filters' /usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/filters.rb:624:in `process_without_session_management_support' /usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/session_management.rb:114:in `process' /usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/base.rb:330:in `process' /usr/lib/ruby/gems/1.8/gems/rails-1.2.3/lib/dispatcher.rb:41:in `dispatch' /usr/lib/ruby/gems/1.8/gems/mongrel-1.0.1/lib/mongrel/rails.rb:78:in `process' /usr/lib/ruby/gems/1.8/gems/mongrel-1.0.1/lib/mongrel/rails.rb:76:in `synchronize' /usr/lib/ruby/gems/1.8/gems/mongrel-1.0.1/lib/mongrel/rails.rb:76:in `process' /usr/lib/ruby/gems/1.8/gems/mongrel-1.0.1/lib/mongrel.rb:618:in `process_client' /usr/lib/ruby/gems/1.8/gems/mongrel-1.0.1/lib/mongrel.rb:617:in `each' /usr/lib/ruby/gems/1.8/gems/mongrel-1.0.1/lib/mongrel.rb:617:in `process_client' /usr/lib/ruby/gems/1.8/gems/mongrel-1.0.1/lib/mongrel.rb:736:in `run' /usr/lib/ruby/gems/1.8/gems/mongrel-1.0.1/lib/mongrel.rb:736:in `initialize' /usr/lib/ruby/gems/1.8/gems/mongrel-1.0.1/lib/mongrel.rb:736:in `new' /usr/lib/ruby/gems/1.8/gems/mongrel-1.0.1/lib/mongrel.rb:736:in `run' /usr/lib/ruby/gems/1.8/gems/mongrel-1.0.1/lib/mongrel.rb:720:in `initialize' /usr/lib/ruby/gems/1.8/gems/mongrel-1.0.1/lib/mongrel.rb:720:in `new' /usr/lib/ruby/gems/1.8/gems/mongrel-1.0.1/lib/mongrel.rb:720:in `run' /usr/lib/ruby/gems/1.8/gems/mongrel-1.0.1/lib/mongrel/configurator.rb:271:in `run' /usr/lib/ruby/gems/1.8/gems/mongrel-1.0.1/lib/mongrel/configurator.rb:270:in `each' /usr/lib/ruby/gems/1.8/gems/mongrel-1.0.1/lib/mongrel/configurator.rb:270:in `run' /usr/lib/ruby/gems/1.8/gems/mongrel-1.0.1/bin/mongrel_rails:127:in `run' /usr/lib/ruby/gems/1.8/gems/mongrel-1.0.1/lib/mongrel/command.rb:211:in `run' /usr/lib/ruby/gems/1.8/gems/mongrel-1.0.1/bin/mongrel_rails:243 /usr/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:488:in `load' /usr/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:488:in `load' /usr/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:342:in `new_constants_in' /usr/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:488:in `load' /usr/lib/ruby/gems/1.8/gems/rails-1.2.3/lib/commands/servers/mongrel.rb:60 /usr/local/lib/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' /usr/local/lib/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' /usr/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:495:in `require' /usr/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:342:in `new_constants_in' /usr/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:495:in `require' /usr/lib/ruby/gems/1.8/gems/rails-1.2.3/lib/commands/server.rb:39 /usr/local/lib/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' /usr/local/lib/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' /usr/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:495:in `require' /usr/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:342:in `new_constants_in' /usr/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/dependencies.rb:495:in `require' script/server:3

Note, it WORKS on first boot of mongrel (i.e., first time I try to tag something), but on subsequent it does not. I can restart the server and it will work again, once.

The problem comes from the association_proxy.rb file, as indicated. Specifically, it comes from:

146 def raise_on_type_mismatch(record) 147 unless record.is_a?(@reflection.klass) 148 raise ActiveRecord::AssociationTypeMismatch, "#{@reflection.class_name} expected, got #{record.class}" 149 end 150 end

I've traced this with ruby-debug, and when the User class is referenced here, record.class.object_id is indeed different than @reflection.klass.object_id, which causes the test to fail. This should not happen, and with my other classes they are still the same. It's only with this class, and I suspect it's because this is the only class I'm using has_many :through.

I've fixed it temporarily by comparing names in raise_type_on_mismatch instead of object_ids, but this is obviously suboptimal. Would someone please advise what might be causing this?

Thanks, JB

Attachments

type_mismatch.patch (2.5 kB) - added by lawrence on 05/21/08 01:31:56.

Change History

05/15/07 09:02:07 changed by mudiarto

  • cc set to mudiarto@gmail.com.

07/03/07 00:35:48 changed by tarmo

  • cc changed from mudiarto@gmail.com to mudiarto@gmail.com, tarmo.

Does this only happen in development mode?

When rails reloads itself in development mode then indeed the object_id for the reloaded classes changes, but in this case it would appear that User gets reloaded after the tag thingy gets a reference to the "old" User.

Does this error dissapear if in the beginning of the action that causes the error you reference the User class?

Could you try printing the User.object_id out at the beginning of the action and at the end of the action, does it change then?

07/22/07 11:30:07 changed by wildchild

I have the same problem. All works in production environment, but development scaring me. I think it's very important ticket, cuze bug like this makes impossible rails development.

07/22/07 12:25:27 changed by wildchild

Oh yeahhh I have a solution: If your plugins have references to Models,etc (those classes reloadable by each request in development mode) add folowing lines of code to environment.rb

# Array of plugins with Application model dependencies. reloadable_plugins = ['acts_as_taggable', 'acts_as_rateable', 'acts_as_commentable']

# Force these plugins to reload, avoiding stale object references. reloadable_plugins.each do |plugin_name|

reloadable_path = RAILS_ROOT + "/vendor/plugins/#{plugin_name}/lib" Dependencies.load_once_paths.delete(reloadable_path)

end

It will reload specified plugins. Good luck.

09/04/07 20:58:42 changed by moorage

If you are using dev_mode_performance_fixes, this bug may also apply to you. We disabled (deleted) this plugin, and stopped receiving these errors.

09/14/07 05:58:04 changed by matijs

Another data point: This error also occurs with a belongs_to relationship (again, in development mode):

  class Foo << AR::B; belongs_to :bar; end

I'm loading the Foo class in config/environment.rb (to set some configuration). As a consequence, it is only loaded that one time. The Bar class is of course loaded on every request. The result is again that Foo has stale data in its AssociationProxy.

10/08/07 00:38:11 changed by bitsweat

  • status changed from new to closed.
  • resolution set to invalid.
config.to_prepare { require_dependency 'foo' }

10/08/07 01:07:55 changed by jbwiv

  • status changed from closed to reopened.
  • resolution deleted.

bitsweat...could you provide a bit more details if it's truly resolved? I'm not sure I completely understand "config.to_prepare { require_dependency 'foo' }" in the context of this ticket.

Thanks!

10/08/07 02:08:32 changed by bitsweat

It ensures the models you're working with are reloaded on each request.

11/26/07 02:07:42 changed by lawrence

  • summary changed from raise_on_type_mismatch on has_many :through to [PATCH] raise_on_type_mismatch on has_many :through.

See also all the reports at

http://groups.google.com/group/rubyonrails-talk/browse_thread/thread/d2adc451ff0236c2/b95a3bc9cb97f988?#b95a3bc9cb97f988

The method raise_on_type_mismatch in association_proxy.rb should only check for matching type, not for matching object as it does now.

Michael Bryzek gave a solution that works.

Attached is the patch.

11/26/07 02:08:12 changed by lawrence

  • keywords changed from raise_on_type_mismatch has_many :through to patch, raise_on_type_mismatch has_many :through.

02/11/08 14:58:34 changed by willcodeforfoo

+1, this one line patch fixed the annoying ActiveRecord::AssociationTypeMismatch exception with the frustrating "Model expected, got Model" issue in development for me even in r8820

02/15/08 01:26:42 changed by kamal

+1 happens to me on recent Edge Rails

03/17/08 10:25:03 changed by m.kristian

+1 after an morning trying to understand my "Language expected, got Language" problem, I hope to see the patch applied soon

03/18/08 01:01:10 changed by lawrence

  • keywords changed from patch, raise_on_type_mismatch has_many :through to verified, patch, raise_on_type_mismatch has_many :through.

03/28/08 21:21:02 changed by bitsweat

  • status changed from reopened to closed.
  • resolution set to untested.

Needs a test demonstrating that it's fixed.

04/22/08 21:39:22 changed by rogerdpack

I get this if I have one class that 'overrides' another. I.e. class Image # in a plugin end class Image # in app/models -- meant to 'override' the use of the plugin image. end

The error message seems very misleading Image expected, got Image. Ugh. A better way would be Image [id XXX] expected, got Image [id YYY]

05/02/08 01:26:08 changed by rogerdpack

another way to fix it is: module ActiveRecord

module Associations

class AssociationProxy #:nodoc:

private

def raise_on_type_mismatch(record)

unless record.class == @reflection.klass or record.class.ancestors.include?(@reflection.klass)

raise ActiveRecord::AssociationTypeMismatch, "#{@reflection.class_name} ID #{@reflection.id} expected, got #{record.class} ID #{record.id}"

end

end

end

end

end

Not sure which one would be faster or what not, esp. with 1.9

05/02/08 22:13:27 changed by bloodroot

+1, I get this on edge as well

05/21/08 01:31:56 changed by lawrence

  • attachment type_mismatch.patch added.

05/21/08 01:34:24 changed by lawrence

  • keywords changed from verified, patch, raise_on_type_mismatch has_many :through to verified, test, patch, raise_on_type_mismatch has_many :through.

@rogerdpack you're suggestion doesn't work unfortunately, try it out and several tests will fail.

Attached new patch. Now including test to prove this thing.

05/21/08 01:55:36 changed by lawrence