rails: instance.destroy leads to undefined method `to_sym' for nil:NilClass

Sample model structure:

class Base::Role < ActiveRecord::Base
  self.table_name = 'roles'
  self.abstract_class = true
end

class Base::Power < ActiveRecord::Base
  self.abstract_class = true
end

class Base::Organization < ActiveRecord::Base
  self.abstract_class = true
end

class Power < Base::Power
self.primary_key = 'id'

has_and_belongs_to_many :roles, class_name: 'Role'
end

class Role < Base::Role
  has_and_belongs_to_many :powers, class_name: 'Power'
  belongs_to :organization, class_name: 'Organization'
end

class Organization < Base::Organization
  self.primary_key = 'id'

  has_many :roles, class_name: 'Role'
end

class Mod::Power < Power; end
class Mod::Organization < Organization; end
class Mod::Role < Role; end

Doing Mod::Role(id).destroy works like a charm. However, this does not work:

Mod::Role.includes(:powers).where(organization_id: 'ORG_00001').find(id).destroy

Causes this NoMethodError:

NoMethodError: undefined method `to_sym' for nil:NilClass
    from /Users/user/.rvm/gems/ruby-2.1.0@mpx-um-app/gems/activerecord-4.1.0.beta1/lib/active_record/sanitization.rb:58:in `block in expand_hash_conditions_for_aggregates'
    from /Users/user/.rvm/gems/ruby-2.1.0@mpx-um-app/gems/activerecord-4.1.0.beta1/lib/active_record/sanitization.rb:57:in `each'
    from /Users/user/.rvm/gems/ruby-2.1.0@mpx-um-app/gems/activerecord-4.1.0.beta1/lib/active_record/sanitization.rb:57:in `expand_hash_conditions_for_aggregates'
    from /Users/user/.rvm/gems/ruby-2.1.0@mpx-um-app/gems/activerecord-4.1.0.beta1/lib/active_record/relation/query_methods.rb:923:in `build_where'
    from /Users/user/.rvm/gems/ruby-2.1.0@mpx-um-app/gems/activerecord-4.1.0.beta1/lib/active_record/relation/query_methods.rb:555:in `where!'
    from /Users/user/.rvm/gems/ruby-2.1.0@mpx-um-app/gems/activerecord-4.1.0.beta1/lib/active_record/relation/query_methods.rb:545:in `where'
    from /Users/user/.rvm/gems/ruby-2.1.0@mpx-um-app/gems/activerecord-4.1.0.beta1/lib/active_record/associations/has_many_association.rb:117:in `delete_records'
    from /Users/user/.rvm/gems/ruby-2.1.0@mpx-um-app/gems/activerecord-4.1.0.beta1/lib/active_record/associations/collection_association.rb:472:in `remove_records'
    from /Users/user/.rvm/gems/ruby-2.1.0@mpx-um-app/gems/activerecord-4.1.0.beta1/lib/active_record/associations/collection_association.rb:465:in `block in delete_or_destroy'
    from /Users/user/.rvm/gems/ruby-2.1.0@mpx-um-app/gems/activerecord-4.1.0.beta1/lib/active_record/associations/collection_association.rb:148:in `block in transaction'
    from /Users/user/.rvm/gems/ruby-2.1.0@mpx-um-app/gems/activerecord-4.1.0.beta1/lib/active_record/connection_adapters/abstract/database_statements.rb:198:in `transaction'
    from /Users/user/.rvm/gems/ruby-2.1.0@mpx-um-app/gems/activerecord-4.1.0.beta1/lib/active_record/transactions.rb:211:in `transaction'
    from /Users/user/.rvm/gems/ruby-2.1.0@mpx-um-app/gems/activerecord-4.1.0.beta1/lib/active_record/associations/collection_association.rb:147:in `transaction'
    from /Users/user/.rvm/gems/ruby-2.1.0@mpx-um-app/gems/activerecord-4.1.0.beta1/lib/active_record/associations/collection_association.rb:465:in `delete_or_destroy'
    from /Users/user/.rvm/gems/ruby-2.1.0@mpx-um-app/gems/activerecord-4.1.0.beta1/lib/active_record/associations/collection_association.rb:233:in `delete'
    from /Users/user/.rvm/gems/ruby-2.1.0@mpx-um-app/gems/activerecord-4.1.0.beta1/lib/active_record/associations/collection_association.rb:178:in `delete_all'
... 11 levels.user
    from /Users/user/.rvm/gems/ruby-2.1.0@mpx-um-app/gems/activerecord-4.1.0.beta1/lib/active_record/transactions.rb:267:in `block in destroy'
    from /Users/user/.rvm/gems/ruby-2.1.0@mpx-um-app/gems/activerecord-4.1.0.beta1/lib/active_record/transactions.rb:329:in `block in with_transaction_returning_status'
    from /Users/user/.rvm/gems/ruby-2.1.0@mpx-um-app/gems/activerecord-4.1.0.beta1/lib/active_record/connection_adapters/abstract/database_statements.rb:200:in `block in transaction'
    from /Users/user/.rvm/gems/ruby-2.1.0@mpx-um-app/gems/activerecord-4.1.0.beta1/lib/active_record/connection_adapters/abstract/database_statements.rb:208:in `within_new_transaction'
    from /Users/user/.rvm/gems/ruby-2.1.0@mpx-um-app/gems/activerecord-4.1.0.beta1/lib/active_record/connection_adapters/abstract/database_statements.rb:200:in `transaction'
    from /Users/user/.rvm/gems/ruby-2.1.0@mpx-um-app/gems/activerecord-4.1.0.beta1/lib/active_record/transactions.rb:211:in `transaction'
    from /Users/user/.rvm/gems/ruby-2.1.0@mpx-um-app/gems/activerecord-4.1.0.beta1/lib/active_record/transactions.rb:326:in `with_transaction_returning_status'
    from /Users/user/.rvm/gems/ruby-2.1.0@mpx-um-app/gems/activerecord-4.1.0.beta1/lib/active_record/transactions.rb:267:in `destroy'

I tried debugging the problem and got into AR internals. This is the problem:

From: /Users/davidhorsak/.rvm/gems/ruby-2.1.0@mpx-um-app/gems/activerecord-4.1.0.beta1/lib/active_record/sanitization.rb @ line 58 ActiveRecord::Sanitization::ClassMethods#expand_hash_conditions_for_aggregates:

    55: def expand_hash_conditions_for_aggregates(attrs)
    56:   expanded_attrs = {}
    57:   attrs.each do |attr, value|
 => 58:     if aggregation = reflect_on_aggregation(attr.to_sym)
    59:       mapping = aggregation.mapping
    60:       mapping.each do |field_attr, aggregate_attr|
    61:         if mapping.size == 1 && !value.respond_to?(aggregate_attr)
    62:           expanded_attrs[field_attr] = value
    63:         else
    64:           expanded_attrs[field_attr] = value.send(aggregate_attr)
    65:         end
    66:       end
    67:     else
    68:       expanded_attrs[attr] = value
    69:     end
    70:   end
    71:   expanded_attrs
    72: end

[61] pry(HABTM_Powers)> attrs
=> {nil=>[#<#<Class:0x00000108b03110> power_id: "af_index_groups", role_id: 8>]}

As seen form the backtrace, we are running Ruby 2.1 and Rails 4.1.0.beta. This problem is not present on Rails 4.0.2.

I tried making a small program where I would replicate the problem on the mentioned sample structure but failed. Let me know if I can provide more info on this topic.

About this issue

  • Original URL
  • State: closed
  • Created 10 years ago
  • Comments: 18 (6 by maintainers)

Most upvoted comments

@sgrif sorry for the confusion here!

I wasn’t trying to make anything happen or request any changes - was merely posting our solution in the event of someone searching Google and coming along this issue (like I had).

I’ll adjust my comment to be more specific.

Thanks for your hard work on Rails!

It seems that Active Record expects a primary_key: true on a column if you do id: false.

Commenting on old closed issues will not make anything happen. If you are experiencing something you believe to be a bug, please open a new issue with steps to reproduce using our standard bug report template.

I stumbled across this today - I’m using Rails 5, but it seems like the same exact issue still happens. In my case the issue was that the join table had no primary key column. Removing id: false from the table migration and re-running it fixed the issue with instance.destroy.

image

This might also help people who land here from Google (as I did). Also this.

@bengotow I ran into this issue as well - it looks like the reason was because our project was using a join table, but also using has_many through:.

Posting details about it in case someone else runs into the same problem:

The Problem:

We were incorrectly wiring up a has_and_belongs_to_many relationship.

# our migration
class CreateJoinTableUsersRoles < ActiveRecord::Migration[5.1]
  def change
    create_join_table :users, :roles, table_name: 'user_roles' do |t|
      t.index [:user_id, :role_id]
    end
  end
end
class User < ApplicationRecord
  has_many :user_roles, dependent: :destroy
  has_many :roles, through: :user_roles
end

We also had a UserRole class (not exactly sure why).

class UserRole < ApplicationRecord
  belongs_to :user
  belongs_to :role
end

Because of this, we were experiencing the same issue as above:

[1] pry(main)> u = User.first
  User Load (0.6ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT $1  [["LIMIT", 1]]
=> #<User id: 1, email: "user@example.com", first_name: "John", last_name: "Doe", created_at: "2017-07-25 14:53:02", updated_at: "2017-07-25 14:53:02">
[2] pry(main)> u.destroy
   (0.2ms)  BEGIN
  UserRole Load (0.3ms)  SELECT "user_roles".* FROM "user_roles" WHERE "user_roles"."user_id" = $1  [["user_id", 1]]
   (0.3ms)  ROLLBACK
NoMethodError: undefined method `to_sym' for nil:NilClass
from /Users/watts/.rbenv/versions/2.4.1/lib/ruby/gems/2.4.0/gems/activerecord-5.1.1/lib/active_record/sanitization.rb:90:in `block in expand_hash_conditions_for_aggregates'

The Fix

If using has_and_belongs_to_many instead of has_many through: with a generated join_table, Rails will automatically do what’s necessary.

class User < ApplicationRecord
  has_and_belongs_to_many :roles
end
[1] pry(main)> u = User.first
  User Load (0.4ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT $1  [["LIMIT", 1]]
=> #<User id: 1, ....>
[2] pry(main)> u.destroy
   (0.3ms)  BEGIN
  UserPermission Load (1.0ms)  SELECT "user_permissions".* FROM "user_permissions" WHERE "user_permissions"."user_id" = $1  [["user_id", 1]]
  SQL (0.4ms)  DELETE FROM "user_roles" WHERE "user_roles"."user_id" = $1  [["user_id", 1]]
  SQL (0.3ms)  DELETE FROM "users" WHERE "users"."id" = $1  [["id", 1]]
   (1.2ms)  COMMIT
=> #<User id: 1, ....>

Not sure it is relevant. But i created relational table for has many relation without primary key(id: false in migration). I had to remove it to make the delete work. It may save some poor souls.