AssociationTypeMismatch: RoleType expected, got RoleType

I am experiencing strange problems with a HABTM relationship in my application.

The two models concerned are Role and RoleType. When attempting to set the RoleTypes on a Role within my controllers I receive an error with the attached stack trace (see bottom).

Unfortunately I am unable to reproduce this error with a unit test, or a functional test. I've even tried creating a new project to test the exact bit of code, and still had no success reproducing the error. I've also checked out the code on another computer and run up against the same problems. This leads me to believe that the problem lies somewhere I haven't considered (i.e. outside the models / controllers / migrations etc).

I'm basically looking for any pointers anyone might be able to offer me. I've spent two and a half days (and counting) banging my head against the wall on this one. I'm aware that this problem appears similar to the below link but since there is no response to that thread, and since the stack trace is quite different I thought I'd try a post of my own.

http://groups.google.com/group/rubyonrails-talk/browse_thread/thread/149262683b0c078f/f1c81a1f756226d1?lnk=gst&q=AssociationTypeMismatch+&rnum=1#f1c81a1f756226d1

I've simplified the code to attempt to isolate the issue; the specific bit of code I'm trying to run is:

    role = Role.find(1)     role_type = RoleType.find(1)     role.role_types = [role_type]

The exception is raised on the last line as I try to assign the list of RoleTypes to the Role.

I'm running Lighttpd under XP with Rails 1.1.6 and Ruby 1.8.5

Thanks in advance for any assistance!

Hugh

My Role model:

class Role < ActiveRecord::Base   has_and_belongs_to_many :role_types   has_and_belongs_to_many :users   has_many :permissions

  ROLE_TYPE_REVIEWER = 'reviewer'   ROLE_TYPE_CONCLUSION_REVIEWER = 'conclusion_reviewer'   ROLE_TYPE_COMMUNITY_REVIEWER = 'community_reviewer'   ROLE_TYPE_INTEREST_TAG_REVIEWER = 'interest_tag_reviewer'   ROLE_TYPE_BATCH_ADMINISTRATOR = 'batch_administrator'   ROLE_TYPE_USER_ADMINISTRATOR = 'user_administrator'   ROLE_TYPE_USER_GROUP_ADMINISTRATOR = 'user_group_administrator'   ROLE_TYPE_CITIZEN_ADMINISTRATOR = 'citizen_administrator'   ROLE_TYPE_INTEREST_TAG_ADMINISTRATOR = 'interest_tag_administrator'   ROLE_TYPE_COMMUNITY_ADMINISTRATOR = 'community_administrator'   ROLE_TYPE_ROLES_ADMINISTRATOR = 'role_administrator'   ROLE_TYPE_QUESTIONS_ADMINISTRATOR = 'question_administrator'

  ROLE_TYPE_INTEREST_TAG_PROPOSER = 'interest_tag_proposer'   ROLE_TYPE_COMMUNITY_PROPOSER = 'community_proposer'   ROLE_TYPE_QUESTIONS_PROPOSER = 'question_proposer'

  ROLE_TYPES = {     ROLE_TYPE_REVIEWER => 'Reviewer',     ROLE_TYPE_CONCLUSION_REVIEWER => 'Conclusion Reviewer',     ROLE_TYPE_COMMUNITY_REVIEWER => 'Community Reviewer',     ROLE_TYPE_INTEREST_TAG_REVIEWER => 'Interest Tag Reviewer',     ROLE_TYPE_BATCH_ADMINISTRATOR => 'Batch Administrator',     ROLE_TYPE_USER_ADMINISTRATOR => 'User Administrator',     ROLE_TYPE_USER_GROUP_ADMINISTRATOR => 'User Group Administrator',     ROLE_TYPE_CITIZEN_ADMINISTRATOR => 'Citizen Administrator',     ROLE_TYPE_INTEREST_TAG_ADMINISTRATOR => 'Interest Tag Administrator',     ROLE_TYPE_COMMUNITY_ADMINISTRATOR => 'Community Administrator',     ROLE_TYPE_ROLES_ADMINISTRATOR => 'Role Administrator',     ROLE_TYPE_QUESTIONS_ADMINISTRATOR => 'Question Administrator',     ROLE_TYPE_INTEREST_TAG_PROPOSER => 'Interest Tag Proposer',     ROLE_TYPE_COMMUNITY_PROPOSER => 'Community Proposer',     ROLE_TYPE_QUESTIONS_PROPOSER => 'Question Proposer'   }   ROLE_NAMES = [     ROLE_TYPE_REVIEWER,     ROLE_TYPE_CONCLUSION_REVIEWER,     ROLE_TYPE_COMMUNITY_REVIEWER,     ROLE_TYPE_INTEREST_TAG_REVIEWER,     ROLE_TYPE_BATCH_ADMINISTRATOR,     ROLE_TYPE_USER_ADMINISTRATOR,     ROLE_TYPE_USER_GROUP_ADMINISTRATOR,     ROLE_TYPE_CITIZEN_ADMINISTRATOR,     ROLE_TYPE_INTEREST_TAG_ADMINISTRATOR,     ROLE_TYPE_COMMUNITY_ADMINISTRATOR,     ROLE_TYPE_ROLES_ADMINISTRATOR,     ROLE_TYPE_QUESTIONS_ADMINISTRATOR,     ROLE_TYPE_INTEREST_TAG_PROPOSER,     ROLE_TYPE_COMMUNITY_PROPOSER,     ROLE_TYPE_QUESTIONS_PROPOSER   ]

  CONTROLLERS = {     :batches => BatchesController.public_instance_methods(false),     :citizens => CitizensController.public_instance_methods(false),     :communities => CommunitiesController.public_instance_methods(false),     :interest_tags => InterestTagsController.public_instance_methods(false),     :login => LoginController.public_instance_methods(false),     :questions => QuestionsController.public_instance_methods(false),     :roles => RolesController.public_instance_methods(false),     :user_groups => UserGroupsController.public_instance_methods(false),     :users => UsersController.public_instance_methods(false),     :welcome => WelcomeController.public_instance_methods(false)   }

  def alter_permissions(values)     result = self     if values       # delete all current permissions and create new ones       self.permissions.each { |permission|         permission.destroy       }       values.each {|value|         value_split = value.split('-')         self.permissions << Permission.new(:controller => value_split[0], :page_action => value_split[1])       }       result = self.save!     end     return result   end end

My Role migration:

class CreateRoles < ActiveRecord::Migration   def self.up     create_table :roles do |t|       t.column :name, :string       t.column :restricted, :boolean, :null => false, :default => false     end     Role.create(:name => 'Role 1')   end

  def self.down     drop_table :roles   end end

My RoleType model:

class RoleType < ActiveRecord::Base   has_and_belongs_to_many :roles end

My RoleType migration:

class CreateRoleTypes < ActiveRecord::Migration   def self.up     create_table :role_types do |t|       t.column :name, :string     end     RoleType.create(:name => 'Role 1')   end

  def self.down     drop_table :role_types   end end

The exception's stack trace:

ActiveRecord::AssociationTypeMismatch (RoleType expected, got RoleType):     h:/ruby/lib/ruby/gems/1.8/gems/activerecord-1.15.1/lib/ active_record/associations/association_proxy.rb:148:in `raise_on_type_mismatch'     h:/ruby/lib/ruby/gems/1.8/gems/activerecord-1.15.1/lib/ active_record/associations/association_collection.rb:137:in `replace'     h:/ruby/lib/ruby/gems/1.8/gems/activerecord-1.15.1/lib/ active_record/associations/association_collection.rb:137:in `each'     h:/ruby/lib/ruby/gems/1.8/gems/activerecord-1.15.1/lib/ active_record/associations/association_collection.rb:137:in `replace'     h:/ruby/lib/ruby/gems/1.8/gems/activerecord-1.15.1/lib/ active_record/associations.rb:944:in `role_types='     h:/ruby/lib/ruby/gems/1.8/gems/activerecord-1.15.1/lib/ active_record/associations.rb:954:in `send'     h:/ruby/lib/ruby/gems/1.8/gems/activerecord-1.15.1/lib/ active_record/associations.rb:954:in `role_type_ids='     /app/controllers/roles_controller.rb:50:in `update'     h:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.13.1/lib/ action_controller/base.rb:1095:in `send'     h:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.13.1/lib/ action_controller/base.rb:1095:in `perform_action_without_filters'     h:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.13.1/lib/ action_controller/filters.rb:632:in `call_filter'     h:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.13.1/lib/ action_controller/filters.rb:638:in `call_filter'     h:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.13.1/lib/ action_controller/filters.rb:438:in `call'     h:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.13.1/lib/ action_controller/filters.rb:637:in `call_filter'     h:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.13.1/lib/ action_controller/filters.rb:638:in `call_filter'     h:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.13.1/lib/ action_controller/filters.rb:438:in `call'     h:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.13.1/lib/ action_controller/filters.rb:637:in `call_filter'     h:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.13.1/lib/ action_controller/filters.rb:638:in `call_filter'     h:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.13.1/lib/ action_controller/filters.rb:438:in `call'     h:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.13.1/lib/ action_controller/filters.rb:637:in `call_filter'     h:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.13.1/lib/ action_controller/filters.rb:619:in `perform_action_without_benchmark'     h:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.13.1/lib/ action_controller/benchmarking.rb:66:in `perform_action_without_rescue'     h:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure'     h:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.13.1/lib/ action_controller/benchmarking.rb:66:in `perform_action_without_rescue'     h:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.13.1/lib/ action_controller/rescue.rb:83:in `perform_action'     h:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.13.1/lib/ action_controller/base.rb:430:in `send'     h:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.13.1/lib/ action_controller/base.rb:430:in `process_without_filters'     h:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.13.1/lib/ action_controller/filters.rb:624:in `process_without_session_management_support'     h:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.13.1/lib/ action_controller/session_management.rb:114:in `process'     h:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.13.1/lib/ action_controller/base.rb:330:in `process'     h:/ruby/lib/ruby/gems/1.8/gems/rails-1.2.1/lib/dispatcher.rb:41:in `dispatch'     h:/ruby/lib/ruby/gems/1.8/gems/scgi_rails-0.4.3/bin/scgi_service: 23:in `process_request'     h:/ruby/lib/ruby/1.8/thread.rb:135:in `synchronize'     h:/ruby/lib/ruby/gems/1.8/gems/scgi_rails-0.4.3/bin/scgi_service: 21:in `process_request'     h:/ruby/lib/ruby/gems/1.8/gems/scgi_rails-0.4.3/lib/scgi.rb:291:in `read_header'     h:/ruby/lib/ruby/gems/1.8/gems/scgi_rails-0.4.3/lib/scgi.rb:253:in `handle_client'     h:/ruby/lib/ruby/gems/1.8/gems/scgi_rails-0.4.3/lib/scgi.rb:234:in `initialize'     h:/ruby/lib/ruby/gems/1.8/gems/scgi_rails-0.4.3/lib/scgi.rb:234:in `new'     h:/ruby/lib/ruby/gems/1.8/gems/scgi_rails-0.4.3/lib/scgi.rb:234:in `handle_client'     h:/ruby/lib/ruby/gems/1.8/gems/scgi_rails-0.4.3/lib/scgi.rb:188:in `listen'     h:/ruby/lib/ruby/gems/1.8/gems/scgi_rails-0.4.3/lib/scgi.rb:186:in `initialize'     h:/ruby/lib/ruby/gems/1.8/gems/scgi_rails-0.4.3/lib/scgi.rb:186:in `new'     h:/ruby/lib/ruby/gems/1.8/gems/scgi_rails-0.4.3/lib/scgi.rb:186:in `listen'     h:/ruby/lib/ruby/gems/1.8/gems/scgi_rails-0.4.3/lib/scgi.rb:412:in `run'     h:/ruby/lib/ruby/gems/1.8/gems/scgi_rails-0.4.3/bin/scgi_service: 61     h:/ruby/bin/scgi_service:18:in `load'     h:/ruby/bin/scgi_service:18

Elad,

Using:

    @role.role_types << RoleType.find(1)

instead of

    @role.role_types = [RoleType.find(1)]

gives exactly the same error. Thanks for the suggestion though!

I do not see it. You are going to have to look at the source of associations.rb and association_proxy.rb to see what they are doing.

Michael

I am having this exactly this problem as well. I will follow Michael Latta's advice and poke around in the rails code.

The first time I encountered it I was trying to delete a relationship. I worked around the problem by changing

    role = Role.find(1) # Already has association with RoleType obj with id 1.     role_type = RoleType.find(1)     role.role_types.delete role_type

to

    role = Role.find(1) # Already has association with RoleType obj with id 1.     for role_type in role.role_types       if role_type.id == params[:roll_type.id].to_i         role.role_type.delete tc         break       end     end

Kloogey, I know, but I just wanted to get past the exception. The workaround's not going to help me when adding a relationship, though.

Oops. Error in the second code snippet. The line "role.role_type.delete tc" should be "role.role_types.delete role_type".

I have had a bit of a look in association_proxy, and can not see why, but it appears that line 147 is misbehaving.

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

If I stick this debug in before 147:           logger.debug "Reflection:#{@reflection.klass.inspect}"           logger.debug "Record:#{record.class.inspect}"

I get: Reflection:RoleType Record:RoleType

out in the log, which looks correct to me. If I hack the raising of the exception out, then this action works fine. I haven't checked it's effect on the rest of the app, since it doesn't sound like a good solution to me.

The most perculiar thing (which I have only just noticed) is that if I resubmit a request that has failed immediately after restarting Lighttpd, it works! So it looks like it's something to do with the initialisation of classes going wrong or something.

This rings true with the fact that I get warnings over constants already being initialised when I run roles_controller_test.rb, or is that normal for a functional test?

Perhaps that sheds some light on the situation...

Cheers, Hugh

In my application, the classes have different object id's, that's why the is_a? method returns false. This leads to another workaround, where you change the test in association_proxy.rb, line 147 from

    unless record.is_a?(@reflection.klass)

to

    if record.class.name != @reflection.klass.name

which would be less dangerous that commenting out the assertion entirely.

I also made the assertion go away by inserting 'require role_type' in the class definition of an object of global scope that is instantiated by environment.rb. I'm sure it's bad form to have an object of global scope that is instantiated by environment.rb; I do plan to fix that.

I knew to try adding a require because I've had trouble with Rails unloading classes. I haven't investigating what Rails does along those lines, but I only had the problem when running the web application, not in the test environment. I'd try to instantiate one of my models and I would get an exception along the lines of "<model> was part of the tree but was unloaded".

My conjecture is that the RoleType class object that was used to set @reflection.klass was subsequently unloaded, and then somehow in the magic of Rails a new RoleType class object came into being, and everything is actually fine, but those circumstances lead to the exception being raised.

I'm fine with my solution for now, though at some point I'll need to actually understand what Rails is doing with unloading and loading the model classes.

F

Frederick

You're exactly right, I hadn't thought to look at object_ids but they are indeed different. It seems that @reflection.klass.object_id remains the same between requests, but record.class.object_id changes. At first I thought it was @reflection that was at fault, but after reviewing the same debug performing another operation between two models with a HABTM relationship I now see that the object_id of the record's class should be the same.

So it sounds exactly like Rails is for some reason reloading the class on every request for the record object.

A minute ago, after reading your tip about requiring the class, I added the line

    require File.dirname(__FILE__)+'/role_type.rb'

to role.rb, since I knew that that model was behaving properly. And hey presto, it worked! So for now I have a workaround what I consider to be a rails bug. And, I'm very happy to be able to move on after 4 days of head banging.

Many thanks for your help Frederick... I think I might go on to file a bug report now.

Hugh

Guys,

I had the exact same problem with Rails 1.2.3…have been banging my head on the desk for a few hours and just found your post. I appreciate you documenting the fix.

Has this been filed as a bug report?

Thanks,

Jake

Hi All,

I also came across this issue, with the message "User expected, got User", which is a very helpful msg of rails of course.. :wink:

Anyways, there was a ticket for this, so more info is to be found here:

http://dev.rubyonrails.org/ticket/8246

Note that I added Michael's patch. +1 it to get it into core.

Cheers, Lawrence

ActiveRecord::AssociationTypeMismatch in ClistingsController#create

Category(#-612714068) expected, got String(#-607696388)

Rails.root: /home/gbolahan/sites/spotvilla Application Trace | Framework Trace | Full Trace

app/controllers/clistings_controller.rb:45:in `new' app/controllers/clistings_controller.rb:45:in `create'

i have implemented collection select but i intend to create a new new model with my Clisting model. how do i get around this error.

Posting some of the code around clistings_controller.rb line 45 would help. (although you might find you're doing this:   clisting.category = params[:clisting][:category] rather than:   clisting.category = Category.find_or_create_by_name(params[:clisting][:category]) or   clisting.category_id = params[:clisting][:category] or some other error relating to passing the Category association a string... assuming your Clisting has an association to Category...)

let me post some code around the Clisting controller

def create     @categories = Category.all     @clisting = Clisting.new(params[:clisting])

    respond_to do |format|       if @clisting.save         format.html { redirect_to @clisting, :notice => 'Clisting was successfully created.' }         format.json { render :json => @clisting,:status => created, :location => @clisting }       else

Like I said, you're likely to be passing a params[:clisting][:category] value (which you probably should have named "category_id" in your form) into your new Clisting. The clisting expects a Category object in the category association, not a string from params.

Do some debugging in this area, and see if that's part of the problem.

undefined method `model_name' for NilClass:Class

Extracted source (around line #1):

1: <%= form_for(@category) do |f| %> 2: <% if @category.errors.any? %> 3: <div id="error_explanation"> 4: <h2><%= pluralize(@category.errors.count, "error") %> prohibited this category from being saved:</h2>

this is another error i encounterd while trying to create a new category in my category model.

can someone pls help out on this

undefined method `model_name' for NilClass:Class

Extracted source (around line #1):

1: <%= form_for(@category) do |f| %> 2: <% if @category.errors.any? %> 3: <div id="error_explanation"> 4: <h2><%= pluralize(@category.errors.count, "error") %> prohibited this category from being saved:</h2>

this is another error i encounterd while trying to create a new category in my category model.

can someone pls help out on this

undefined method `model_name' for NilClass:Class

Extracted source (around line #1):

1: <%= form_for(@category) do |f| %>

this is another error i encounterd while trying to create a new category in my category model.

If it's a different problem, please start a different thread. (this thread would be a good place for an update on your last problem.... *hint*)

can someone pls help out on this

It pretty much tells you all you need to know - @category is most likely nil.

undefined method `model_name' for NilClass:Class

Extracted source (around line #1):

1: <%= form_for(@category) do |f| %>

As I think I mentioned in a previous mail, if you see 'nil' in an error message it quite likely indicates that something is nil. Since the only variable on that line is @category I think there is a strong possibility that it is nil. The reason for that particular error is that it is trying to find the name of the model that @category is, but as it is nil the error is shown.

Colin