Skip to content

Replacing extract_options! with *args, **options in #2618 breaks callers that pass options as a positional Hash #2851

Description

@OuYangJinTing

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions