{"record":{"id":"69b5bc3d06ed5088","repo":"varvet/pundit","slug":"unable-to-find-policy-find-object-for-obj","errorCode":null,"errorMessage":"unable to find policy `#{find(object)}` for `#{object.inspect}`","messagePattern":"unable to find policy `#(.+?)` for `#(.+?)`","errorType":"exception","errorClass":"NotDefinedError","httpStatus":null,"severity":"error","filePath":"lib/pundit/policy_finder.rb","lineNumber":70,"sourceCode":"    def policy\n      klass = find(object)\n      klass.is_a?(String) ? klass.safe_constantize : klass\n    end\n\n    # @return [Scope{#resolve}] scope class which can resolve to a scope\n    # @raise [NotDefinedError] if scope could not be determined\n    #\n    # @since v0.1.0\n    def scope!\n      scope or raise NotDefinedError, \"unable to find scope `#{find(object)}::Scope` for `#{object.inspect}`\"\n    end\n\n    # @return [Class] policy class with query methods\n    # @raise [NotDefinedError] if policy could not be determined\n    #\n    # @since v0.1.0\n    def policy!\n      policy or raise NotDefinedError, \"unable to find policy `#{find(object)}` for `#{object.inspect}`\"\n    end\n\n    # @return [String] the name of the key this object would have in a params hash\n    #\n    # @since v1.1.0\n    def param_key # rubocop:disable Metrics/AbcSize\n      model = object.is_a?(Array) ? object.last : object\n\n      if model.respond_to?(:model_name)\n        model.model_name.param_key.to_s\n      elsif model.is_a?(Class)\n        model.to_s.demodulize.underscore\n      else\n        model.class.to_s.demodulize.underscore\n      end\n    end\n\n    private","sourceCodeStart":52,"sourceCodeEnd":88,"githubUrl":"https://github.com/varvet/pundit/blob/06318683c960066a2e499341cb372e0ff4540334/lib/pundit/policy_finder.rb#L52-L88","documentation":"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.","triggerScenarios":"`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.","commonSituations":"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.","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."],"exampleFix":"# before\n# app/policies/posts_policy.rb  (wrong constant — Zeitwerk won't map it to PostPolicy)\nclass PostsPolicy < ApplicationPolicy\nend\n\npundit.policy!(post) # NotDefinedError: unable to find policy `PostPolicy`\n\n# after\n# app/policies/post_policy.rb\nclass PostPolicy < ApplicationPolicy\n  def show?\n    true\n  end\nend","handlingStrategy":"validation","validationCode":"policy_class = Pundit::PolicyFinder.new(record).policy # nil instead of raising\n\nif policy_class.nil?\n  # no PostPolicy for this record — create it, override policy_class, or handle explicitly\nelse\n  pundit.policy!(record)\nend","typeGuard":"def policy_defined?(record)\n  !Pundit::PolicyFinder.new(record).policy.nil?\nend\n\n# for the class-based lookup:\ndef policy_defined_for_class?(klass)\n  !Pundit::PolicyFinder.new(klass).policy.nil?\nend","tryCatchPattern":"begin\n  pundit.policy!(record)\nrescue Pundit::NotDefinedError => e\n  # e.message names the constant that failed to constantize, e.g. `PostPolicy`\n  # verify Zeitwerk can load it: Rails.autoloaders.main.reload\n  raise\nend","preventionTips":["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."],"tags":["ruby","pundit","authorization","policy","naming-convention","class-not-found","not-defined-error","zeitwerk"],"backgroundTag":"class-not-found","analyzedSha":"06318683c960066a2e499341cb372e0ff4540334","analyzedAt":"2026-08-21T18:13:24.520Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}