varvet/pundit · error · InvalidConstructorError
Invalid #<#{klass}> constructor is called
Error message
Invalid #<#{klass}> constructor is called What it means
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.
Source
Thrown at lib/pundit/context.rb:168
# @api private
# @param record [Object] the object we're retrieving the policy for
# @yield a policy finder if no policy was cached
# @yieldparam [PolicyFinder] policy_finder
# @yieldreturn [#new(user, model)]
# @return [Policy, nil] an instantiated policy
# @raise [InvalidConstructorError] if policy can't be instantated
# @since v2.3.2
def cached_find(record)
policy_cache.fetch(user: user, record: record) do
klass = yield policy_finder(record)
next unless klass
model = pundit_model(record)
begin
klass.new(user, model)
rescue ArgumentError
raise InvalidConstructorError, "Invalid #<#{klass}> constructor is called"
end
end
end
# Return a policy finder for the given record.
#
# @api private
# @return [PolicyFinder]
# @since v2.3.2
def policy_finder(record)
PolicyFinder.new(record)
end
# Given a possibly namespaced record, return the actual record.
#
# @api private
# @since v2.3.2
def pundit_model(record)View on GitHub (pinned to 06318683c9)
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.
Example fix
# before
class PostPolicy # does not inherit ApplicationPolicy
def initialize(user, record, audit_log) # pundit calls new(user, record): ArgumentError
@user = user
@record = record
end
end
# after
class PostPolicy < ApplicationPolicy
# ApplicationPolicy#initialize(user, record) is used as-is
def show?
user.admin? || record.user == user
end
end Defensive patterns
Strategy: validation
Validate before calling
def pundit_policy_constructible?(record) klass = Pundit::PolicyFinder.new(record).policy return false if klass.nil? arity = klass.instance_method(:initialize).arity arity == 2 || arity == -1 || arity <= -3 end policy = pundit.policy(record) if pundit_policy_constructible?(record)
Type guard
def two_arg_policy?(klass) return false if klass.nil? arity = klass.instance_method(:initialize).arity arity == 2 || arity == -1 || arity <= -3 end
Try / catch
begin pundit.policy(record) rescue Pundit::InvalidConstructorError => e # klass is named in the message; see the real failure via: # Pundit::PolicyFinder.new(record).policy.new(pundit.user, record) raise end
Prevention
- 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.
When it happens
Trigger: `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.
Common situations: 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).
Related errors
- Invalid #<#{policy_scope_class}> constructor is called
- unable to find policy `#{find(object)}` for `#{object.inspec
- You must define #resolve in #{self.class}
- unable to find scope `#{find(object)}::Scope` for `#{object.
- 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/6120a2842fccd645.
Report an issue: GitHub.