ruby 143 lines · 3 tabs

Authorizing Controller Actions with Pundit Policy Objects in Rails

Shared by codesnips Aug 2026
3 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
3 files · ruby Explain with highlit

This snippet shows how a Pundit-style authorization layer is wired into a Rails app across three collaborating files: the base policy that all policies inherit from, a concrete ArticlePolicy, and the ArticlesController that invokes it. The central idea of the policy object pattern is that authorization logic — the answer to "can this user perform this action on this record?" — lives in a plain Ruby class dedicated to one resource, keeping controllers thin and business rules testable in isolation.

In ApplicationPolicy, the base class captures the two arguments every Pundit policy receives: the user and the record under consideration. The default predicate methods (index?, show?, create?, and so on) all return false, following a secure-by-default posture where nothing is permitted unless a subclass explicitly opts in. The nested Scope class encapsulates the other half of authorization: filtering collections down to the records a user is allowed to see, rather than checking one record at a time.

ArticlePolicy overrides those predicates with real rules. show? allows access to published articles or to the owner; update? and destroy? delegate to a private owner_or_admin? helper so ownership logic is defined once. Its inner Scope#resolve narrows the query for non-admins to published articles plus the user's own drafts using an or chain, which is exactly how per-tenant or per-owner visibility is enforced at the database level.

In ArticlesController, authorize @article raises Pundit::NotAuthorizedError when a predicate returns false, and policy_scope(Article) runs the scope resolver so the index only ever loads permitted rows. The rescue_from handler converts that exception into a flash message and redirect, giving a consistent denial response. permitted_attributes even lets the policy declare which params are assignable, so mass-assignment rules track the same object.

The trade-off is discipline: every collection must go through policy_scope and every member action through authorize, or records leak. The verify_authorized and verify_policy_scoped callbacks exist precisely to catch that mistake in development. This pattern shines once authorization rules grow beyond a single if current_user.admin? check.


Related snips

Share this code

Here's the card — post it anywhere.

Authorizing Controller Actions with Pundit Policy Objects in Rails — share card
Link copied