CanCan (CanCanCan)

CanCan was written by Ryan Bates to make role based security simple and fast for Ruby on Rails applications. It was first published in late 2009 and was the go to role based security gem for rails applications. There were several reasons why so many developers wanted to use it. Some of those reasons were because it was fast, easy to setup and worked well. Support for the original CanCan gem ended in mid 2013. Ryan Bates decided to take a break from development all together so the gem was forked by Bryan Rite and is being maintained by him today. This allowed the gem to work with the latest version of rails and is able to keep working when new versions of rails are released. As one of the developers that use’s CanCanCan I can say that it’s just as stable as it’s ever been. It’s a mature gem by this point in time so you won’t see too many breaking changes if any.

Features

One thing that I really love about CanCan is that it gives you an ability class that allows you to define all of your roles and what they can do in one place. You can define as many roles as you want and have them share operations with little effort. Also you are able to assign multiple roles to a user. This will give you the flexibility to only have one role for one purpose. Instead of mixing roles together to fit some custom role you need. You are able to just give the user two roles instead. Another thing that I found really useful is you can pass in a set of criteria that can be applied to the role check. For example a user creates a new record. That record belongs to that user and should only be able to be viewed or edited by that user. With CanCan you can simply pass in a scope like can :edit, ModelName, user_id: user.id. This will apply this scope to the check so only the user who created the record will be able to view or change it. This is really powerful because you can do it all in one place. If you want to setup multiple roles per user simply following this guide (Role-Based-Authorization). Here is how I implemented it based on that how to.


class User < ActiveRecord::Base
  ROLES = %i[role_1 role_2 role_3]

  attr_accessor :roles

  def ability
    @ability ||= Ability.new(self)
  end
  delegate :can?, :cannot?, to: :ability

  def roles=(roles)
    roles = [*roles].map { |r| r.to_sym }
    self.roles_mask = (roles & ROLES).map { |r| 2**ROLES.index(r) }.inject(0, :+)
  end

  def roles
    ROLES.reject do |r|
      ((roles_mask.to_i || 0) & 2**ROLES.index(r)).zero?
    end
  end

  def has_role?(role)
    roles.include?(role)
  end
end

Lessons Learned

One thing I learned while working on my project using CanCan is you need to setup the scopes for the can statements. Originally when I had developed this project I did not add these scopes on. This gave any user the ability to edit any record they had access to. So for example a user was able to view and edit a record that they did not create. This would have caused major issues if it wouldn’t have been caught before going live. Here is an example on how you can provide a scope to your can statement.


can :show, Vehicle, id: user.vehicles.map { |vehicle| vehicle.id }

This will allow a user who’s user_id is in the vehicle table and only that user to view that record. So if a user decided to change the id in the url they will get the access denied error message. You can do this with any active record call as long as it ties back to a user.

Alternatives

When originally choosing CanCan I took the time to look into a couple of alternatives. One of the major players today in rails is Pundit. When Ryan decided to take a leave from the development world a lot of people started looking for an alternative to CanCan. Since it was no longer going to be maintained. A lot of people chose Pundit. I have never used or setup Pundit fully but while doing my research I couldn’t figure out a good way to assign multiple roles to one user. Leave a comment if this is actually simple and I just missed it in the Pundit documentation. I’m sure this is possible but I didn’t want to invest the time digging through the code to find out. CanCan had good documentation in how to do so. At the end of the day I encourage everybody to do their research and determine the solution that best fits their application. I’ve seen CanCan used in multiple applications and so far it’s been able to handle everything thrown at it.

4 thoughts on “CanCan (CanCanCan)

    1. wallerjake Post author

      Yes, I agree I found Pundit too time consuming. It could be that I just don’t understand fully how to use it or something.

      I ran into a similar situation when I started using CanCan. The difference was the documentation was so good it actually made things pretty simple. I am also using CanCanCan which is just a fork of CanCan. You still got to give credit where credit it due.

      I would be interested if anybody has successfully setup Pundit with multiple roles. It would be nice to see how it’s done and to see if has any advantages over CanCanCan. I don’t think the setup could be an easier so it would probably have to be performance based.

      Thanks for the feedback.

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *