Keeping it Simple: Migrating to Pundit from CanCan

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 ApplicationController.

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_authorized and verify_policy_scoped. They’re optional, but a Good Idea.

III. Controllers

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.

PROTIP: 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.

About Christian Nelson

Christian is a software developer, technical lead and agile coach. He's passionate about helping teams find creative ways to make work fun and productive. He's a partner at Carbon Five and serves as the Director of Engineering in the San Francisco office. When not slinging code or playing agile games, you can find him trekking in the Sierras and playing with his daughters.
This entry was posted in Web and tagged , , , . Bookmark the permalink.
  • http://stevenhaddox.com stevenhaddox

    Awesome write up. Wish I’d come across those few months ago!

    • Christian Nelson

      My next post will be on time travel. Look for it last year. :)

      • http://stevenhaddox.com stevenhaddox

        Ha ha! I knew the blog post was new. I should’ve specified that I wish I’d come across the library a few months ago :P

        This is an excellent write-up of how to get started with it all though. Thanks again :)

  • Tyrel Richey

    I didn’t even know about pundit it’s awesome thank you!

  • Pingback: Diacode weekly #6 | Blog de Diacode

  • Hamed Asghari

    Great write-up! We haven’t migrated our application to Rails 4 but still decided to migrate to Pundit from CanCan due to the simple nature of Pundit as you pointed out. I also did not appreciate how all of our authorization logic ended up in one big “Ability” file in CanCan and it quickly became a maintenance headache. I like the separation that Pundit provides.

  • Phil Pirozhkov

    > “Must be signed in.” unless user

    Weird example. What about users that don’t want to sign up?

    > record.user == user

    Looks extremely unreadable.

    CanCan is undoubtedly a relic monster, but it still does it job pretty well.

    There’s a new kid on the block, https://github.com/inossidabile/protector which aims on model security as opposed to controller based security in cancan.

    • JoshuaMuheim

      Protector looks nice, indeed. Do you have any experience with it already and are able to compare it to Pundit and CanCan?

  • Pingback: Raygun: Generating Rails Apps for the Last Year | The Carbon Emitter

  • http://danielkehoe.com/ Daniel Kehoe

    There is an example application from the RailsApps project that shows how to use Pundit: https://github.com/RailsApps/rails-devise-pundit