Pundit
#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! :)