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.