Pundit Authorization

Pundit is a simple, flexible authorization library for Ruby on Rails. This recipe shows how to integrate Pundit authorization into your Operandi.

Why Use Pundit with Services?

  • Centralized authorization: Keep authorization logic in policy classes

  • Consistent patterns: Same authorization approach across controllers and services

  • Testable: Policies are easy to unit test

  • Reusable: Services can be called from controllers, jobs, or other services with consistent authorization

Basic Setup

1. Create an Authorization Concern

# app/services/concerns/authorize_user.rb
module AuthorizeUser
  extend ActiveSupport::Concern

  included do
    arg :current_user, type: User, optional: true, context: true
  end

  # Authorize an action on a record or class
  def auth(record, action)
    policy = policy(record)
    
    unless policy.public_send(action)
      errors.add(:authorization, "You are not authorized to perform this action")
    end
  end
  alias_method :authorize!, :auth

  # Get permitted attributes for an action
  def permitted_attributes(record, action = nil)
    policy = policy(record)
    
    method_name = if action
      "permitted_attributes_for_#{action}"
    else
      "permitted_attributes"
    end

    if policy.respond_to?(method_name)
      attributes = policy.public_send(method_name)
      params.require(param_key(record)).permit(*attributes)
    else
      raise Pundit::NotDefinedError, "#{method_name} not defined in #{policy.class}"
    end
  end

  private

  def policy(record)
    Pundit.policy!(current_user, record)
  end

  def param_key(record)
    if record.is_a?(Class)
      record.model_name.param_key
    else
      record.model_name.param_key
    end
  end
end

2. Include in ApplicationService

Usage Examples

Authorizing Actions

Authorizing on a Class (for Create actions)

Using Permitted Attributes

Policy Example

Integration with CRUD Services

Combine Pundit with the CRUD recipe for powerful, authorized services:

Handling Authorization Failures

Option 1: Collect as Errors (Default)

Option 2: Raise Exceptions

Option 3: Custom Error Handling

Testing Services with Authorization

Skipping Authorization

For system-level operations or background jobs where authorization isn't needed:

Or create a "system" flag:

What's Next?

Return to the recipes overview:

Back to Recipes

Last updated