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

Defining Abilities with Blocks

pkqk edited this page Nov 12, 2014 · 15 revisions

If your conditions are too complex to define in a hash (as shown in Defining Abilities page), you can use a block to define them in Ruby.

can :update, Project do |project|
  project.priority < 3
end

If the block returns true then the user has that ability, otherwise they will be denied access.

Only for Object Attributes

The block is only evaluated when an actual instance object is present. It is not evaluated when checking permissions on the class (such as in the index action). This means any conditions which are not dependent on the object attributes should be moved outside of the block.

# don't do this
can :update, Project do |project|
  user.admin? # this won't always get called
end

# do this
can :update, Project if user.admin?

See Checking Abilities for more information.

Fetching Records

A block's conditions are only executable through Ruby. If you are Fetching Records using accessible_by it will raise an exception. To fetch records from the database you need to supply an SQL string representing the condition. The SQL will go in the WHERE clause, if you need to do joins consider using sub-queries or scopes (below).

can :update, Project, ["priority < ?", 3] do |project|
  project.priority < 3
end

If you are using load_resource and don't supply this SQL argument, the instance variable will not be set for the index action since they cannot be translated to a database query.

Block Conditions with Scopes

As of CanCan 1.6 it's possible to pass a scope instead of an SQL string when using a block in an ability.

can :read, Article, Article.published do |article|
  article.published_at <= Time.now
end

This is really useful if you have complex conditions which require joins. A couple caveats:

  • You cannot use this with multiple can definitions that match the same action and model since it is not possible to combine them. An exception will be raised when that is the case.
  • If you use this with cannot, the scope needs to be the inverse since it's passed directly through. For example, if you don't want someone to read discontinued products the scope will need to fetch non discontinued ones:
cannot :read, Product, Product.where(:discontinued => false) do |product|
  product.discontinued?
end

It is only recommended to use scopes if a situation is too complex for a hash condition.

Overriding All Behavior

You can override all can behavior by passing no arguments, this is useful when permissions are defined outside of ruby such as when defining Abilities in Database.

can do |action, subject_class, subject|
  # ...
end

Here the block will be triggered for every can? check, even when only a class is used in the check.

Additional Docs