varvet/pundit · error · NotDefinedError

unable to find policy `#{find(object)}` for `#{object.inspec

Error message

unable to find policy `#{find(object)}` for `#{object.inspect}`

What it means

PolicyFinder#policy! derives the policy class for a record — object's class name (or model_name), plus the "Policy" suffix, honoring Array namespaces and `policy_class` overrides — and constantizes it. When constantization fails it raises NotDefinedError with the name it looked for, e.g. `unable to find policy `PostPolicy` for `#<Post ...>`. `policy` returns nil in the same case; only the bang variant and `authorize` (which routes through policy!) raise.

Source

Thrown at lib/pundit/policy_finder.rb:70

    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
      elsif model.is_a?(Class)
        model.to_s.demodulize.underscore
      else
        model.class.to_s.demodulize.underscore
      end
    end

    private

View on GitHub (pinned to 06318683c9)

Solutions

  1. Create the missing policy class: `rails g pundit:policy Post`, which generates app/policies/post_policy.rb with `class PostPolicy < ApplicationPolicy`.
  2. If the policy exists, make the constant path match exactly: file app/policies/post_policy.rb must define `PostPolicy`; namespaced records like `[:admin, post]` need `Admin::PostPolicy` in app/policies/admin/post_policy.rb.
  3. For non-standard mappings, define `def self.policy_class` on the model (or its concern) returning the correct class.
  4. Use the non-bang `policy`/`policy_scope` APIs where absence is expected and you want nil instead of an exception.

Example fix

# before
# app/policies/posts_policy.rb  (wrong constant — Zeitwerk won't map it to PostPolicy)
class PostsPolicy < ApplicationPolicy
end

pundit.policy!(post) # NotDefinedError: unable to find policy `PostPolicy`

# after
# app/policies/post_policy.rb
class PostPolicy < ApplicationPolicy
  def show?
    true
  end
end
Defensive patterns

Strategy: validation

Validate before calling

policy_class = Pundit::PolicyFinder.new(record).policy # nil instead of raising

if policy_class.nil?
  # no PostPolicy for this record — create it, override policy_class, or handle explicitly
else
  pundit.policy!(record)
end

Type guard

def policy_defined?(record)
  !Pundit::PolicyFinder.new(record).policy.nil?
end

# for the class-based lookup:
def policy_defined_for_class?(klass)
  !Pundit::PolicyFinder.new(klass).policy.nil?
end

Try / catch

begin
  pundit.policy!(record)
rescue Pundit::NotDefinedError => e
  # e.message names the constant that failed to constantize, e.g. `PostPolicy`
  # verify Zeitwerk can load it: Rails.autoloaders.main.reload
  raise
end

Prevention

When it happens

Trigger: `pundit.policy!(post)`, `Pundit.authorize(user, post, :show?)`, or `Pundit::PolicyFinder.new(post).policy!` when no `PostPolicy` constant exists: the policy file was never created, is misspelled (`PostsPolicy`), sits in the wrong namespace (`[:admin, post]` but only top-level PostPolicy, or vice versa), or the model's custom `model_name`/`policy_class` points at a constant that is not defined.

Common situations: New model without `rails g pundit:policy`; Zeitwerk failing to load app/policies/post_policy.rb because the file name or module nesting does not match the constant (classic `uninitialized constant` masked as NotDefinedError); mountable engines where developers forget the module namespace; passing a symbol like `:post` when only some policies exist; inheriting a codebase where policy coverage is partial and a controller calls the strict variant.

Related errors


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