Skip to content
ryanb edited this page Nov 15, 2010 · 8 revisions

CanCan version 1.4 is a large update with several backwards-incompatbile changes, specifically concerning blocks.

See Upgrading to 1.3 if you have not upgraded to 1.3 yet. Also check out the CHANGELOG for the full list of changes.

Loading index action

A collection of resources is automatically loaded in the index action using accessible_by.

class ProductsController < ApplicationController
  load_and_authorize_resource
  def index
    # @products automatically set to Product.accessible_by(current_ability)
  end
end

Since this is a scope you can build on it further.

def index
  @products = @products.paginate(:page => params[:page])
end

The @products variable will not be set if Product does not respond to accessible_by (for example if you aren't using Active Record). It will also not be set if you are using blocks in any of the can definitions because that is incompatible with accessible_by.

Blocks in can definitions

There are many backward-incompatible changes regarding blocks in can definitions.

The block is no longer triggered if a class is passed in to the can? check. This makes the behavior consistent with the conditions hash and means you no longer need to check for nil.

can :read, Project do |project|
  project.publicly_available?
end
can? :read, Project # returns true without triggering block
can? :read, @project # triggers block

The action and class are no longer passed to the block when using :manage or :all arguments. Only the object is passed in. Here are a couple examples:

can :manage, Project do |project|
  # ...
end

can :manage, :all do |object|
  # ...
end

If you need the old behavior (like when defining Abilities in Database), you can call can without any arguments and everything will be passed to the block.

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

This block will be triggered with every check even when a class is passed in to the can? call.

Internationalization

The AccessDenied messages can be customized through internationalization. For example:

# in config/locales/en.yml
en:
  unauthorized:
    manage:
      all: "Not authorized to %{action} %{subject}."
      user: "Not allowed to manage other user accounts."
    update:
      project: "Not allowed to update this project."

Notice manage and all can be used to generalize the subject and actions. Also %{action} and %{subject} can be used as variables in the message.

Ensure authorization is performed

If you want to be certain authorization is not forgotten in some controller action, add check_authorization to your ApplicationController.

class ApplicationController < ActionController::Base
  check_authorization
end

This will add an after_filter to ensure authorization takes place in every inherited controller action. If not it will raise an exception. You can skip this check by adding skip_authorization_check to that controller. Both of these methods take the same arguments as before_filter so you can exclude certain actions with :only and :except.

Nested Resources

It is now possible to load a nested resource through a method. This is often used with current_user.

class ProjectsController < ApplicationController
  load_and_authorize_resource :through => :current_user
end

Here everything will be loaded through the current_user.projects association.

This parent resource is now required and will raise an error when nil. If you want the parent to be optional, add the :shallow => true option.

Inherited Resources

It will automatically detect if you are using Inherited Resources and load the resource through that. The load_resource is still necessary since Inherited Resources does lazy loading.

class ProjectsController < InheritedResources::Base
  load_and_authorize_resource
end

See Inherited Resources for details.

Default attributes in new and create actions

The new and create actions will initialize the resource with the attributes in the hash conditions. For example, if we have this can definition.

can :manage, Product, :discontinued => false

Then the product will be built with that attribute in the controller.

@product = Product.new(:discontinued => false)

This way it will pass authorization when the user accesses the new action. The attributes are still overridden by whatever is passed by the user in params[:product].

SQL in can definition

If you are defining can definitions with a block, it's now possible to pass SQL conditions as an argument for use with accessible_by.

can :read, :project, ["publicly_available = ?", true] do |project|
  project.publicly_available?
end

In this simple case it would be better to define the conditions with a hash instead of a block, but this will be useful in more complex scenarios where a hash is not possible.

Running Specs

If you want to contribute to this project, a Gemfile has been added to make it easy to start developing and running the specs. Just run bundle and rake to run the specs. The specs currently do not work in Ruby 1.9 due to the RR mocking framework.

Special thanks to everyone who helped contribute to this release. See the issue tracker for the history.

Clone this wiki locally