ruby-grape/grape · error · ArgumentError
oneof: each variant must be a Proc
Error message
oneof: each variant must be a Proc
What it means
Each `oneof:` variant must be a Proc because Grape evaluates it in a scratch ParamsScope (OneofCollector) to capture its validators. Symbols, strings, hashes, or method names cannot be evaluated that way, so `variants.all?(Proc)` fails and ArgumentError is raised at definition time. Use `types:` for plain type unions.
Source
Thrown at lib/grape/validations/params_scope.rb:408
# here is correct — the real mounted instance will replay this step
# with the actual type value.
return unless coerce_options.type
validate('coerce', coerce_options, attrs, spec.required?, spec.shared_opts)
end
# Translate a `oneof: [proc, proc, ...]` declaration into a list of
# captured validator arrays — one array per variant. Each variant's
# block is evaluated in its own +ParamsScope+ backed by an
# {OneofCollector} so the full params DSL is available inside variants
# and the resulting validators are kept out of the real API's
# registration list.
def process_oneof!(validations)
raise ArgumentError, 'oneof: requires type: Hash' unless validations[:type] == Hash
variants = validations[:oneof]
raise ArgumentError, 'oneof: must be a non-empty Array of blocks' unless variants.is_a?(Array) && variants.any?
raise ArgumentError, 'oneof: each variant must be a Proc' unless variants.all?(Proc)
validations[:oneof] = variants.map { |block| OneofCollector.collect(block) }
end
def validate(type, options, attrs, required, opts)
validator_class = Validations.require_validator(type)
validator_instance = validator_class.new(
attrs,
options,
required,
self,
opts
)
@api.inheritable_setting.add_validation(validator_instance)
end
def all_element_blank?(scoped_params)
scoped_params.respond_to?(:all?) && scoped_params.all?(&:blank?)View on GitHub (pinned to 22d7975629)
Solutions
- Express each variant as a lambda/proc: `oneof: [-> { requires :name, type: String }, -> { requires :id, type: Integer }]`
- Wrap named methods as `-> { by_name_variant }` so the variant is still a Proc
- For plain type unions (not structural alternatives), use `types: [Integer, String]`, not `oneof:`
Example fix
# before
requires :filter, type: Hash, oneof: [:by_name, :by_id]
# after
requires :filter, type: Hash, oneof: [
-> { requires :name, type: String },
-> { requires :id, type: Integer }
] Defensive patterns
Strategy: type-guard
Validate before calling
def oneof_variants_valid?(opts) variants = opts[:oneof] opts[:type] == Hash && variants.is_a?(Array) && !variants.empty? && variants.all?(Proc) end
Type guard
def oneof_shape?(opts) opts[:type] == Hash && opts[:oneof].is_a?(Array) && opts[:oneof].any? && opts[:oneof].all?(Proc) end return unless oneof_shape?(filter_opts)
Try / catch
begin
requires :filter, type: Hash, oneof: variant_list
rescue ArgumentError => e
raise "oneof variants must be procs (got #{variant_list.map(&:class).inspect}): #{e.message}"
end Prevention
- Build variant lists as lambdas from the start: `VARIANTS = [-> { ... }, -> { ... }].freeze`
- Never mix symbols or hashes into oneof lists — wrap named methods as `-> { method_name }`
- Use `types:` for plain type unions so proc shapes never come into question
When it happens
Trigger: `oneof: [:by_name, :by_id]` (symbols); `oneof: ['name', 'id']` (strings); `oneof: [-> { requires :id }, { type: String }]` (mixed proc and hash).
Common situations: Expecting `oneof:` to accept a plain list of type names the way `types:` does; refactoring named methods into variants without converting them to lambdas.
Related errors
- oneof: requires type: Hash
- oneof: must be a non-empty Array of blocks
- :type may not be supplied with :types
- must supply type for coerce_with
- coerce_with disallowed for type: JSON
AI-assisted analysis of ruby-grape/grape@22d7975629 (2026-08-21).
Data as JSON: /api/errors/07f7a46ab6a97ce0.
Report an issue: GitHub.