We’ve been using CanCan for Rails Authorization on most projects for a few years now. When upgrading an internal application to Rails 4, I discovered that CanCan does not play well with strong parameters. There are some patches to make things work, but they didn’t feel right. Also, CanCan hasn’t been given much love recently; it has the signs of an abandoned project: sporadic commits, many open issues/pull-requests, poor Code Climate score, etc. This was a cue that it’s time to review the options and see how the authorization landscape has changed.
Jonas Nicklas (of Capybara fame) released Pundit about a year ago, aiming to create something simple and less “magical” than the other options available. This line of thinking resonates strongly with me. I dislike frameworks that do too much or are difficult to understand when you have a look inside. I want something that’s simple and fits just right, or something that provides a solid foundation upon which I can build something that is a perfect fit.
I decided to give Pundit a try…
Migrating to Pundit
Migrating our application was straight forward. I followed these high-level steps:
I. Policies and Policy Specs
Pundit captures authorization rules in Policy objects that map to model objects. By convention,
NotePolicy captures the rules for the
Note model. Manually convert whatever is in your CanCan ability.rb into one or more policies. Here’s an example of one policy and a convenient base class:
It’s very easy to unit test your policies. PROTIP: Follow the pattern introduced on the Thunderbolt Labs blog to write specs that are concise and easy to read:
PROTIP: use FactoryGirl’s
build_stubbed method instead of create. In most cases, you will not need a persisted instance of your model. We don’t want to hit the database unless it’s absolutely necessary.
II. Application Controller
Mix the Pundit functionality in to all controllers through the
PROTIP: Use Pundit’s filters, lines 5-6, to ensure actions go through the authorization step. These are similar to CanCan’s
check_authorization helper. There’s a section in the official readme about
verify_policy_scoped. They’re optional, but a Good Idea.
CanCan provides a helper which loads and authorizes individual models or scope (in the case of index). Pundit doesn’t, which means we have to be more explicit about it (see lines 9, 15, 22).
This may seem like a drag at first (more code), but I came around pretty quickly and like the explicitness. It’s easier to understand what’s happening in the controller. No more “how did @wombats get set and is it scoped correctly?” moments.
IV. Views and View Specs
Pundit provides a policy helper that you can use in views to conditionally render elements.
policy is not available when running view specs (details). If you write view specs, there are two things you can do:
A. Configure an all-allowing policy for all view specs (as spec/support/pundit_view_policy.rb).
B. Stub out
policy on a case by case basis, adjusting what is allowed for each spec.
PROTIP: Combine these strategies. Assume the current user is authorized, then use the 2nd technique to say otherwise (e.g. destroy?: false). This makes it easy to write lean and fast unit view specs that cover the important cases.
V. Strong Parameters
Since pundit isn’t reaching into Rails’ internals, it Just Works with strong parameters. Follow this recipe when you need attribute-level authorization:
Overall Impression and Conclusion
I am very pleased with Pundit. It’s easy to use and works for the scenarios I run in to. Pundit is tiny — ~252 LOC vs CanCan’s ~1,620 — and has a great Code Climate score of 3.9. The Elabs team is careful about what they add because they want to keep it simple.
In the end, I traded an additional couple lines of code for less hidden complexity (aka “magic”). That’s a fair trade and it leaves the code a little more explicit, which is a good thing in general and especially for developers new to the application. Even if Pundit doesn’t become the go-to authorization library, it’s simple enough that any developer can pop open the hood and understand what’s going on.
My conclusion: use Pundit for the win!
Finally, huge thanks to Ryan Bates for writing CanCan; it’s been incredibly useful over the years. And props out to Jonas for creating Pundit. We’re all very lucky to be a part of such a vibrant open source community.
PS: Note that Pundit isn’t yet included in the authorization category on Ruby Toolbox so it’s not as easy for newcomers to discover.