Skip to content
Go back

Namespaced Pundit Policies Without the Repetition Racket

Edit page

Need expert help solving your business and technical challenges? I bridge the gap between business requirements and technical implementation to deliver results-driven solutions.

Book a consultation today and let's transform your problems into opportunities.

If you’re using Pundit in a Rails app with namespaced policies — for example, Admin::PostPolicy — you’ve probably seen the official recommendation that goes something like this:

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

Yeah, it works. But repeating that in every base controller gets old fast — and feels a bit noisy.

Let’s clean it up.

Here’s a small concern you can drop into app/controllers/concerns/namespaced_policy.rb:

# Controller concern for namespaced Pundit policies
#
# Usage:
#   include NamespacedPolicy::Policy(:users)
#   include NamespacedPolicy::Policy(:admin)
module NamespacedPolicy
  def self.Policy(scope)
    Module.new do
      define_method :policy_scope do |scope_class|
        super([scope, scope_class])
      end

      define_method :authorize do |record, query = nil|
        super([scope, record], query)
      end

      private :policy_scope, :authorize
    end
  end
end

And now your controllers stay nice and tidy:

class AdminController < ApplicationController
  include NamespacedPolicy::Policy(:admin)
end

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

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

Much cleaner. Less boilerplate. Your future self will thank you.


Edit page
Share this post on:

Next Post
Kamal Deployment: The Newest Form of Self-Torture