-
Notifications
You must be signed in to change notification settings - Fork 405
Devise CanCanCan rolify Tutorial
This Tutorial shows you how to setup a Rails >=3.1 application with a strong and flexible authentication/authorization stack using Devise, CanCanCan and rolify (3.0 and later)
First, create a bare new rails app. If you already have an existing app with Devise and CanCanCan set up and you just want to add rolify, just add rolify in your Gemfile, run bundle install
and skip to step 5
# rails new rolify_tutorial
- edit the
Gemfile
and add Devise, CanCanCan and rolify gems:
gem 'devise'
gem 'cancancan'
gem 'rolify'
-
run
bundle install
to install all required gems -
Run Devise generator
# rails generate devise:install
- Create the User model from Devise
# rails generate devise User
- Create the Ability class from CanCanCan
# rails generate cancan:ability
- Create the Role class from rolify
# rails generate rolify Role User
- Run migrations
# rake db:migrate
-
Configure Devise according to your needs. Follow Devise README for details.
-
Edit the Ability model class, add these lines in the initialize method:
if user.has_role? :admin
can :manage, :all
else
can :read, :all
end
- Use the resourcify method in all models you want to put a role on. For example, if we have the Forum model:
class Forum < ActiveRecord::Base
resourcify
end
- Create a User using
rails console
> user = User.new
> user.email = "[email protected]"
> user.password = "test1234"
> user.save
- Add a role to the new User
> user.add_role "admin"
- Check if the user has admin rights
> ability = Ability.new(user)
> ability.can? :manage, :all
=> true
If you want to use class scoped role with CanCanCan, it's a bit tricky. Currently in CanCan 1.x, you cannot mix instance and class checking, because the two are OR-ed and class checking skips the hash of conditions (see https://github.com/CanCanCommunity/cancancan/wiki/Checking-Abilities for more details). Let's take this ability class example:
if user.has_role? :admin
can :manage, :all
else
can :read, Forum
can :write, Forum if user.has_role?(:moderator, Forum)
can :write, Forum, :id => Forum.with_role(:moderator, user).pluck(:id)
end
This won't work as you expect, because the last :write
clause will always return true
if you ask ability.can? :write, Forum
, even if your user has only a role on an instance of Forum.
But you can use some workarounds:
- don't use the class scoped role for an instance. That means, you will need class scoped only roles and instance scoped only roles separated. In that case, it's better to use different action names like:
if user.has_role? :admin
can :manage, :all
else
can :read, Forum
can :manage, Forum if user.has_role?(:manager, Forum)
can :write, Forum, :id => Forum.with_role(:moderator, user).pluck(:id)
end
- don't use
can?
method for class checking. Use rolify instead. so if you want to display a button or some info only if a user has a specific class role, use this:
<% if user.has_role? :moderator %>
...
<% end %>
and for the instance scoped roles, you still are able to use the Ability
class:
if user.has_role? :admin
can :manage, :all
else
can :read, Forum
can :write, Forum, :id => Forum.with_role(:moderator, user).pluck(:id)
end
Please note that the with_role
method allows us to restrict the Forum instances the user has a role on. It's provided by rolify library using resourcify
method on the Forum
class.