Skip to content

Instantly share code, notes, and snippets.

@stoffie
Created December 9, 2015 14:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stoffie/2ae2b20b965a2df158db to your computer and use it in GitHub Desktop.
Save stoffie/2ae2b20b965a2df158db to your computer and use it in GitHub Desktop.

"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

Installation

In your Gemfile:

gem "pundit"

Include Pundit in your application controller:

class ApplicationController < ActionController::Base
  include Pundit
end

Policies

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

Conventions

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?

Authorizing

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

Authorizing

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

Helpers

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 %>

Enforcing Authorization

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

Rescuing a denied Authorization

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

Just plain old Ruby

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.

RSpec

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment