Convert Figma logo to code with AI

varvet logopundit

Minimal authorization through OO design and pure Ruby classes

8,249
626
8,249
15

Top Related Projects

8,249

Minimal authorization through OO design and pure Ruby classes

The authorization Gem for Ruby on Rails.

23,904

Flexible authentication solution for Rails with Warden.

6,271

Authorization Gem for Ruby on Rails.

Quick Overview

Pundit is a Ruby gem that provides a simple and robust authorization system for Rails applications. It offers a minimal yet powerful approach to defining and enforcing access policies, allowing developers to easily manage permissions and access control in their projects.

Pros

  • Simple and intuitive API for defining authorization policies
  • Easily integrates with Rails applications
  • Encourages separation of concerns by keeping authorization logic separate from models and controllers
  • Highly customizable and extensible

Cons

  • Requires writing separate policy classes for each model, which can lead to a large number of files in complex applications
  • May have a steeper learning curve for developers new to authorization concepts
  • Limited built-in support for role-based access control (though can be implemented with custom logic)

Code Examples

  1. Defining a basic policy:
class PostPolicy < ApplicationPolicy
  def update?
    user.admin? || record.author == user
  end
end

This code defines a policy for the Post model, allowing updates only for admins or the post's author.

  1. Using Pundit in a controller:
class PostsController < ApplicationController
  def update
    @post = Post.find(params[:id])
    authorize @post
    if @post.update(post_params)
      redirect_to @post
    else
      render :edit
    end
  end
end

This example shows how to use Pundit's authorize method in a controller action to check if the current user is allowed to update a post.

  1. Customizing error handling:
class ApplicationController < ActionController::Base
  include Pundit::Authorization

  rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized

  private

  def user_not_authorized
    flash[:alert] = "You are not authorized to perform this action."
    redirect_to(request.referrer || root_path)
  end
end

This code demonstrates how to handle authorization errors by rescuing from Pundit::NotAuthorizedError and providing a custom error message and redirect.

Getting Started

To get started with Pundit, follow these steps:

  1. Add Pundit to your Gemfile:

    gem 'pundit'
    
  2. Run bundle install

  3. Include Pundit in your application controller:

    class ApplicationController < ActionController::Base
      include Pundit::Authorization
    end
    
  4. Generate a policy for a model:

    rails generate pundit:policy post
    
  5. Define authorization rules in the generated policy file (e.g., app/policies/post_policy.rb)

  6. Use authorize @post in your controller actions to enforce the policy rules

Competitor Comparisons

8,249

Minimal authorization through OO design and pure Ruby classes

Pros of Pundit

  • Well-established and widely adopted authorization solution for Ruby on Rails
  • Extensive documentation and community support
  • Flexible policy-based approach for defining authorization rules

Cons of Pundit

  • Learning curve for newcomers to understand policy-based authorization
  • Requires manual setup and configuration for each resource
  • Can lead to code duplication in complex authorization scenarios

Code Comparison

Both repositories are the same project, so there's no code comparison to make. Here's a sample of how Pundit is typically used:

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

  def update?
    @user.admin? || @post.author == @user
  end
end

Summary

Pundit is a popular authorization library for Ruby on Rails applications. It provides a clean and intuitive way to define and manage authorization rules using policy objects. While it offers flexibility and maintainability, it may require some initial setup and learning for developers new to the concept of policy-based authorization. Overall, Pundit remains a solid choice for handling authorization in Rails projects, with strong community support and documentation.

The authorization Gem for Ruby on Rails.

Pros of CanCanCan

  • Simpler syntax for defining abilities
  • Built-in support for role-based authorization
  • Easier to implement complex authorization rules across multiple models

Cons of CanCanCan

  • Can become complex and harder to maintain for large applications
  • Less granular control over individual actions
  • Potential performance issues with large sets of rules

Code Comparison

CanCanCan:

class Ability
  include CanCan::Ability

  def initialize(user)
    can :read, Post
    can :manage, Post, user_id: user.id
  end
end

Pundit:

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

  def read?
    true
  end

  def manage?
    @post.user_id == @user.id
  end
end

Both Pundit and CanCanCan are popular authorization gems for Ruby on Rails applications. CanCanCan offers a more straightforward approach to defining abilities and is particularly useful for role-based authorization. It excels in handling complex rules across multiple models. However, it can become unwieldy in large applications and may have performance issues with extensive rule sets.

Pundit, on the other hand, provides more granular control over individual actions and is generally easier to maintain in larger applications. It follows a more object-oriented approach, which some developers find more intuitive and easier to test.

The choice between the two often depends on the specific needs of the project and the developer's preference for syntax and structure.

23,904

Flexible authentication solution for Rails with Warden.

Pros of Devise

  • Comprehensive authentication solution with built-in features like password recovery and session management
  • Highly customizable with many configuration options and extensible modules
  • Large community and extensive documentation, making it easier to find support and resources

Cons of Devise

  • Can be overkill for simple authentication needs, adding unnecessary complexity
  • Tightly coupled with Rails, making it less flexible for non-standard setups
  • Steeper learning curve due to its extensive feature set and configuration options

Code Comparison

Devise (user registration):

class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
end

Pundit (authorization policy):

class PostPolicy < ApplicationPolicy
  def update?
    user.admin? || record.author == user
  end
end

While Devise focuses on authentication and user management, Pundit specializes in authorization. Devise provides a more comprehensive solution for user authentication, including database integration and various modules. Pundit, on the other hand, offers a simpler and more flexible approach to defining and managing authorization policies.

Devise is better suited for projects requiring full-featured authentication, while Pundit excels in scenarios where fine-grained authorization control is needed. The choice between the two depends on the specific requirements of your application and the level of control you need over authentication and authorization processes.

6,271

Authorization Gem for Ruby on Rails.

Pros of CanCan

  • Simpler setup and configuration for basic authorization needs
  • Built-in helpers for views and controllers
  • Supports both class-level and instance-level permissions

Cons of CanCan

  • Less flexible for complex authorization scenarios
  • Not actively maintained (last major update in 2013)
  • Performance can be an issue with large numbers of rules

Code Comparison

CanCan:

class Ability
  include CanCan::Ability

  def initialize(user)
    can :read, Post
    can :manage, Post, user_id: user.id
  end
end

Pundit:

class PostPolicy
  attr_reader :user, :post

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

  def update?
    user.admin? || post.user_id == user.id
  end
end

Summary

CanCan offers a simpler setup and built-in helpers, making it easier for beginners and small projects. However, Pundit provides more flexibility and better performance for complex authorization scenarios. Pundit is also actively maintained, making it a more future-proof choice for new projects. The code comparison shows that CanCan uses a centralized Ability class, while Pundit employs separate policy classes for each model, allowing for more granular control over permissions.

Convert Figma logo designs to code with AI

Visual Copilot

Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.

Try Visual Copilot

README

Pundit

Main Code Climate Inline docs Gem Version

Pundit provides a set of helpers which guide you in leveraging regular Ruby classes and object oriented design patterns to build a straightforward, robust, and scalable authorization system.

Links:

Sponsored by: Varvet

Varvet logo

Installation

Please note that the README on GitHub is accurate with the latest code on GitHub. You are most likely using a released version of Pundit, so please refer to the documentation for the latest released version of Pundit.

bundle add pundit

Include Pundit::Authorization in your application controller:

class ApplicationController < ActionController::Base
  include Pundit::Authorization
end

Optionally, you can run the generator, which will set up an application policy with some useful defaults for you:

rails g pundit:install

After generating your application policy, restart the Rails server so that Rails can pick up any classes in the new app/policies/ directory.

Policies

Pundit is focused around the notion of policy classes. We suggest that you put these classes in app/policies. This is an example that allows updating a post if the user is an admin, or if the post is unpublished:

class PostPolicy
  attr_reader :user, :post

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

  def update?
    user.admin? || !post.published?
  end
end

As you can see, this is a plain Ruby class. Pundit makes the following assumptions about this class:

  • The class has the same name as some kind of model class, only suffixed with the word "Policy".
  • The first argument is a user. In your controller, Pundit will call the current_user method to retrieve what to send into this argument
  • The second argument is some kind of model object, whose authorization you want to check. This does not need to be an ActiveRecord or even an ActiveModel object, it can be anything really.
  • The class implements some kind of query method, in this case update?. Usually, this will map to the name of a particular controller action.

That's it really.

Usually you'll want to inherit from the application policy created by the generator, or set up your own base class to inherit from:

class PostPolicy < ApplicationPolicy
  def update?
    user.admin? or not record.published?
  end
end

In the generated ApplicationPolicy, the model object is called record.

Supposing that you have an instance of class Post, Pundit now lets you do this in your controller:

def update
  @post = Post.find(params[:id])
  authorize @post
  if @post.update(post_params)
    redirect_to @post
  else
    render :edit
  end
end

The authorize method automatically infers that Post will have a matching PostPolicy class, and instantiates this class, handing in the current user and the given record. It then infers from the action name, that it should call update? on this instance of the policy. In this case, you can imagine that authorize would have done something like this:

unless PostPolicy.new(current_user, @post).update?
  raise Pundit::NotAuthorizedError, "not allowed to PostPolicy#update? this Post"
end

You can pass a second argument to authorize if the name of the permission you want to check doesn't match the action name. For example:

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

You can pass an argument to override the policy class if necessary. For example:

def create
  @publication = find_publication # assume this method returns any model that behaves like a publication
  # @publication.class => Post
  authorize @publication, policy_class: PublicationPolicy
  @publication.publish!
  redirect_to @publication
end

If you don't have an instance for the first argument to authorize, then you can pass the class. For example:

Policy:

class PostPolicy < ApplicationPolicy
  def admin_list?
    user.admin?
  end
end

Controller:

def admin_list
  authorize Post # we don't have a particular post to authorize
  # Rest of controller action
end

authorize returns the instance passed to it, so you can chain it like this:

Controller:

def show
  @user = authorize User.find(params[:id])
end

# return the record even for namespaced policies
def show
  @user = authorize [:admin, User.find(params[:id])]
end

You can easily get a hold of an instance of the policy through the policy method in both the view and controller. This 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 %>

Headless policies

Given there is a policy without a corresponding model / ruby class, you can retrieve it by passing a symbol.

# app/policies/dashboard_policy.rb
class DashboardPolicy
  attr_reader :user

  # `_record` in this example will be :dashboard
  def initialize(user, _record)
    @user = user
  end

  def show?
    user.admin?
  end
end

Note that the headless policy still needs to accept two arguments. The second argument will be the symbol :dashboard in this case, which is what is passed as the record to authorize below.

# In controllers
def show
  authorize :dashboard, :show?
  ...
end
# In views
<% if policy(:dashboard).show? %>
  <%= link_to 'Dashboard', dashboard_path %>
<% end %>

Scopes

Often, you will want to have some kind of view listing records which a particular user has access to. When using Pundit, you are expected to define a class called a policy scope. It can look something like this:

class PostPolicy < ApplicationPolicy
  class Scope
    def initialize(user, scope)
      @user  = user
      @scope = scope
    end

    def resolve
      if user.admin?
        scope.all
      else
        scope.where(published: true)
      end
    end

    private

    attr_reader :user, :scope
  end

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

Pundit makes the following assumptions about this class:

  • The class has the name Scope and is nested under the policy class.
  • The first argument is a user. In your controller, Pundit will call the current_user method to retrieve what to send into this argument.
  • The second argument is a scope of some kind on which to perform some kind of query. It will usually be an ActiveRecord class or a ActiveRecord::Relation, but it could be something else entirely.
  • Instances of this class respond to the method resolve, which should return some kind of result which can be iterated over. For ActiveRecord classes, this would usually be an ActiveRecord::Relation.

You'll probably want to inherit from the application policy scope generated by the generator, or create your own base class to inherit from:

class PostPolicy < ApplicationPolicy
  class Scope < ApplicationPolicy::Scope
    def resolve
      if user.admin?
        scope.all
      else
        scope.where(published: true)
      end
    end
  end

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

You can now use this class from your controller via the policy_scope method:

def index
  @posts = policy_scope(Post)
end

def show
  @post = policy_scope(Post).find(params[:id])
end

Like with the authorize method, you can also override the policy scope class:

def index
  # publication_class => Post
  @publications = policy_scope(publication_class, policy_scope_class: PublicationPolicy::Scope)
end

In this case it is a shortcut for doing:

def index
  @publications = PublicationPolicy::Scope.new(current_user, Post).resolve
end

You can, and are encouraged to, use this method in views:

<% policy_scope(@user.posts).each do |post| %>
  <p><%= link_to post.title, post_path(post) %></p>
<% end %>

Ensuring policies and scopes are used

When you are developing an application with Pundit it can be easy to forget to authorize some action. People are forgetful after all. Since Pundit encourages you to add the authorize call manually to each controller action, it's really easy to miss one.

Thankfully, Pundit has a handy feature which reminds you in case you forget. Pundit tracks whether you have called authorize anywhere in your controller action. Pundit also adds a method to your controllers called verify_authorized. This method will raise an exception if authorize has not yet been called. You should run this method in an after_action hook to ensure that you haven't forgotten to authorize the action. For example:

class ApplicationController < ActionController::Base
  include Pundit::Authorization
  after_action :verify_authorized
end

Likewise, Pundit also adds verify_policy_scoped to your controller. This will raise an exception similar to verify_authorized. However, it tracks if policy_scope is used instead of authorize. This is mostly useful for controller actions like index which find collections with a scope and don't authorize individual instances.

class ApplicationController < ActionController::Base
  include Pundit::Authorization
  after_action :verify_pundit_authorization

  def verify_pundit_authorization
    if action_name == "index"
      verify_policy_scoped
    else
      verify_authorized
    end
  end
end

This verification mechanism only exists to aid you while developing your application, so you don't forget to call authorize. It is not some kind of failsafe mechanism or authorization mechanism. You should be able to remove these filters without affecting how your app works in any way.

Some people have found this feature confusing, while many others find it extremely helpful. If you fall into the category of people who find it confusing then you do not need to use it. Pundit will work fine without using verify_authorized and verify_policy_scoped.

Conditional verification

If you're using verify_authorized in your controllers but need to conditionally bypass verification, you can use skip_authorization. For bypassing verify_policy_scoped, use skip_policy_scope. These are useful in circumstances where you don't want to disable verification for the entire action, but have some cases where you intend to not authorize.

def show
  record = Record.find_by(attribute: "value")
  if record.present?
    authorize record
  else
    skip_authorization
  end
end

Manually specifying policy classes

Sometimes you might want to explicitly declare which policy to use for a given class, instead of letting Pundit infer it. This can be done like so:

class Post
  def self.policy_class
    PostablePolicy
  end
end

Alternatively, you can declare an instance method:

class Post
  def policy_class
    PostablePolicy
  end
end

Plain old Ruby

Pundit is a very small library on purpose, and it doesn't do anything you can't do yourself. There's no secret sauce here. It does as little as possible, and then gets out of your way.

With the few but powerful helpers available in Pundit, you have the power to build a well structured, fully working authorization system without using any special DSLs or funky syntax.

Remember that all of the policy and scope classes are plain Ruby classes, which means you can use the same mechanisms you always use to DRY things up. Encapsulate a set of permissions into a module and include them in multiple policies. Use alias_method to make some permissions behave the same as others. Inherit from a base set of permissions. Use metaprogramming if you really have to.

Generator

Use the supplied generator to generate policies:

rails g pundit:policy post

Closed systems

In many applications, only logged in users are really able to do anything. If you're building such a system, it can be kind of cumbersome to check that the user in a policy isn't nil for every single permission. Aside from policies, you can add this check to the base class for scopes.

We suggest that you define a filter that redirects unauthenticated users to the login page. As a secondary defence, if you've defined an ApplicationPolicy, it might be a good idea to raise an exception if somehow an unauthenticated user got through. This way you can fail more gracefully.

class ApplicationPolicy
  def initialize(user, record)
    raise Pundit::NotAuthorizedError, "must be logged in" unless user
    @user   = user
    @record = record
  end

  class Scope
    attr_reader :user, :scope

    def initialize(user, scope)
      raise Pundit::NotAuthorizedError, "must be logged in" unless user
      @user = user
      @scope = scope
    end
  end
end

NilClassPolicy

To support a null object pattern you may find that you want to implement a NilClassPolicy. This might be useful where you want to extend your ApplicationPolicy to allow some tolerance of, for example, associations which might be nil.

class NilClassPolicy < ApplicationPolicy
  class Scope < ApplicationPolicy::Scope
    def resolve
      raise Pundit::NotDefinedError, "Cannot scope NilClass"
    end
  end

  def show?
    false # Nobody can see nothing
  end
end

Rescuing a denied Authorization in Rails

Pundit raises a Pundit::NotAuthorizedError you can rescue_from in your ApplicationController. You can customize the user_not_authorized method in every controller.

class ApplicationController < ActionController::Base
  include Pundit::Authorization

  rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized

  private

  def user_not_authorized
    flash[:alert] = "You are not authorized to perform this action."
    redirect_back_or_to(root_path)
  end
end

Alternatively, you can globally handle Pundit::NotAuthorizedError's by having rails handle them as a 403 error and serving a 403 error page. Add the following to application.rb:

config.action_dispatch.rescue_responses["Pundit::NotAuthorizedError"] = :forbidden

Creating custom error messages

NotAuthorizedErrors provide information on what query (e.g. :create?), what record (e.g. an instance of Post), and what policy (e.g. an instance of PostPolicy) caused the error to be raised.

One way to use these query, record, and policy properties is to connect them with I18n to generate error messages. Here's how you might go about doing that.

class ApplicationController < ActionController::Base
 rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized

 private

 def user_not_authorized(exception)
   policy_name = exception.policy.class.to_s.underscore

   flash[:error] = t "#{policy_name}.#{exception.query}", scope: "pundit", default: :default
   redirect_back_or_to(root_path)
 end
end
en:
 pundit:
   default: 'You cannot perform this action.'
   post_policy:
     update?: 'You cannot edit this post!'
     create?: 'You cannot create posts!'

This is an example. Pundit is agnostic as to how you implement your error messaging.

Manually retrieving policies and scopes

Sometimes you want to retrieve a policy for a record outside the controller or view. For example when you delegate permissions from one policy to another.

You can easily retrieve policies and scopes like this:

Pundit.policy!(user, post)
Pundit.policy(user, post)

Pundit.policy_scope!(user, Post)
Pundit.policy_scope(user, Post)

The bang methods will raise an exception if the policy does not exist, whereas those without the bang will return nil.

Customize Pundit user

On occasion, your controller may be unable to access current_user, or the method that should be invoked by Pundit may not be current_user. To address this, you can define a method in your controller named pundit_user.

def pundit_user
  User.find_by_other_means
end

Policy Namespacing

In some cases it might be helpful to have multiple policies that serve different contexts for a resource. A prime example of this is the case where User policies differ from Admin policies. To authorize with a namespaced policy, pass the namespace into the authorize helper in an array:

authorize(post)                   # => will look for a PostPolicy
authorize([:admin, post])         # => will look for an Admin::PostPolicy
authorize([:foo, :bar, post])     # => will look for a Foo::Bar::PostPolicy

policy_scope(Post)                # => will look for a PostPolicy::Scope
policy_scope([:admin, Post])      # => will look for an Admin::PostPolicy::Scope
policy_scope([:foo, :bar, Post])  # => will look for a Foo::Bar::PostPolicy::Scope

If you are using namespaced policies for something like Admin views, it can be useful to override the policy_scope and authorize helpers in your AdminController to automatically apply the namespacing:

class AdminController < ApplicationController
  def policy_scope(scope)
    super([:admin, scope])
  end

  def authorize(record, query = nil)
    super([:admin, record], query)
  end
end

class Admin::PostController < AdminController
  def index
    policy_scope(Post)
  end

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

Additional context

Pundit strongly encourages you to model your application in such a way that the only context you need for authorization is a user object and a domain model that you want to check authorization for. If you find yourself needing more context than that, consider whether you are authorizing the right domain model, maybe another domain model (or a wrapper around multiple domain models) can provide the context you need.

Pundit does not allow you to pass additional arguments to policies for precisely this reason.

However, in very rare cases, you might need to authorize based on more context than just the currently authenticated user. Suppose for example that authorization is dependent on IP address in addition to the authenticated user. In that case, one option is to create a special class which wraps up both user and IP and passes it to the policy.

class UserContext
  attr_reader :user, :ip

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

class ApplicationController
  include Pundit::Authorization

  def pundit_user
    UserContext.new(current_user, request.ip)
  end
end

Strong parameters

In Rails, mass-assignment protection is handled in the controller. With Pundit you can control which attributes a user has access to update via your policies. You can set up a permitted_attributes method in your policy like this:

# app/policies/post_policy.rb
class PostPolicy < ApplicationPolicy
  def permitted_attributes
    if user.admin? || user.owner_of?(post)
      [:title, :body, :tag_list]
    else
      [:tag_list]
    end
  end
end

You can now retrieve these attributes from the policy:

# app/controllers/posts_controller.rb
class PostsController < ApplicationController
  def update
    @post = Post.find(params[:id])
    if @post.update(post_params)
      redirect_to @post
    else
      render :edit
    end
  end

  private

  def post_params
    params.require(:post).permit(policy(@post).permitted_attributes)
  end
end

However, this is a bit cumbersome, so Pundit provides a convenient helper method:

# app/controllers/posts_controller.rb
class PostsController < ApplicationController
  def update
    @post = Post.find(params[:id])
    if @post.update(permitted_attributes(@post))
      redirect_to @post
    else
      render :edit
    end
  end
end

If you want to permit different attributes based on the current action, you can define a permitted_attributes_for_#{action} method on your policy:

# app/policies/post_policy.rb
class PostPolicy < ApplicationPolicy
  def permitted_attributes_for_create
    [:title, :body]
  end

  def permitted_attributes_for_edit
    [:body]
  end
end

If you have defined an action-specific method on your policy for the current action, the permitted_attributes helper will call it instead of calling permitted_attributes on your controller.

If you need to fetch parameters based on namespaces different from the suggested one, override the below method, in your controller, and return an instance of ActionController::Parameters.

def pundit_params_for(record)
  params.require(PolicyFinder.new(record).param_key)
end

For example:

# If you don't want to use require
def pundit_params_for(record)
  params.fetch(PolicyFinder.new(record).param_key, {})
end

# If you are using something like the JSON API spec
def pundit_params_for(_record)
  params.fetch(:data, {}).fetch(:attributes, {})
end

RSpec

Policy Specs

[!TIP] An alternative approach to Pundit policy specs is scoping them to a user context as outlined in this excellent post and implemented in the third party pundit-matchers gem.

Pundit includes a mini-DSL for writing expressive tests for your policies in RSpec. Require pundit/rspec in your spec_helper.rb:

require "pundit/rspec"

Then put your policy specs in spec/policies, and make them look somewhat like this:

describe PostPolicy do
  subject { described_class }

  permissions :update?, :edit? do
    it "denies access if post is published" do
      expect(subject).not_to permit(User.new(admin: false), Post.new(published: true))
    end

    it "grants access if post is published and user is an admin" do
      expect(subject).to permit(User.new(admin: true), Post.new(published: true))
    end

    it "grants access if post is unpublished" do
      expect(subject).to permit(User.new(admin: false), Post.new(published: false))
    end
  end
end

Custom matcher description

By default rspec includes an inspected user and record in the matcher description, which might become overly verbose:

PostPolicy
  update? and show?
    is expected to permit #<User:0x0000000104aefd80> and #<Post:0x0000000104aef8d0 @user=#<User:0x0000000104aefd80>>

You can override the default description with a static string, or a block:

# static alternative: Pundit::RSpec::Matchers.description = "permit the user"
Pundit::RSpec::Matchers.description = ->(user, record) do
  "permit user with role #{user.role} to access record with ID #{record.id}"
end

Which would make for a less chatty output:

PostPolicy
  update? and show?
    is expected to permit user with role admin to access record with ID 130

Focus Support

If your RSpec config has filter_run_when_matching :focus, you may tag the permissions helper like so:

permissions :show?, :focus do

Scope Specs

Pundit does not provide a DSL for testing scopes. Test them like you would a regular Ruby class!

Linting with RuboCop RSpec

When you lint your RSpec spec files with rubocop-rspec, it will fail to properly detect RSpec constructs that Pundit defines, permissions. Make sure to use rubocop-rspec 2.0 or newer and add the following to your .rubocop.yml:

inherit_gem:
  pundit: config/rubocop-rspec.yml

External Resources

Other implementations

License

Licensed under the MIT license, see the separate LICENSE.txt file.