Rails 3 Authentication and Authorization with Devise and CanCan (Part 2)

Dec 16 2010 Published by thepanakawan under Tech

This is the second part of two posts tutorial on how to use Devise and CanCan with Rails 3. You can find the first part of this tutorial here.

Where were we?

In the previous part, we have used Devise library to create our User model in CRUD style. With what we have coded in the previous part, you should now be able to manage users and authenticate them. Now it’s time to add the roles and define what features they may access.

1. Adding the Roles

This step is pretty traditional, we can just scaffold all the MVC part for our roles. In this article, I only need one attribute that is the name of the role.

1
rails generate scaffold Role name:string

2. Making HABTM relationship between roles and users

We have to do several tasks in this step. The first thing is to manually create the migration file for the habtm table. We do it by first generating the migration file like this:

1
rails generate migration UsersHaveAndBelongToManyRoles

and then we fill the file with this code:

1
2
3
4
5
6
7
8
9
10
11
class UsersHaveAndBelongsToManyRoles < ActiveRecord::Migration
  def self.up
    create_table :roles_users, :id => false do |t|
      t.references :role, :user
    end
  end

  def self.down
    drop_table :roles_users
  end
end

And do not forget to modify our model files. This is how our role.rb looks like after we modify it:

1
2
3
class Role < ActiveRecord::Base
  has_and_belongs_to_many :users
end

And this is how our user.rb file looks like. Please be noted that we also add :role_ids in the attr_accessible and we add a method to identify the role of the respective user.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class User < ActiveRecord::Base
  has_and_belongs_to_many :roles
 
  # Include default devise modules. Others available are:
  # :token_authenticatable, :confirmable, :lockable and :timeoutable
  devise :database_authenticatable,
         :recoverable, :rememberable, :trackable, :validatable

  # Setup accessible (or protected) attributes for your model
  attr_accessible :email, :username, :password, :password_confirmation, :remember_me, :role_ids

  def role?(role)
    return !!self.roles.find_by_name(role.to_s.camelize)
  end
end

In the views part, we have to modify at least the _form.html.erb from our users view so that we can assign the role to the user from web interface. This is how the file users/_form.html.erb looks like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<%= form_for(@user) do |f| %>
  <% if @user.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@user.errors.count, "error") %> prohibited this user from being saved:</h2>

      <ul>
        <% @user.errors.full_messages.each do |msg| %>
          <li><%= msg %></li>
        <% end %>
      </ul>
    </div>
  <% end %>
  <div class="field">
    <%= f.label :email %><br />
    <%= f.text_field :email %>
  </div>
  <div class="field">
    <%= f.label :username %><br />
    <%= f.text_field :username %>
  </div>
  <% if @current_method == "new" %>
    <div class="field">
      <%= f.label :password %><br />
      <%= f.password_field :password %>
    </div>
    <div class="field">
      <%= f.label :password_confirmation %><br />
      <%= f.password_field :password_confirmation %>
    </div>
  <% end %>
  <% for role in Role.find(:all) %>
    <div>
      <%= check_box_tag "user[role_ids][]", role.id, @user.roles.include?(role) %>
      <%= role.name %>
    </div>
  <% end %>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

Of course you can also modify your index.html.erb and show.html.erb files to show the role of the listed user(s). But I think I leave it to you to do it yourself. After all these tasks, we can run rake task db:migrate.

1
rake db:migrate

In this state, we can now add users and assign them with the roles we desire. But of course each role has not yet identified with any permission by CanCan. That’s what we’re going to do in the next step.

3. Adding ability

To use CanCan, we have to make a class that defines the permissions that are given to each role. In CanCan, this is called as “ability” and to set it up, we have to create the ability.rb file as part of our models.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Ability
  include CanCan::Ability
 
  def initialize(user)
    user ||= User.new # guest user

    if user.role? :administrator
      can :manage, :all
    elsif user.role? :operator
      can :manage, Post
    else
      can :read, :all
    end
  end
end

CanCan use a very pleasant way to describe our roles and it’s corresponding allowed features. In our code above, we use the keyword “can” and then follow it with the level of authorization we want the respective role to have and at last we end it with the name of the model. The level of authorizations that I have tried so far is :create, :read, :update, :delete, and :manage. Maybe there is a way to define another ability, you can find about it in depth in the official Github page of CanCan library here.

4. Adding authorization

Finally, in every controller that needs authorization, we add this line:

1
load_and_authorize_resource

Final Words

I think that’s it for now. I know I only have covered the basic part of using Devise and CanCan but I hope this can be helpful for anyone that just starts to learn about both libraries. I may add a working source code to this tutorial later. Meanwhile, if you have any trouble following this tutorial, you can ask me in the comment section.

6 responses so far

  • Pavel says:

    Hi,
    thanks for this very good post with examples. I have one question. In ability class you are using roles administrator and operator. These roles are predefined in CanCan or you have to define it by yourself? I need to have some different roles.
    Thank you
    Pavel T.

    • thepanakawan says:

      Hi, Pavel.

      So sorry it takes this long to reply. The role “administrator” and “operator” were just an example that I made. You can pretty much define any role you want.

  • Jenny says:

    Hi

    You have saved my bacon! Thanks for the help, was stuggling for hours on this one.

    Could I ask a question though – I’m really struggling to understand how to display the roles on the show page.

    Could you point me in the right direction?

    Have been searching through the rails guides but don’t really know what I should be looking for.

    Thanks

    Jenny Newbie ;)

  • thepanakawan says:

    Hi, Jenny.

    I’ll add some snippets to this post later on. Hopefully today. Would you mind to wait?

  • Jenny says:

    Hi

    That would be fantastic!! :)

  • hectique says:

    There is a typo in the code after “rails generate migration UsersHaveAndBelongToManyRoles”:

    The first line should read
    “class UsersHaveAndBelongToManyRoles < ActiveRecord::Migration"
    instead of
    "class UsersHaveAndBelongsToManyRoles < ActiveRecord::Migration"

Leave a Reply