Rails: Using CanCan to define multiple roles depending on instances of single Model?
Asked Answered
R

2

7

I'm currently stuck on how to separate roles for CanCan depending on each condition that we want. In our application, there are many categories (such as math, english, history, etc.) and within each are many courses.

Each user can have many different roles on each category. For example, John can be a "reader" for math, which means he can read all the courses that are in math. John can also be a "writer" for english, which means he can read all the courses in english, create a course within category english, and edit/delete only the courses he created within english.

If these were the only roles John had, he would not be able to see the category history in the navbar, and would be denied access to courses that are within history.

These are how relations are set up:

class User < ActiveRecord::Base
  has_many :roles

  def has_role?(role_sym)
    roles.any? { |r| r.level.underscore.to_sym == role_sym }
  end
end

class Category < ActiveRecord::Base
  has_many :roles
  has_many :courses
end

class Role < ActiveRecord::Base
  belongs_to :user
  belongs_to :category
  attr_accessible :level, :category_id, :user_id
end

in model/ability.rb we have

class Ability
include CanCan::Ability

def initialize(user)
  user ||= User.new # guest user (not logged in)  #guest

  if user.has_role? :reader 
     reader(user)
  end

  if user.has_role? :writer
    writer(user)
  end
end

#BE ABLE TO SEE COURSES AND CATS FOR PERMITTED CATS.
def reader(user)
  can :read, Category, :roles => { :user_id => user.id, :level => "reader" }
   ## how would we be able to limit reading of courses that are within permitted categories? something like category.courses ~~ 
end

def writer(user)
  reader(user) #inheriting from reader? this doesnt work because level is hardcoded into reader
  can :read, Category, :roles => { :user_id => user.id, :level => "writer"}
  # 1.you can read all courses in category that you are given permission to
  # 2.you can write courses in permitted category
  # 3.you can edit, delete courses that only youve created within permitted category
end
end

Questions:

  1. How do we separate the roles of "reader" and "writer" in the correct way? How do we access the courses that are within the categories that we have access to?

  2. After defining the reader and writer methods in the ability.rb, how do we use them in our view pages? It looks like the current documentations use something like "<% if can? :read, @category %> " but that doesn't use the methods we separated and defined.

p.s. We will have 7 different roles: guest, reader, writer, editor, manager, admin, and app_admin(our developers)

I've been trying to solve this for 3 days now - please understand that I'm still fairly a beginner! Thanks in advance

Ruffner answered 28/8, 2013 at 8:20 Comment(0)
O
0

I ran into a same needs today and found a way to do this on CanCan Wiki.

Simple follow these simple steps:

1) Create a constant under the User class with your role names:

class User < ActiveRecord::Base
  ROLES = %w[admin moderator author banned]
end

2a) Create and run a migration if you are using ActiveRecord:

rails generate migration add_roles_mask_to_users roles_mask:integer
rake db:migrate

2b) Add these field on User model if you are using Mongoid:

field :roles_mask, type: Integer

3) Next you'll need to add the following code to the User model:

# in models/user.rb
def roles=(roles)
  self.roles_mask = (roles & ROLES).map { |r| 2**ROLES.index(r) }.inject(0, :+)
end

def roles
  ROLES.reject do |r|
    ((roles_mask.to_i || 0) & 2**ROLES.index(r)).zero?
  end
end

4) If you're using devise without strong parameters, don't forget to add attr_accessible :roles to you user model. If you're using devise with strong_parameters, either as a gem in a Rails 3 app, or as is built-in in Rails 4, dont forget to add the roles to the permitted list in the controller:

class ApplicationController < ActionController::Base
  before_filter :configure_permitted_parameters, if: :devise_controller?

  protected

  def configure_permitted_parameters
    devise_parameter_sanitizer.for(:sign_up) {|u| u.permit(:email, :password, :password_confirmation, roles: [])}
  end
end

5) Add the code bellow to generate checkboxes in the view for setting these roles:

<% for role in User::ROLES %>
  <%= check_box_tag "user[roles][#{role}]", role, @user.roles.include?(role), {:name => "user[roles][]"}%>
  <%= label_tag "user_roles_#{role}", role.humanize %><br />
<% end %>
<%= hidden_field_tag "user[roles][]", "" %>

6) Finally, you can then add a convenient way to check the user's roles in the Ability class:

# in models/user.rb
def is?(role)
  roles.include?(role.to_s)
end

# in models/ability.rb
can :manage, :all if user.is? :admin

That's it.

I hope this can help.

Ordovician answered 3/12, 2014 at 0:57 Comment(0)
C
-2

In your gemfile Include.

  1. gem "cancan"

  2. install bundle.

  3. rails g cancan:ability

this will generate an ability class in your models.

define your Abilities there like below.

but keep remember that you have already defined roles,

such as you have a User model,

having two roles defined i.e admin and support.

  class Ability
      include CanCan::Ability
       def initialize(user)
         user||= User.new
           can :read, :all
       if user.role == 'admin'
          can :manage, :all
       else
          can :read, :all
          end
       end
    end

4. the resource on which you want to restrict a user, use the following filter in their controller.

              load_and_authorize_resource

5. if you want restrict something in the views not to show.

    <% if can? :manage, @flower %>
     <td><%= link_to 'Edit', edit_flower_path(flower) %></td>
    <% end %>
    <% if can? :manage, @flower %>
      <td><%= link_to 'Destroy', flower_path(flower), method: :delete, data: { confirm: 'Are you sure?' } %></td>
    <% end %>
Coverley answered 28/8, 2013 at 11:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.