-
Notifications
You must be signed in to change notification settings - Fork 0
Upgrading to 1.4
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.
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
.
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.
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.
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
.
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.
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.
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]
.
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.
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.