ruby 161 lines · 4 tabs

Enforcing Controller Authorization with a Pundit-Style Policy Object in Rails

Shared by codesnips Aug 2026
4 tabs
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 update?
    false
  end

  def destroy?
    false
  end

  class Scope
    attr_reader :user, :scope

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

    def resolve
      raise NotImplementedError, "#{self.class} must implement #resolve"
    end
  end
end
4 files · ruby Explain with highlit

This snippet shows how a Pundit-style authorization layer is wired into a Rails controller so that every sensitive action is guarded by an explicit policy object rather than ad-hoc if checks scattered through the code. The pattern separates who can do what (the policy) from how the request flows (the controller), which keeps authorization logic testable in isolation and consistent across actions.

In ApplicationPolicy, a small base class captures the two things every policy needs: the user performing the action and the record being acted on. Each permission is a predicate method (update?, destroy?) that returns a boolean, and the default answers are false so that a missing rule fails closed — a critical security property. The nested Scope class handles collection-level filtering, letting index actions narrow what a user is even allowed to see.

ArticlePolicy subclasses it and encodes the real rules: admins can do anything, authors can edit or delete their own articles, and resolve limits non-admins to their own records. Because these are plain Ruby objects with no Rails coupling, they can be unit-tested by instantiating them directly with a user and a record.

Authorizable is a controller concern that provides the glue. authorize infers the policy class from the record's class name, instantiates it, and raises NotAuthorizedError unless the named query method returns true. policy_scope does the analogous thing for collections. The rescue_from handler translates that exception into a 403 response and a flash message, so an authorization failure never leaks data or 500s. verify_authorized is an after_action guard that raises if a request completed without ever calling authorize — this catches the dangerous case where a developer forgets to protect a new action.

Finally, ArticlesController shows the payoff: authorize @article is a single, readable line before each mutating operation, and policy_scope(Article) replaces a manual where in index. The controller no longer knows the rules; it just asks. This trade of a little indirection for centralized, fail-closed, unit-testable authorization is why the policy-object pattern scales far better than inline checks as an application grows.


Related snips

Share this code

Here's the card — post it anywhere.

Enforcing Controller Authorization with a Pundit-Style Policy Object in Rails — share card
Link copied