AbilityList

Simple permissions system as plain old Ruby objects. No fancy integration with ORMs or frameworks.

All of this is just a single Ruby file with less than 50 lines of significant code. Read it now.

Defining abilities

Define the list of abilities a user has by subclassing AbilityList.

Each ability is comprised of a verb (required) and an object (optional). A verb is any symbol, while the object can be a symbol or a class.

class Abilities < AbilityList
  def initialize(user)
    can :view, Video

    if user.admin?
      can :delete, Video
      can :upload, Video
    end

    can :login

    can :view, :admin
  end
end

Then hook it to user by defining an abilities method.

class User < OpenStruct
  include AbilityList::Helpers

  def abilities
    @abilities ||= Abilities.new(self)
  end
end

Checking for abilities

Now you may use can?:

user = User.new
user.can?(:view, Video)
user.can?(:view, Video.find(20))

user.can?(:login)
user.can?(:view, :admin)

The inverse cannot? is also available.

Raising errors

Or you can use authorize!, which is exactly like can? except it raises an AbilityList::Error exception. Perfect for controllers.

user.authorize! :view, Video.find(20)

Custom criteria

You can pass a block to can for custom criteria:

can :view, Video do |video|
  !video.restricted? or user.age > 18
end

You can even use Ruby's &:sym syntax:

cannot :edit, Article, &:published?

# Equivalent to cannot(:edit, Article) { |article| article.published? }

Object types

The method can always accepts at least 2 arguments: a verb and an object.

You can define your permissions by passing a class as the object:

can :view, Video

which makes it possible to check for instances or classes:

user.can?(:view, Video)                 #-> passing a class
user.can?(:view, Video.find(1008))      #-> passing an instance

But this doesn't have to be classes. Just pass anything else, like a symbol:

can :login, :mobile_site

# user.can?(:login, :mobile_site)

Overriding criteria

Criteria are evaluated on a top-down basis, and the ones at the bottom will override the ones on top.

The method cannot is provided to make exceptions to rules.

For example:

# Everyone can edit comments.
can :edit, Comment

# ...but unconfirmed users can't edit their comments.
if user.unconfirmed?
  cannot :edit, Comment
end

# ...but if the comments are really new, they can be edited, even if the user
# hasn't confirmed.
can :edit, Comment { |c| c.created_at < 3.minutes.ago }

The :manage keyword

You can use :manage as the verb to allow any verb.

can :manage, Group

This allows the user to do anything to Group its instances.

user.can?(:delete, Group)       #=> true
user.can?(:create, Group)       #=> true
user.can?(:eviscerate, Group)   #=> true

The :all keyword

You can use :all as the object for any permission. This allows a verb to work on anything.

Don't know why you'll want this, but cancan has it, so:

can :delete, :all

So you can:

user.can?(:delete, Video)     #=> true
user.can?(:delete, Article)   #=> true
user.can?(:delete, Recipe)    #=> true

More examples

See RECIPES.md for some practical examples.

Limitations

AbilityList aims to be extremely lean, and to be as framework- and ORM-agnostic as possible. As such, it doesn't:

  • No explicit integration with Rails controllers.

  • No explicit integration with ActiveRecord (or any other ORM).

  • No explicit provisions for roles.

See RECIPES.md on how to do these things.

Acknowledgements

Heavily inspired by cancan. AbilityList is generally a stripped-down version of cancan with a lot less features (see Limitations) above.

(c) 2013 MIT License.