Using CanCan to do Feature Authorization

At PetroFeed we’ve started to implement tiered packages for our users. We have a Basic (free) tier and a Pro (paid) tier. If you’ve ever done tiered authorization before you know it can be really invasive!

We use CanCan to reduce that complexity and define all that authorization in a single file. However I have yet to see an example of authorization for tiered packages. I’d like to show you a snapshot of what we’re doing with PetroFeed so that you can benefit from what we’ve done to date (and hopefully improve upon it too!)

This post presumes you’re familiar with the standard CanCan installation — If you’re not familiar with it, I suggest the README.

On a Basic plan in PetroFeed you can follow 10 Wells (Well == A hole in the ground that produces Oil) and a Pro plan allows unlimited follows. To achieve that our ability file looks like this:

The beauty of this code is that we can continue to use the standard CanCan authorization methods to check:

  1. Whether the user can access the page
  2. Whether the user can access the feature

Here’s an example of the WellsController that handles the following of a well:

PetroFeed has a really interesting architecture in that we’re running as a single page web app (we have a brilliant team of JavaScript developers!) backed by a rails API. When a user reaches their follow limit on our Basic plan they need to be informed that they’ve reached that limit and prompted to upgrade to Pro. Our web app listens for a 401 to see if a user is authorized to access pages, and the standard response for payment is 402.

So that means when we rescue from the CanCan::AccessDenied error we need to distinguish from a payment required exception vs an unauthorized exception.

Our code to do that looks like this:

current_ability is simply an instance of Ability, and so we’ve defined the payment_required? instance method on it to check wher the user received the exception due to a requirement for payment.

As a whole the code is really simple, and that’s because we’ve spent a lot of time figuring out how to do this elegantly! I foresee needing to package up the logic as different tiers are introduced into the app. If you have any suggestions on how to improve this, I’d love to hear your feedback! :D

  1. gavingmiller reblogged this from petrofeedeng
  2. petrofeedeng posted this