Pundit for authorization and policy objects

Sarah Mitchell Feb 2026
2 tabs
# app/policies/post_policy.rb
class PostPolicy < ApplicationPolicy
  class Scope < Scope
    def resolve
      if user.admin?
        scope.all
      else
        scope.where(published: true)
          .or(scope.where(user: user))
      end
    end
  end

  def index?
    true  # Everyone can view post listings
  end

  def show?
    record.published? || record.user == user || user.admin?
  end

  def create?
    user.present?
  end

  def update?
    user.present? && (record.user == user || user.admin?)
  end

  def destroy?
    user.present? && (record.user == user || user.admin?)
  end

  def publish?
    user.admin? || (record.user == user && user.can_publish?)
  end

  # Custom permission
  def feature?
    user.admin?
  end

  # Attribute-level authorization
  def permitted_attributes
    if user.admin?
      [:title, :content, :published, :featured, :category_id]
    else
      [:title, :content]
    end
  end
end

# app/policies/application_policy.rb
class ApplicationPolicy
  attr_reader :user, :record

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

  def index?
    false
  end

  def show?
    false
  end

  def create?
    false
  end

  def new?
    create?
  end

  def update?
    false
  end

  def edit?
    update?
  end

  def destroy?
    false
  end

  class Scope
    attr_reader :user, :scope

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

    def resolve
      raise NotImplementedError
    end
  end
end
2 files · ruby Explain with highlit

Pundit provides simple, object-oriented authorization. Policies encapsulate authorization rules in plain Ruby classes. Each model gets a policy class defining who can perform actions. I use Pundit for fine-grained permissions—different users see different data. Policies are easy to test—pure Ruby objects without Rails dependencies. Scopes filter collections based on permissions—users only see authorized records. Pundit integrates seamlessly with controllers via authorize helper. Policy errors raise Pundit::NotAuthorizedError, easily rescuable for proper responses. Policies keep authorization logic out of models and controllers. Understanding policy context—user and record—is key. Pundit scales better than CanCanCan for complex permissions, being more explicit and testable.