{"record":{"id":"286debabab2485a3","repo":"varvet/pundit","slug":"invalid-policy-scope-class-constructor-is-ca","errorCode":null,"errorMessage":"Invalid #<#{policy_scope_class}> constructor is called","messagePattern":"Invalid #<#(.+?)> constructor is called","errorType":"exception","errorClass":"InvalidConstructorError","httpStatus":null,"severity":"error","filePath":"lib/pundit/context.rb","lineNumber":116,"sourceCode":"    # @!endgroup\n\n    # @!group Scopes\n\n    # Retrieves the policy scope for the given record.\n    #\n    # @see https://github.com/varvet/pundit#scopes\n    # @param scope [Object] the object we're retrieving the policy scope for\n    # @raise [InvalidConstructorError] if the policy constructor called incorrectly\n    # @return [Scope{#resolve}, nil] instance of scope class which can resolve to a scope\n    # @since v2.3.2\n    def policy_scope(scope)\n      policy_scope_class = policy_finder(scope).scope\n      return unless policy_scope_class\n\n      begin\n        policy_scope = policy_scope_class.new(user, pundit_model(scope))\n      rescue ArgumentError\n        raise InvalidConstructorError, \"Invalid #<#{policy_scope_class}> constructor is called\"\n      end\n\n      policy_scope.resolve\n    end\n\n    # Retrieves the policy scope for the given record. Raises if not found.\n    #\n    # @see https://github.com/varvet/pundit#scopes\n    # @param scope [Object] the object we're retrieving the policy scope for\n    # @raise [NotDefinedError] if the policy scope cannot be found\n    # @raise [InvalidConstructorError] if the policy constructor called incorrectly\n    # @return [Scope{#resolve}] instance of scope class which can resolve to a scope\n    # @since v2.3.2\n    def policy_scope!(scope)\n      policy_scope_class = policy_finder(scope).scope!\n\n      begin\n        policy_scope = policy_scope_class.new(user, pundit_model(scope))","sourceCodeStart":98,"sourceCodeEnd":134,"githubUrl":"https://github.com/varvet/pundit/blob/06318683c960066a2e499341cb372e0ff4540334/lib/pundit/context.rb#L98-L134","documentation":"Pundit::Context#policy_scope looks up the record's `X::Scope` class and instantiates it with exactly two positional arguments, `new(user, pundit_model(scope))`. If that constructor call raises ArgumentError — almost always a mismatch between the number of arguments your Scope#initialize accepts and the two Pundit passes — it is re-raised as InvalidConstructorError naming the offending scope class.","triggerScenarios":"`pundit.policy_scope(Post)` (or `Pundit.policy_scope(user, Post)`) where `PostPolicy::Scope#initialize(user)` expects one argument, `initialize(user, scope, context)` requires three, or the initializer is keyword-only (`def initialize(user:, scope:)`, arity 0 positional). Any of these makes `.new(user, model)` raise ArgumentError, which the rescue at context.rb:115 converts.","commonSituations":"Custom base policy scopes that add extra constructor parameters (tenant, context, request); porting scopes from another authorization library with a different initializer signature; refactoring ApplicationPolicy#initialize and forgetting the nested Scope; keyword-args-only initializers introduced during a Ruby 3 upgrade.","solutions":["Change the Scope initializer to accept exactly `(user, scope)`, giving any extra parameters defaults: `def initialize(user, scope, context = nil)`.","If extra data is required, pass it through the user object or a custom policy/scope class set via the model's `policy_class` override, instead of extra constructor args.","When inheriting a custom base, keep `super(user, scope)` so the two-argument contract holds through the chain.","Reproduce the underlying ArgumentError by calling `Pundit::PolicyFinder.new(Post).scope.new(user, Post)` directly — InvalidConstructorError hides the original message, so this shows the real arity problem."],"exampleFix":"# before\nclass PostPolicy < ApplicationPolicy\n  class Scope < ApplicationPolicy::Scope\n    def initialize(user, scope, tenant) # Pundit calls new(user, scope): ArgumentError\n      super(user, scope)\n      @tenant = tenant\n    end\n  end\nend\n\n# after\nclass PostPolicy < ApplicationPolicy\n  class Scope < ApplicationPolicy::Scope\n    def initialize(user, scope, tenant = nil)\n      super(user, scope)\n      @tenant = tenant || user&.tenant\n    end\n  end\nend","handlingStrategy":"validation","validationCode":"def pundit_two_arg_constructor?(klass)\n  return false if klass.nil?\n  arity = klass.instance_method(:initialize).arity\n  arity == 2 || arity == -1 || arity <= -3 # exact 2, splat, or 2 required + optionals\nend\n\nscope_class = Pundit::PolicyFinder.new(record).scope\nresult = pundit.policy_scope(record) if pundit_two_arg_constructor?(scope_class)","typeGuard":"def pundit_scope_safe?(record)\n  scope_class = Pundit::PolicyFinder.new(record).scope\n  return false if scope_class.nil?\n  arity = scope_class.instance_method(:initialize).arity\n  arity == 2 || arity == -1 || arity <= -3\nend","tryCatchPattern":"begin\n  pundit.policy_scope(record)\nrescue Pundit::InvalidConstructorError => e\n  # the original ArgumentError message is masked — reproduce it to see the real arity mismatch:\n  # Pundit::PolicyFinder.new(record).scope.new(pundit.user, record)\n  logger.error(\"#{e.message} — check #{Pundit::PolicyFinder.new(record).scope}#initialize arity\")\n  raise\nend","preventionTips":["Keep every Scope#initialize at exactly `(user, scope)`; give extra parameters defaults instead of required args.","Never use keyword-only initializers in policy or scope classes — pundit always calls `new(user, record)` positionally.","Add a spec that instantiates each policy and its Scope with two arguments; it catches signature drift the moment refactoring introduces it."],"tags":["ruby","pundit","authorization","constructor","arity-mismatch","policy-scope","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"}