Rails 4 user roles and permissions
Your first guess was right, use cancancan and you'll be good with it.
EDIT Jul 24, 2015
I've been using cancancan for a long time now and it was always working great. I've recently started working on a project where Pundit is used for authorization.
It is awesome. It prompts you to define the policy for each resource and it feels more natural than one bloated Ability class.
For bigger projects, I would definitely recommend Pundit.
how to add Roles and Permission in Rails even it's already build by devise
we can do this by using cancan gem.
Basic Steps to implement:
import gem into Gemfile
create a ability file by using the below command
rails g cancan:ability
- define the accesses like below.
class Ability
include CanCan::Ability
def initialize(user)
alias_action :create, :read, :update, :destroy, :to => :crud
if user.super_admin?
can :access, :rails_admin # grant access to rails_admin
can :dashboard # grant access to the dashboard
# Accss Level to Models
can :crud
end
end
end
See more details for Gems documentation
Rails: Is there any way to build dynamic role based authorization in rails?
Pundit vs CanCanCan
Your conclusions about CanCanCan and Pundit are just nonsense. Neither of them are "static" or "dynamic" and they have pretty much the same features. The architecture and design philosophy are radically different though.
CanCanCan (originally CanCan) is written as a DSL which was the hottest thing since pre-sliced bread back when Ryan Bates created CanCan 10 years ago. It scales down really well and is easy to learn but gets really ugly as soon as you reach any level of complexity. If anything doing "dynamic authorization" in CanCanCan is going to be a nightmare due its architecture. The ability class in CanCanCan is the god of all god objects.
Pundit is just Object Oriented Programming. In pundit your policies are just classes that take a user and resource as initializer arguments and respond to methods like show?
, create?
etc. Pundit is harder to understand initially but since its just OOP you can tailor it however you want. And since your authentication logic is stored in separate objects it scales up to complexity far better and adheres to the SOLID principles.
How do I setup a dynamic roles system?
This is you standard role system ala Rolify:
class User < ApplicationRecord
has_many :user_roles
has_many :roles, through: :user_roles
def has_role?(role, resource = nil)
roles.where({ name: role, resource: resource }.compact).exists?
end
def add_role(role, resource = nil)
role = Role.find_or_create_by!({ name: role, resource: resource }.compact)
roles << role
end
end
# rails g model user_roles user:belongs_to role:belongs_to
class UserRole < ApplicationRecord
belongs_to :user
belongs_to :role
end
# rails g model role name:string resource:belongs_to:polymorphic
class Role < ApplicationRecord
belongs_to :resource, polymorphic: true, optional: true
has_many :user_roles
has_many :users, through: :user_roles
end
You can then scope roles to resources:
class Forum < ApplicationRecord
has_many :roles, as: :resource
end
Rolify lets you go a step further and just defines roles with a class as the resource. Like for example user.add_role(:admin, Forum)
which makes the user an admin on all forums.
How do I create a permissions system?
A simple RBAC system could be built as:
class Role < ApplicationRecord
has_many :role_permissions
has_many :permissions, through: :role_permissions
def has_permission?(permission)
permissions.where(name: permission).exists?
end
end
# rails g model permission name:string
class Permission < ApplicationRecord
end
# rails g model role_permission role:belongs_to permission:belongs_to
class RolePermission < ApplicationRecord
belongs_to :role
belongs_to :permission
end
So for example you could grant "destroy" to "moderators" on Forum.find(1)
by:
role = Role.find_by!(name: 'moderator', resource: Forum.find(1))
role.permissions.create!(name: 'destroy')
role.has_permission?('destroy') # true
Although I doubt its really going to be this simple in reality.
User Roles in Ruby on Rails
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
Related Topics
How to Install Ruby 1.9.3 on Ubuntu Without Rvm
Differencebetween #Encode and #Force_Encoding in Ruby
How to Instruct Capistrano 3 to Load My Shell Environment Variables Set at Remote Host
Using Soap and Other Standard Libraries in Ruby 1.9.2
Certificate Verify Failed in "Gem Install Foundation"
Most Efficient Way to Calculate Hamming Distance in Ruby
In Ruby What Does the "Receiver" Refer To
Rubocop Error 'Class Definition Is Too Long Ruby'
Advantages and Disadvantages of Ruby on Rails Polymorphic Relationships
Select Mailbox "Sent Mail" or "All Mail" in Ruby Net::Imap
How to Replace the Last Occurrence of a Substring in Ruby
Rails: Switch Connection on Each Request But Keep a Connection Pool
How to Use Ajax Send Data to Controller in Ruby on Rails
Convert Array to Hash While Preserving Array Index Values in Ruby