Rails, Devise, CanCan, Rolify and User Company Role
During development of a web application, I came across a problem with users having one role per company.
For example, you have a user who can manage two companies. He created the first company and therefore he is the owner and an admin of that company, but he has been invited to second company, where he haves limited access of content, so he's the moderator of that company.
On data level, I have a User table created by Devise, Companies table and a Role which Rolify created for me.
Because I want to know which user has which role for a company; I need a table to store their IDs, so I created a Unities table.
create_table :unities do |t|
t.integer :role_id
t.integer :user_id
t.integer :company_id
t.timestamps
end
After that I created a model called Unity, which I will use for easier role manipulation.
unity.rb
class Unity < ActiveRecord::Base
attr_accessible :role_id, :user_id, :company_id
belongs_to :role
belongs_to :company
belongs_to :user
end
and I also added associtaions to company, role and user models.
user.rb
class User < ActiveRecord::Base
rolify
cattr_accessor :current_role
has_many :unities
has_many :roles, through: :unities
has_many :companies, through: :unities
end
I added current_role so I can check which role does user haves on a company that he is on but I will get to that later
role.rb
class Role < ActiveRecord::Base
has_many :unities
has_many :users, through: :unities
has_many :companies, through: :unities
end
company.rb
class Company < ActiveRecord::Base
cattr_accessor :current
has_many :unities
has_many :users, through: :unities
has_many :roles, through: :unities
end
Here I added a current company, so I can check wich company is active I will use that later.
What is cattr_accestor?
http://www.cowboycoded.com/2010/04/20/ruby-cattr_accessor-vs-attr_accessor/
After defining associations between these three tables, we can now define roles and abilities in Ability.rb which is used by CanCan
But before that, we need to find a current User role.
Since one user can manage multiple companies but on different level, first we need to find wich company is he currently on, and we can get that from params.
So in a ApplicationController add two methods.
class ApplicationController < ActionController::Base
before_filter :initialize_company, :initialize_user_role
def initialize_company
if params.has_key?(:company_id)
Company.current = Company.find(params[:company_id])
end
end
def initialize_user_role
unless Company.current.nil?
User.current_role = Unity.where(company_id: Company.current.id, user_id: current_user.id).first.role.name
end
end
end
And now we are ready to create ability.rb
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new
if User.current_role == 'admin'
can :manage, :all
else
if User.current_role == 'moderator'
can :read, Products
can :update, Products
cannot :destroy, Products
cannot :create, Products
cannot :manage, Client
end
can :read, :all
end
end
end
From ApplicationController we can get current_role and check it up against the role we want.
if User.current_role == 'admin'
In this article I explained how things should work, and basic principle of creating a role per company and user. In next article I will describe how to change user permissions and how to add roles to users.
And here is gist for better view
https://gist.github.com/vmarcetic/6049929
This is just my way of doing things. If you have any suggestions, or you know how to do it better, write a comment.
Written by Vedran Marčetić
Related protips
7 Responses
Is this thread safe? I mean User.current_role and Company.current.
I think User.currentrole usage is not correct, cause Ability gets specified user. It's better to make: user.currentrole - meaning the use of given user of initialize method.
Thank you for posting this!
You can get rid of the nested conditionals by refactoring it like this:
def initialize(user)
user ||= User.new
can :read, :all
if user.current_role == 'admin'
can :manage, :all
else
can :read, Products
can :update, Products
cannot :destroy, Products
cannot :create, Products
cannot :manage, Client
end
end
And in the case that you have multiple roles, you can do the following:
def initialize(user)
user ||= User.new
can :read, :all
case user.current_role
when 'admin'
can :manage, :all
when 'moderator'
can :read, Products
can :update, Products
cannot :destroy, Products
cannot :create, Products
cannot :manage, Client
# ... other roles and abilities ...
end
end
You're correct with this - hopefully, it was just a typo.
You're correct with this - hopefully, it was just a typo.
Hi,
Did you complete your next article on this?
I need to know "how to change user permissions"