A new way to cancan

Cancan, Rails Posted on

Inspired by one of my student's code, I recently adopted a new way to define authorization with cancan.

Often times, it can be very cumbersome to keep track of all the different resources and roles inside an application. Imagine a hypothetical application that has 3 user roles (admin, employee, and guest). The cancan wiki recommends something like the following:

class Ability
  include CanCan::Ability

  def initialize(user)
    user ||= User.new

    if user.role?(:admin)
      can :manage, Resource
    elsif user.role?(:employee)
      can :update, Resource
    else
      can :read, Resource
    end
  end
end

This, however, can become quite cumbersome - especially when the roles become very lengthy. You'll be scrolling the whole way down the page, it's easy to accidentally overwrite existing rules, and it just plain sucks. Instead, we can break the roles into their own methods:

class Ability
  include CanCan::Ability

  def initialize(user)
    user ||= User.new

    begin
      send(user.role)
    rescue
      Rails.logger.error("Undefined role method for role #{user.role}")
      guest
    end
  end

  private
  def admin
    can :manage, Resource
  end

  def employee
    can :update, Resource
  end

  def guest
    can :read, Resource
  end
end

If you have other ways you are using Cancan ability definitions, post a comment!

About Seth

Seth Vargo is a Distinguished Software Engineer at Google. Previously he worked at HashiCorp, Chef Software, CustomInk, and some Pittsburgh-based startups. He is the author of Learning Chef and is passionate about reducing inequality in technology. When he is not writing, working on open source, teaching, or speaking at conferences, Seth advises non-profits.