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
privateView on GitHub (pinned to 06318683c9)
Solutions
- Create the missing policy class: `rails g pundit:policy Post`, which generates app/policies/post_policy.rb with `class PostPolicy < ApplicationPolicy`.
- 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.
- For non-standard mappings, define `def self.policy_class` on the model (or its concern) returning the correct class.
- 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
- Always create the policy when you create the model: make `rails g model` workflows include `rails g pundit:policy`.
- Guard against constant/file mismatch by running `bin/rails zeitwerk:check` in CI — it catches naming errors before pundit's safe_constantize silently returns nil.
- For non-conventional mappings, define `self.policy_class` on the model instead of relying on string suffix rules.
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
- unable to find scope `#{find(object)}::Scope` for `#{object.
- Invalid #<#{klass}> constructor is called
- You must define #resolve in #{self.class}
- Invalid #<#{policy_scope_class}> constructor is called
- No permissions in example metadata, did you forget to wrap w
AI-assisted analysis of varvet/pundit@06318683c9 (2026-08-21).
Data as JSON: /api/errors/69b5bc3d06ed5088.
Report an issue: GitHub.