Skip to content
This repository has been archived by the owner on Dec 12, 2021. It is now read-only.

Defining Abilities

Sarah Vessels edited this page Feb 3, 2014 · 28 revisions

The Ability class is where all user permissions are defined. An example class looks like this.

class Ability
  include CanCan::Ability

  def initialize(user)
    user ||= User.new # guest user (not logged in)
    if user.admin?
      can :manage, :all
    else
      can :read, :all
    end
  end
end

The current user model is passed into the initialize method, so the permissions can be modified based on any user attributes. CanCan makes no assumption about how roles are handled in your application. See Role Based Authorization for an example.

The can Method

The can method is used to define permissions and requires two arguments. The first one is the action you're setting the permission for, the second one is the class of object you're setting it on.

can :update, Article

You can pass :manage to represent any action and :all to represent any object.

can :manage, Article  # user can perform any action on the article
can :read, :all       # user can read any object
can :manage, :all     # user can perform any action on any object

Common actions are :read, :create, :update and :destroy but it can be anything. See Action Aliases and Custom Actions for more information on actions.

You can pass an array for either of these parameters to match any one. For example, here the user will have the ability to update or destroy both articles and comments.

can [:update, :destroy], [Article, Comment]

Important notice about :manage. As you read above it represents ANY action on the object. So if you have something like:

can :manage, User
can :invite, User

and if you take a test of last :invite rule you always get true. Why? That's because :manage represents ANY action on object and :manage is not just :create, :read, :update, :destroy on object.

If you want only CRUD actions on object, you should create custom action that called :crud for example, and use it instead of :manage:

def initialize(user)
  user ||= User.new

  alias_action :create, :read, :update, :destroy, :to => :crud
 
  can :crud, User
  can :invite, User
end

Hash of Conditions

A hash of conditions can be passed to further restrict which records this permission applies to. Here the user will only have permission to read active projects which he owns.

can :read, Project, :active => true, :user_id => user.id

It is important to only use database columns for these conditions so it can be used for Fetching Records.

You can use nested hashes to define conditions on associations. Here the project can only be read if the category it belongs to is visible.

can :read, Project, :category => { :visible => true }

The above will issue a query that performs an INNER JOIN to query conditions on associated records. If you require the associations to be queried with a LEFT OUTER JOIN then you can pass in a scope. The example below will use a scope that returns all Photos that do not belong to a group.

class Photo
  has_and_belongs_to_many :groups
  scope :unowned, includes(:groups).where(:groups => {:id => nil})
end

class Group
  has_and_belongs_to_many :photos
end

class Ability
  def initialize(user)
    user ||= User.new # guest user (not logged in)
    can :read, Photo, Photo.unowned do |photo|
      photo.groups.empty?
    end
  end
end

An array or range can be passed to match multiple values. Here the user can only read projects of priority 1 through 3.

can :read, Project, :priority => 1..3

Anything that you can pass to a hash of conditions in Active Record will work here. The only exception is working with model ids. You can't pass in the model objects directly, you must pass in the ids.

can :manage, Project, :group => { :id => user.group_ids }

If you have a complex case which cannot be done through a hash of conditions, see Defining Abilities with Blocks or MetaWhere.

Combining Abilities

It is possible to define multiple abilities for the same resource. Here the user will be able to read projects which are released OR available for preview.

can :read, Project, :released => true
can :read, Project, :preview => true

The cannot method takes the same arguments as can and defines which actions the user is unable to perform. This is normally done after a more generic can call.

can :manage, Project
cannot :destroy, Project

The order of these calls is important. See Ability Precedence for more details.

Additional Docs