Pundit

Posted on by Chris Moore

#Pundit

Recently, I’ve had a chance to work with Pundit again. I wasn’t wowed with it previously, but I really took notice of it this time around.

What’s it for?

Pundit is an authorization gem. Through the use of Plain Old Ruby Objects (PORO), Pundit allows you to place a slick authorization layer of simple, happy-fun-time Ruby over your Rails models.

Why not CanCanCanCanCanCanCan?

In the past, when a Rails application needed authorization we reached in The Toolbox for CanCanCan.

Truthfully, in smaller, less intense applications it’s a matter of preference which gem to use.

However, as the applications grow more and more complex, the singular ability.rb file used to define CanCanCan’s permissions can become more and more monolithic and potentially disorganized to hair-pulling proportions.

Enter Pundit’s Policy classes to clear things up!

First Glance

Let’s start with something straight out of the README:

class PostPolicy
  attr_reader :user, :post

  def initialize(user, post)
    @user = user
    @post = post
  end

  def update?
    user.admin? or not post.published?
  end
end

With minimal effort, we can make reasonable, informed decisions about what’s going on here. The Post object in question can be updated if the user attempting the update has the admin role, or the post hasn’t been published yet.

OK, How?

Well, let’s build on the example above, shall we?

class PostsController < ApplicationController
  before_filter :verify_authorized

  def update
    @Post = Post.find(params[:id])
    authorize @post
    @post.update(params[:post])
  end
end

verify_authorized is a helper method supplied by Pundit that, in this case, ensures authorize is called in every controller action.

Behind the scenes, Pundit is making assumptions that current_user will be available. (This lookup call is configurable, if, say, your application has a current_mongoose… it’s possible… )

The authorize call in our example is being passed @post, a Post object.Pundit makes some assumptions here. It should check the PostPolicy since it’s dealing with a Post object, and that the permission matches the action name (update?). We can, of course, override those assumptions.

class PostsController < ApplicationController
  before_filter :verify_authorized

  def update
    @Post = Post.find(params[:id])
    authorize @post, :mongoose?
    @post.update(params[:post])
  end
end

class PostPolicy
  attr_reader :user, :post

  def initialize(user, post)
    @user = user
    @post = post
  end

  def update?
    user.admin? or not post.published?
  end

  def mongoose?
    # I've really taken this mongoose thing too far...
  end
end

Are you just going to rewrite the README?

Nah.

Pundit did a well enough job on their own.

They made the how? easy, you’re here for the why?.

So why Pundit?

Simply put, it’s clean. It’s organized. It’s sexy.

I remember when I first started learning Ruby on Rails, the mantra was “skinny controller, fat model”. Also, “No logic in the views!”, “WTF is a rake!?”, and “What’s up with that guy’s hair?”.

As the framework and the language have matured, there’s been a departure from the old “skinny controller, fat model” mantra; and as a programmer, I’ve come to embrace that.

Models should be kept lean and stay true to its ORM layer. Models define a table in Object Oriented space and its associations, delegates, callbacs and so forth.

Controllers should contain API specific code. What the application does.

We can keep our models and controllers nice and tight by taking advantage of ServiceObjects, Concerns, engines, Serializers - and a whole host of other tried and true approaches.

So why Pundit?

Because Policy objects allow you to organize your code in a way that just feels right. Your code is tighter, easier to read, easier to debug, more organized - where’s the downside here?

That’s why! :)

 
comments powered by Disqus