Description
#2618 replaced several args.extract_options! call sites with an explicit **options (or **kwargs) parameter, e.g. def use(*names, **options) in lib/grape/dsl/parameters.rb. This changes observable behavior for any caller that passes the trailing options as a literal Hash instead of using the ** splat (or bare key: value keywords), because Ruby 3+ no longer auto-converts a trailing Hash argument into keyword arguments unless it is explicitly double-splatted at the call site.
extract_options! and *args, **options are not equivalent:
def debug1(*args)
options = args.count > 1 ? args.extract_options! : {}
puts args.inspect
puts options.inspect
end
def debug2(*args, **options)
puts args.inspect
puts options.inspect
end
debug1(:key, :object, { k: :v })
# args: [:key, :object]
# options: {:k=>:v}
debug2(:key, :object, { k: :v })
# args: [:key, :object, {:k=>:v}]
# options: {}
With extract_options!, the trailing hash is pulled out of args and treated as options regardless of how it was passed. With *args, **options, a hash literal (or a hash stored in a variable) that isn't double-splatted stays a positional argument and lands in args, while options is silently empty.
This affects any existing caller who builds an options hash and passes it positionally, e.g.:
params do
use :pagination, { some: :option }
end
instead of:
params do
use :pagination, **{ some: :option }
end
The former used to work with extract_options! and silently stops working after #2618, since :pagination and { some: :option } both end up in names and options stays {}.
Steps to reproduce
require 'grape'
class API < Grape::API
params do
use :pagination, { max_per_page: 100 }
end
get '/items' do
params
end
end
Expected behavior
The options hash passed positionally is extracted and forwarded to the named param block, same as before #2618.
Actual behavior
The positional hash is treated as another name in *names instead of being merged into **options, so Params :{max_per_page: 100} not found! is raised (or, more generally, the options are silently dropped).
Additional context
Description
#2618 replaced several
args.extract_options!call sites with an explicit**options(or**kwargs) parameter, e.g.def use(*names, **options)inlib/grape/dsl/parameters.rb. This changes observable behavior for any caller that passes the trailing options as a literalHashinstead of using the**splat (or barekey: valuekeywords), because Ruby 3+ no longer auto-converts a trailingHashargument into keyword arguments unless it is explicitly double-splatted at the call site.extract_options!and*args, **optionsare not equivalent:With
extract_options!, the trailing hash is pulled out ofargsand treated as options regardless of how it was passed. With*args, **options, a hash literal (or a hash stored in a variable) that isn't double-splatted stays a positional argument and lands inargs, whileoptionsis silently empty.This affects any existing caller who builds an options hash and passes it positionally, e.g.:
instead of:
The former used to work with
extract_options!and silently stops working after #2618, since:paginationand{ some: :option }both end up innamesandoptionsstays{}.Steps to reproduce
Expected behavior
The options hash passed positionally is extracted and forwarded to the named param block, same as before #2618.
Actual behavior
The positional hash is treated as another name in
*namesinstead of being merged into**options, soParams :{max_per_page: 100} not found!is raised (or, more generally, the options are silently dropped).Additional context