4

I have a problem with has_and_belongs_to_many in a Rails 4 app. The setup is as follows:

  • A User can have several Roles
  • A Role can have several Permissions

Since many Users can share the same Roles, and many Roles can share the same Permissions, and I don't need special connection models between them, I am using has_and_belongs_to_many for both these relationships.

Here are the models (stripped of validations):

class User < ActiveRecord::Base
  has_and_belongs_to_many :roles
end

class Role < ActiveRecord::Base
  has_and_belongs_to_many :permissions
  has_and_belongs_to_many :users
end

class Permission < ActiveRecord::Base
  has_and_belongs_to_many :roles
end

The join tables are named as per convention:

create_table "permissions_roles" do |t|
  t.integer "role_id"
  t.integer "permission_id"
end

create_table "roles_users" do |t|
  t.integer "role_id"
  t.integer "user_id"
end

Roles <-> Permissions works great, but Users <-> Roles seems to work only one way. I can attach Users to Roles, but not Roles to Users – the collection methods do not exist on the User objects. From the rails console:

> r = Role.first # Fetch a role
> r.users        # Empty list of users -- so far so good
> u = User.first # Fetch a user
> u.roles        # NoMethodError: undefined method `roles' for #<User:0x007fe67562f580>

Any idea what could be going on here?

Update:

When I run User.has_and_belongs_to_many :roles from the console, the association is correctly set up and I can run User.first.roles without issue. It seems the association for some reason isn't set up when the application is bootstrapped.

arnemart
  • 14,180
  • 3
  • 17
  • 11
  • 1
    does the output of `u.methods` include `roles`? – dax Mar 21 '14 at 10:37
  • 1
    Surely permissions would be programmatic? I mean, do you need a database to house them? – Richard Peck Mar 21 '14 at 10:42
  • @dax: No, it does not. – arnemart Mar 21 '14 at 11:15
  • @Rich Peck: Well, yes, and we're changing that, but that's besides the point here. The relationship should still work. – arnemart Mar 21 '14 at 11:16
  • Good point - thanks for clarification :) – Richard Peck Mar 21 '14 at 11:18
  • does `r.users.methods` include `roles`? – dax Mar 21 '14 at 11:22
  • I suppose you are doing it wrong.The HABTM relation should have one join model with the corresponding two other models on which the relation is set.Correct me if i'm wrong. – Pavan Mar 21 '14 at 11:26
  • @dax: No, it doesn't either. – arnemart Mar 21 '14 at 11:27
  • give this a try: `r.reflect_on_association(users)` `u.reflect_on_association(roles)` - if the output of these is the same, then i don't know :P if it's not, i suggest you take a look at your `user` and `role` tables (not the join tables) – dax Mar 21 '14 at 11:28
  • @Pavan: No, that's a `has_many :through`-relation. HABTM does not need a join model. – arnemart Mar 21 '14 at 11:28
  • ah, or better: `r.reflect_on_all_associations` and `u.reflect_on_all_associations` – dax Mar 21 '14 at 11:30
  • Actually it does need I think so by seeing this guide http://guides.rubyonrails.org/association_basics.html#the-has-and-belongs-to-many-association – Pavan Mar 21 '14 at 11:30
  • read that link again, Paven. First sentence: A has_and_belongs_to_many association creates a direct many-to-many connection with another model, **with no intervening model**. – dax Mar 21 '14 at 11:30
  • @dax oops! Misjudged by seeing the picture of it.Apologies to the OP. – Pavan Mar 21 '14 at 11:32
  • dax: The association shows up when doing `Role.reflect_on_all_associations`, but not when doing `User.reflect_on_all_associations`. This is weird. – arnemart Mar 21 '14 at 11:33
  • not sure why this would work, but try changing order of habtm in Role.rb? – dax Mar 21 '14 at 11:36
  • maybe check out the [docs](http://guides.rubyonrails.org/association_basics.html#has-and-belongs-to-many-association-reference) – dax Mar 21 '14 at 11:40
  • Do u have additional columns other than the foreign keys? – Pavan Mar 21 '14 at 11:46
  • @dax: I tried changing the order, it does not seem to do anything. – arnemart Mar 21 '14 at 11:56

2 Answers2

0

Maybe you should consider using has_many, :through

Here is an example

From rubyonrails.org:

class Assembly < ActiveRecord::Base
  has_many :manifests
  has_many :parts, through: :manifests
end

class Manifest < ActiveRecord::Base
  belongs_to :assembly
  belongs_to :part
end

class Part < ActiveRecord::Base
  has_many :manifests
  has_many :assemblies, through: :manifests
end

More ressources on:

  1. the-has-many-through-association
  2. choosing-between-has-many-through-and-has-and-belongs-to-many
geoom
  • 6,279
  • 2
  • 26
  • 37
Zaratan
  • 70
  • 1
  • 3
0

Solved it, it was extremely project-specific. All our models need to be used across multiple projects, so they live in their own gem. Turns out there was a different User model in another location that superseded the one in the gem.

arnemart
  • 14,180
  • 3
  • 17
  • 11