varvet/pundit · error · NotDefinedError

unable to find scope `#{find(object)}::Scope` for `#{object.

Error message

unable to find scope `#{find(object)}::Scope` for `#{object.inspect}`

What it means

PolicyFinder#scope! resolves the policy class by convention (`#{model}Policy`) and then tries to constantize `#{policy}::Scope`. When that constant does not exist it raises NotDefinedError showing the exact name it searched for. It is the strict variant — `scope` returns nil in the same situation, so this error means the bang API was used and the lookup failed.

Source

Thrown at lib/pundit/policy_finder.rb:62

    # @return [nil, Class] policy class with query methods
    # @see https://github.com/varvet/pundit#policies
    # @example
    #   policy = finder.policy #=> UserPolicy
    #   policy.show? #=> true
    #   policy.update? #=> false
    #
    # @since v0.1.0
    def policy
      klass = find(object)
      klass.is_a?(String) ? klass.safe_constantize : klass
    end

    # @return [Scope{#resolve}] scope class which can resolve to a scope
    # @raise [NotDefinedError] if scope could not be determined
    #
    # @since v0.1.0
    def scope!
      scope or raise NotDefinedError, "unable to find scope `#{find(object)}::Scope` for `#{object.inspect}`"
    end

    # @return [Class] policy class with query methods
    # @raise [NotDefinedError] if policy could not be determined
    #
    # @since v0.1.0
    def policy!
      policy or raise NotDefinedError, "unable to find policy `#{find(object)}` for `#{object.inspect}`"
    end

    # @return [String] the name of the key this object would have in a params hash
    #
    # @since v1.1.0
    def param_key # rubocop:disable Metrics/AbcSize
      model = object.is_a?(Array) ? object.last : object

      if model.respond_to?(:model_name)
        model.model_name.param_key.to_s

View on GitHub (pinned to 06318683c9)

Solutions

  1. Add a nested scope to the policy named in the message: `class Scope; def initialize(user, scope); ...; end; def resolve; ...; end; end`.
  2. If the message shows an unexpected constant name, fix the policy class name/namespace to match convention (model class name + Policy, mirroring the model's namespace).
  3. If you would rather get nil than a raise for uncovered models, call the non-bang `policy_scope` instead.
  4. When overriding lookup, make the model's `policy_class` (or the array namespace form) point at a class that actually contains a nested Scope.

Example fix

# before
class PostPolicy < ApplicationPolicy
  def index?
    false
  end
  # no Scope nested class
end

pundit.policy_scope!(Post) # NotDefinedError: unable to find scope `PostPolicy::Scope`

# after
class PostPolicy < ApplicationPolicy
  class Scope < ApplicationPolicy::Scope
    def resolve
      scope.all
    end
  end
end
Defensive patterns

Strategy: validation

Validate before calling

scope_class = Pundit::PolicyFinder.new(record).scope # non-bang: returns nil instead of raising

if scope_class
  pundit.policy_scope!(record) # or policy_scope — class is known to exist now
else
  # X::Scope missing: fall back, skip, or raise your own domain error
end

Type guard

def policy_scope_defined?(record)
  !Pundit::PolicyFinder.new(record).scope.nil?
end

Try / catch

begin
  pundit.policy_scope!(record)
rescue Pundit::NotDefinedError => e
  raise unless e.message.include?("::Scope") # distinguish scope-missing from policy-missing
  # decide deliberately: unscoped access is usually NOT safe to default
  raise MissingPolicyScopeError, e.message
end

Prevention

When it happens

Trigger: `pundit.policy_scope!(Post)`, `Pundit.policy_scope!(user, Post)`, or `Pundit::PolicyFinder.new(post).scope!` when: PostPolicy exists but has no nested `class Scope`; PostPolicy itself is missing (policy is nil, so `"#{policy}::Scope"` cannot resolve); a namespaced record `[:admin, Post]` lacks `Admin::PostPolicy::Scope`; a `policy_class` override on the model returns a class with no Scope nested inside.

Common situations: Generating a policy with query methods but omitting the nested Scope; namespaced models (Admin::Post) whose policy was scaffolded at top level; Rails/Zeitwerk naming mismatches where app/policies/admin/post_policy.rb defines the wrong constant; using symbols (`policy_scope!(:post)`) with no matching policy; switching from `policy_scope` (silent nil) to `policy_scope!` in a codebase with partially covered policies.

Related errors


AI-assisted analysis of varvet/pundit@06318683c9 (2026-08-21). Data as JSON: /api/errors/be6753698655b514. Report an issue: GitHub.