{"record":{"id":"6120a2842fccd645","repo":"varvet/pundit","slug":"invalid-klass-constructor-is-called","errorCode":null,"errorMessage":"Invalid #<#{klass}> constructor is called","messagePattern":"Invalid #<#(.+?)> constructor is called","errorType":"exception","errorClass":"InvalidConstructorError","httpStatus":null,"severity":"error","filePath":"lib/pundit/context.rb","lineNumber":168,"sourceCode":"    # @api private\n    # @param record [Object] the object we're retrieving the policy for\n    # @yield a policy finder if no policy was cached\n    # @yieldparam [PolicyFinder] policy_finder\n    # @yieldreturn [#new(user, model)]\n    # @return [Policy, nil] an instantiated policy\n    # @raise [InvalidConstructorError] if policy can't be instantated\n    # @since v2.3.2\n    def cached_find(record)\n      policy_cache.fetch(user: user, record: record) do\n        klass = yield policy_finder(record)\n        next unless klass\n\n        model = pundit_model(record)\n\n        begin\n          klass.new(user, model)\n        rescue ArgumentError\n          raise InvalidConstructorError, \"Invalid #<#{klass}> constructor is called\"\n        end\n      end\n    end\n\n    # Return a policy finder for the given record.\n    #\n    # @api private\n    # @return [PolicyFinder]\n    # @since v2.3.2\n    def policy_finder(record)\n      PolicyFinder.new(record)\n    end\n\n    # Given a possibly namespaced record, return the actual record.\n    #\n    # @api private\n    # @since v2.3.2\n    def pundit_model(record)","sourceCodeStart":150,"sourceCodeEnd":186,"githubUrl":"https://github.com/varvet/pundit/blob/06318683c960066a2e499341cb372e0ff4540334/lib/pundit/context.rb#L150-L186","documentation":"Pundit::Context#cached_find backs `policy` and `policy!`: it resolves the policy class for a record, then builds it with `klass.new(user, model)`. If the policy class's constructor raises ArgumentError, pundit re-raises InvalidConstructorError naming that policy class. Policies (and policy caches) must follow the two-argument `(user, record)` initializer contract that ApplicationPolicy establishes.","triggerScenarios":"`pundit.policy(post)`, `pundit.policy!(post)`, or `pundit.authorize(post, query: :show?, policy_class: nil)` (which routes through policy!) where the policy class defines `initialize(user)` with one argument, requires three (`(user, record, logger)`), or uses keyword-only params. The rescue at context.rb:167 converts the ArgumentError. Note: with an explicit `policy_class:` kwarg, authorize calls `policy_class.new` unguarded and raises plain ArgumentError instead.","commonSituations":"Hand-written PORO policies that skipped inheriting ApplicationPolicy and defined a different initializer; custom base policies adding auditing/context parameters; keyword-only initializers after a Ruby 3 upgrade; refactoring initialize signatures without updating the policy cache assumptions (the error also fires inside `policy_cache.fetch`, which can mislead the stack trace).","solutions":["Inherit from ApplicationPolicy so the policy gets its `initialize(user, record)` for free.","If the policy cannot inherit it, define `def initialize(user, record)` yourself; give any extra parameters defaults.","Pass auxiliary objects through the user (e.g. `user.context`) instead of extra constructor arguments.","Instantiate the class manually (`Pundit::PolicyFinder.new(post).policy.new(user, post)`) to see the underlying ArgumentError, since the re-raise drops the original message."],"exampleFix":"# before\nclass PostPolicy # does not inherit ApplicationPolicy\n  def initialize(user, record, audit_log) # pundit calls new(user, record): ArgumentError\n    @user = user\n    @record = record\n  end\nend\n\n# after\nclass PostPolicy < ApplicationPolicy\n  # ApplicationPolicy#initialize(user, record) is used as-is\n  def show?\n    user.admin? || record.user == user\n  end\nend","handlingStrategy":"validation","validationCode":"def pundit_policy_constructible?(record)\n  klass = Pundit::PolicyFinder.new(record).policy\n  return false if klass.nil?\n  arity = klass.instance_method(:initialize).arity\n  arity == 2 || arity == -1 || arity <= -3\nend\n\npolicy = pundit.policy(record) if pundit_policy_constructible?(record)","typeGuard":"def two_arg_policy?(klass)\n  return false if klass.nil?\n  arity = klass.instance_method(:initialize).arity\n  arity == 2 || arity == -1 || arity <= -3\nend","tryCatchPattern":"begin\n  pundit.policy(record)\nrescue Pundit::InvalidConstructorError => e\n  # klass is named in the message; see the real failure via:\n  # Pundit::PolicyFinder.new(record).policy.new(pundit.user, record)\n  raise\nend","preventionTips":["Inherit every policy from ApplicationPolicy so `(user, record)` initialization is inherited, not reimplemented.","If you write a PORO policy, add an arity spec asserting `initialize` accepts two positional arguments.","Remember this raise happens inside `policy_cache.fetch` — when debugging from the backtrace, look past the cache frame to the `klass.new(user, model)` call."],"tags":["ruby","pundit","authorization","constructor","arity-mismatch","policy","invalid-constructor-error"],"backgroundTag":"constructor-arity-mismatch","analyzedSha":"06318683c960066a2e499341cb372e0ff4540334","analyzedAt":"2026-08-21T18:13:24.520Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}