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