"A pundit is a person who offers to mass media their opinion or commentary on a particular subject area (typically political analysis, the social sciences, technology or sport) on which they are knowledgeable, or considered a scholar in said area" - Wikipedia
"Pundit provides a set of helpers which guide you in leveraging regular Ruby classes and object oriented design patterns to build a simple, robust and scaleable authorization system" - Github
In your Gemfile:
gem "pundit"
Include Pundit in your application controller:
class ApplicationController < ActionController::Base
include Pundit
end
Pundit is focused around the notion of policy classes. You put these classes in app/policies
.
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
Pundit makes the following assumptions:
- The class has the same name as some kind of model class, only suffixed with the word "Policy"
- The first argument of the constructor is a user
- The second argument is some kind of model object, whose authorization you want to check
- The class implements some kind of query method, in this case
update?
Pundit now lets you do this in your controller:
def update
@post = Post.find(params[:id])
authorize @post
# etc...
end
What authorize
is actually doing is something like this:
unless PostPolicy.new(current_user, @post).update?
raise "not authorized"
end
If you don't have an instance for the first argument to authorize, then you can pass the class. For example:
class PostPolicy < ApplicationPolicy
def admin_list?
user.admin?
end
end
# In the controller
def admin_list
authorize Post
# we don't have a particular post to authorize
end
The policy
method is especially useful for conditionally showing links or buttons in the view:
<% if policy(@post).update? %>
<%= link_to "Edit post", edit_post_path(@post) %>
<% end %>
Pundit adds a method called verify_authorized
to your controllers. This method will raise an exception if authorize
has not been called
class ApplicationController < ActionController::Base
include Pundit
after_action :verify_authorized
end
Pundit raises a Pundit::NotAuthorizedError
you can rescue_from
in your ApplicationController
class ApplicationController < ActionController::Base
rescue_from Pundit::NotAuthorizedError,
with: :user_not_authorized
private
def user_not_authorized
flash[:alert] = "You shall not pass >:("
redirect_to(request.referrer || root_path)
end
end
As you can see, Pundit doesn't do anything you couldn't have easily done yourself.
Remember that all of the policy classes are just plain Ruby classes, which means you can use the same mechanisms you always use to DRY things up.
Since policies are normal ruby classes, they are super easy to test
describe PostPolicy do
subject { PostPolicy.new(user, record) }
context "for a user" do
let(:user) { FactoryGirl.create(:user) }
context "for any kind of record" do
let(:record) { nil }
it { should permit(:index) }
it { should_not permit(:new) }
it { should_not permit(:create) }
end
end
end