Asdrubal
Asdrubal

Reputation: 2461

Chain has_many :through associations

I would like to know if there is a more elegant way to chain has_many through relationships. In my example I have a user whom can have multiple roles. Each role has multiple permissions. So a user has multiple permissions. The code below works fine, but I am wonder if there is a better way to do this.

class User < ActiveRecord::Base
  has_many :role_user_mappings
  has_many :roles, through: :role_user_mappings

  def permissions
    permitted_actions = []
    self.roles.each do |role|
       role.permissions.each do |permission|
         permitted_actions << permission
       end
    end
    permitted_actions
  end
end

class Role < ActiveRecord::Base
  has_many :permission_role_mappings
  has_many :permissions, through: :permission_role_mappings
end

class Permission < ActiveRecord::Base
end

class PermissionRoleMapping < ActiveRecord::Base
  belongs_to :permission
  belongs_to :role
end

class RoleUserMapping < ActiveRecord::Base
  belongs_to :user
  belongs_to :role
end

I would like to be able to do this.

user.permissions

EDIT: Tried On thing that I tried that at least DRYs the User model a little bit is adding the function as a concern

module Permittable
  extend ActiveSupport::Concern

  def permissions
    permitted_actions = []
    self.roles.each do |role|
       role.permissions.each do |permission|
         permitted_actions << permission
       end
    end
    permitted_actions
  end
end

Upvotes: 0

Views: 1338

Answers (2)

jvillian
jvillian

Reputation: 20263

If you do this:

class Permission < ActiveRecord::Base
  has_many :permission_role_mappings
end

Then you should be able to do this:

class User < ActiveRecord::Base
  has_many :role_user_mappings
  has_many :roles, through: :role_user_mappings

  def permissions
    Permission.
      joins(:permission_role_mappings).
      where(permission_role_mappings: {role: roles})
  end
end    

By the way, you may already know this and it may be why you're asking the question... but this is going to give you an N+1 query:

  permitted_actions = []
  self.roles.each do |role|
     role.permissions.each do |permission|
       permitted_actions << permission
     end
  end
  permitted_actions

Also, FWIW, when wanting an array back from a collection, you don't need to do:

  permitted_actions = []
  self.roles.each do |role|
     ...
  end
  permitted_actions

You can just do:

  roles.map do |role|
    ...
  end

Since map returns an array.

Upvotes: 1

SteveTurczyn
SteveTurczyn

Reputation: 36860

Have you tried..

class User < ActiveRecord::Base
  has_many :role_user_mappings
  has_many :roles, through: :role_user_mappings
  has_many :permissions, through: roles

That should give you

user.permissions

I'm not sure when the HMT via HMT feature was made available, I know it was missing in earlier versions of rails, but it works for me on Rails 5.

Upvotes: 4

Related Questions