September 12nd, 2015
CanCanCan is a gem for permissions used in Rails development. The original gem, CanCan was developed by Ryan Bates of Rails Casts. After he stopped supporting the project since Rails 3, the community picked up the project, maintaining much of the original features over the years.
To use permissions, we first have to define them, and this is done in the file
ability.rb. In here, CanCanCan provides the flexible
cannot methods, which we can use to check the abilities of the
current_user against a resource. More specifically, we can define rules to use against a specific instance of a model, for example
project. This instance does not restrict to a model:
# ability.rb can :manage, WelcomeController can [:edit, :update], Project do |project| user == project.owner end # welcome_controller.rb authorize! :index, self
A controller like the
WelcomeController does not have a model instance which it manages. In this case, we check against the controller itself in a method, passing
self to the
authorize method (taking advantage of Ruby as an object oriented language). In the
ProjectController, we can pass in the
@project instance and get the appropriate response.
By using CanCanCan’s setup, we eliminate those scattered if-statements that usually hang around the beginning of a method like:
# some_controller.rb def show @model = Model.find(params[:id]) if @model.user == current_user # do something else # throw something end end
Moreover, we have collected all the rules into a single method, which makes changing permissions much easier. The only possible downside might be that it makes creating new scaffolds a bit harder, as you’d have to worry about setting permissions much before you’d prefer to.
A worry that came up, especially with developers who have experience dealing large-scale applications, is that they prefer to define permissions in the database[^database]. In a simple and rapid product pushing environment, we argue that if we change permissions, we at least have to change something somewhere else in the codebase. This is saying that we simply cannot handle on-the-fly permission changing with the current state of code. Therefore, we’d have to redeploy anyway, so we can keep permission as part of the codebase. CanCanCan does in fact provide loading database permissions. See Database Permissions
CanCanCan provides methods that eliminates repeating lines in controllers, a feature that greatly accommodates Rails’ DRY philosophy. For example, this could further reduce the
# some_controller.rb # before def show @model = Model.find(params[:id]) authorize! :read, @model end # after load_and_authorize_resource def show # nothing is needed end
load_and_authorize_resource method is in the scope of the entire class, so other methods do not have to explicitly find their methods at all. In methods like
update, CanCanCan expects a method defined as
model_params (where model is the name of the specific model), and this enforces the usage of strong parameters as well (Rails 4+).
Variations to this method,
authorize_resource, are both provided. All three variations can take a symbol for the name of the model to load, which is defaulted to the name of the controller. The extended benefit of this feature is that it accounts for nested resources:
# routes.rb resources :project do resources :tasks end # tasks_controller.rb load_and_authorize_resource :project load_and_authorize_resource :task, through: :project
This gives us a
@task instance in every method, and also gives us incentive to use Rails’ resourceful routes.
Furthermore, CanCanCan has the assurance method
check_authorization that, when put in
ApplicationController, will raise an exception if any method of any controller does not have the authorize method. CanCanCan allows us to skip checks for specific controllers by adding
skip_authorization_check. We don’t encourage using this method because it lets business logic slip into specific controllers when we have already encapsulated it well. We use the trick of checking for controller instances (see before) to ensure we have an authorize for each method. We encourage developers to define abilities first and not wait until their scaffold is complete, as it will eliminate mistakes of forgetting permission checking. See also, Integration Testing.
CanCanCan allows us to check permissions in the views too; we take full advantage of this:
# ability.rb can :follow, Profile do |profile| (user.profile != profile) and (not UserFollow.find_by(follower_id: user.id, followee_id: profile.user.id)) end can :unfollow, Profile do |profile| (user.profile != profile) and UserFollow.find_by(follower_id: user.id, followee_id: profile.user.id) end <!-- _follow_button.html.erb --> <%= button_to 'Follow', follow_profile_path(profile) if can? :follow, profile %> <%= button_to 'Unfollow', unfollow_profile_path(profile) if can? :unfollow, profile %>
Here, if a user can both follow and unfollow a profile, they would definitely see both buttons. We control this inside
ability.rb, where it is impossible for both permissions to satisfy. There are cases where a user can neither follow nor unfollow a user (e.g. they are seeing their own profile). This logic is all handled inside ability, and the view is left only in charge of presentation.