Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
My idea on policy objects

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment