Ruby on Training Wheels

A Weblog for Ruby and Rubyists.

On the Internals of CanCan

Sometimes we forget that Ruby on Rails is in fact built on … Ruby. In this post, I want to explore the CanCan gem to see how it works its magic.


This post explores how the CanCan gem (v1.6.10) works internally. Lots of this applies to other gems as well.

If you’re completely unfamiliar with CanCan, the authorization gem from Ryan Bates, I highly recommend you look it up. Very briefly, this gem allows me to write code such as this:

1
2
3
<% if can? :edit, @article %>
  <%= link_to 'Edit', edit_article_path(@article) %>
<% end %>

How simple is that?

On the flip side, the user abilities are defined like this:

1
2
3
4
5
6
7
8
9
10
class Ability
  include CanCan::Ability

  def initialize(user)

    can :edit, Article, author_id: user.id

  end

end

The user param that you see passed into the initialize method is the result of querying a global current_user method.

It is your responsibility to create that method, and ensure it return the logged in user. Cancan handles authorization, not authentication.

In these lines of code, we allow the current_user to :edit an instance of the Article model if the author_id property on the Article matched the id of the user.

There are lot’s of other options here, so read the well-written wiki.

Make sure you create this class, and name it Ability. CanCan relies on that convention.

So how does the magic happen? How do we have access to this new method can? (and cannot?).

As is the case of any gem, when you add gem 'cancan' to your Gemfile, and run bundle, ruby will fetch the gem source-code if doesn’t exist on your system yet, and will load the file in the lib/GEM_NAME.rb, in this case lib/cancan.rb.

You’ll see that this file simply requires the rest of the gem’s code, all in the lib dir, namespaced under the cancan dir.

The first file required is ability.rb. This is the file that declares the CanCan::Ability module, which as seen before will get included in our Ability class.

The methods defined in this module are what we can call in our Ability file, such as can :edit, Article, author_id: user.id.

The next file, rule.rb, is what handles a lot of the dirty work under the hood. The methods in this file are all for internal use only. This gem has been designed so as to completely isolate code meant to be called directly (such as the can and authorize methods) from these methods, which are never meant to be directly referenced.

So it is with the next two required files, controller_resource.rb and controller_additions.rb, the former of which is the internal implemention of the latter. It is within this latter file that we see the can?, and other methods that get called within our views and controllers.

So how do we actually have access to these methods without adding any include or extend calls into our model or controller classes?

The magic happens in the last few lines:

1
2
3
4
5
if defined? ActionController::Base
  ActionController::Base.class_eval do
    include CanCan::ControllerAdditions
  end
end

This checks if we’re using Action Controller, and if we are, it includes this module into the ActionController::Base, the module which the ApplicationController inherits from, which in turn, is the class from which all other controllers inherit.

But to get access to the can and other methods which are defined in CanCan::ControllerAdditions::ClassMethods module, we need this bit of code:

1
2
3
4
def self.included(base)
  base.extend ClassMethods
  base.helper_method :can?, :cannot?, :current_ability
end

This extends all our application controllers (by way of inheriting from ActionController::Base), and gives them the ability to respond to those method calls.

It also calls the helper_method method passing in the :can?, :cannot?, and :current_ability methods, giving our views access to those methods as well.

So that’s the short of it. Although the wiki is very helpful, the inline documentation is more verbose, so if you get stuck, that might be a good place to start. Besides, code organization is a skill hard to pickup, and I’m sure I’ve learned something perusing this codebase, written by a programmer far more skilled than me. And, best of all, Rails is that much less magical. Magic != Fun.

386 Duty Calls: What do you want me to do? LEAVE? Then they’ll keep being wrong!


All Revisions to this document are visible here