Cannabis
Very simple permissions that I have used on my last several projects so I figured it was time to abstract and wrap up into something more easily reusable.
Cans
Whatever class you want all permissions to run through should include Cannabis::Cans.
class User
include MongoMapper::Document
include Cannabis::Cans
end
This means that an instance of a user automatically gets can methods for the default REST actions: can_view?(resource), can_create?(resource), can_update?(resource), can_destroy?(resource).
Ables
Each of the can methods simply calls the related “able” method (viewable, creatable, updatable, destroyable) for the action (view, create, update, delete). Cannabis comes with defaults for this methods that you can then override as makes sense for your permissions.
class Article
include MongoMapper::Document
include Cannabis::Ables
end
Including Cannabis::Ables adds the able methods to the class including it. In this instance, article now has viewable_by?(user), creatable_by?(user), updatable_by?(user) and destroyable_by?(user).
Lets say an article can be viewed and created by anyone, but only updated or destroyed by the user that created the article. To do that, you could leave viewable_by? and creatable_by? alone as they default to true and just override the other methods.
class Article
include MongoMapper::Document
include Cannabis::Ables
userstamps! # adds creator and updater
def updatable_by?(user)
creator == user
end
def destroyable_by?(user)
updatable_by?(user)
end
end
Lets look at some sample code now:
john = User.create(:name => 'John')
steve = User.create(:name =. 'Steve')
ruby = Article.new(:title => 'Ruby')
john.can_create?(ruby) # true
steve.can_create?(ruby) # true
ruby.creator = john
ruby.save
john.can_view?(ruby) # true
steve.can_view?(ruby) # true
john.can_update?(ruby) # true
steve.can_update?(ruby) # false
john.can_destroy?(ruby) # true
steve.can_destroy?(ruby) # false
Now we can implement our permissions for each resource and then always check whether a user can or cannot do something. This makes it all really easy to test. Next, how would you use this in the controller.
Enforcers
class ApplicationController
include Cannabis::Enforcers
end
Including Cannabis::Enforcers adds an enforce permission method for each of the actions defined (by default view/create/update/destroy). It is the same thing as doing this for each Cannabis action:
class ApplicationController
include Cannabis::Enforcers
delegate :can_view?, :to => :current_user
helper_method :can_view? # so you can use it in your views
hide_action :can_view?
private
def (resource)
raise Cannabis::Transgression unless can_view?(resource)
end
end
Which means you can use it like this:
class ArticlesController < ApplicationController
def show
@article = Article.find!(params[:id])
(@article)
end
end
If the user can_view? the article, all is well. If not, a Cannabis::Transgression is raised which you can decide how to handle (show 404, slap them on the wrist, etc.).
Adding Your Own Actions
You can add your own actions like this:
Cannabis.add(:publish, :publishable)
The first parameter is the can method (ie: can_publish?) and the second is the able method (ie: publishable_by?).
Review
So, lets review: cans go on user model, ables go on everything, you override ables in each model where you want to enforce permissions, and enforcers go after each time you find or initialize an object in a controller. Bing, bang, boom.
Copyright
Copyright © 2010 John Nunemaker. See LICENSE for details.