Cannon Moyer
Cannon Moyer

Reputation: 3174

User Roles in Ruby on Rails

This may be more of a conceptual question. I've got a user table and I need to provide roles for each of the users. For example I would like for some users to create customers and some to only view customers. I figure that I could create an association like this:

User has_one :role
Role belongs_to :user

With this structure, I could create boolean fields on the Role model such as can_create_customer. If I did, then I could use the following code to check for permissions based on every user:

if role.can_create_customer?
    Customer.create(name: "Test")
end

Is there a better way of accomplishing the same thing?

Upvotes: 1

Views: 2211

Answers (1)

Tallboy
Tallboy

Reputation: 13467

This is how I do user roles and rights.

I create the following models:

class Role < ApplicationRecord
  has_and_belongs_to_many :users
  has_and_belongs_to_many :rights
  validates :name, presence: true
end

class Right < ApplicationRecord
  has_and_belongs_to_many :roles
  validates :name, presence: true
end

Make sure you have proper constraints and indexes set in your database:

add_index :roles, :name, unique: true
add_index :rights, :name, unique: true

From there, you will need join tables for roles_users as well as rights_roles (because it's a many to many)

Then in seeds I create a few roles and rights:

role_admin = Role.create!(name: 'admin')               
role_admin.rights.create!(name: 'full_access')         

role_cm = Role.create!(name: 'company_manager')     
role_cm.rights.create!(name: 'edit_company')        
role_cm.rights.create!(name: 'edit_all_invoices')      
role_cm.rights.create!(name: 'edit_all_users') 

Then you need to assign one or more roles to your user

current_user.roles << Role.find_by(name: 'company_manager') 

Now, I simply check the roles/rights on login, and store it in a session along with the user_id

def session_login(user)
  session[:user_id] = user.id
  session[:rights] = user.list_rights
end

And you can access roles/rights with several JOIN sql statements efficiently. You want to store it in a session on login so you dont need to make this query for every request. It does mean however if you change roles/rights in the middle of your users session they will need to log back out and in again to see the updated changes

class User < ApplicationRecord
  def list_roles
    roles.map(&:name)
  end

  def list_rights
    Right.joins(roles: :roles_users).where('roles_users.user_id = ?', id).map(&:name)
  end
end

Final notes

Now, when you do your 'checking', make sure you verify based on RIGHTS (dont check a users' role)

You can make this helper method

def current_user_has_right?(right)
  return false unless session[:rights]
  session[:rights].include? right
end

You can authorize! an entire controller for instance in this way:

def authorize!
  not_found unless current_user_has_right?('full_access')
end

Upvotes: 6

Related Questions