I've been thinking for a while on how to make permission handling more modular while maintaining ease of use. I'm a big fan of CanCan's outer mechanism, in that I ask the question "can I read this post?", or more specifically can? :read, post
. The simplicity of that is where I think permission checking needs to be. As for defining abilities, that leads to be something less than desired. An ability becomes a nightmare to maintain relatively quickly. It knows more than one object should about the system and isn't modular. I can't take one permission out and use it elsewhere, instead having to copy and paste it into another ability file and tweak it to make it fit. A policy object seems like the solution, but I haven't seen any implementation that is easy to use on the outside. With that this is what I have been thinking.
A policy can have any number of objects assigned to it but only one context. Most, if not all, cases will suffice with a single responsibility policy but why limit ourselves. The context is the key, it is what we're checking our permissions against. If we ask our policy a question we want to get an answer. What I didn't like about CanCans implementation was it built its permissions against the context. This was highly inflexible as the context has more chances to change then an object yet that's what was happening. A policy should care about the object it is for foremost and check against the context when asked.
So we start with a simple policy, which is similar to an ability. What is different is that each action is a method prefixed by the word can
, as so:
class PostPolicy
attr_reader :post
def initialize post
@post = post
end
def can? action, context = nil
method = "can_#{action}?"
respond_to?(method) && send(method, context)
end
def can_create? user
post.author == user
end
def can_destroy? user
post.author == user
end
end
The model has a helper method to fetch the policy and assign it itself. This allows the model to return different policies for different states or circumstances if we so want.
class Post
def to_policy
PostPolicy.new self
end
end
After all that, all that is needed is some tiny glue code in the controller/view and we get:
def can? action, object
return true unless object.respond_to? :to_policy
object.to_policy.can? action, current_user
end
def edit
if can? :edit, post
render :edit
else
head :forbidden
end
end
This mechanism replaces CanCan quite nicely while being very module and separating permission concerns more concisely. This is a very crude sample of code but can be a very simple yet powerful plugin for any app.