From 8891bf317fc383249140b6cff4066c8fa8778033 Mon Sep 17 00:00:00 2001 From: Eric Proulx Date: Sun, 24 May 2026 21:51:30 +0200 Subject: [PATCH] Hoist `using:` / `except:` to explicit kwargs on requires / optional MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `Grape::DSL::Parameters#requires` and `#optional` both treat `:using` (and the `:except` it pairs with) as a method-local early-return signal: when `:using` is set, the method returns through `require_required_and_optional_fields` / `require_optional_fields` and the keys never flow into `validate_attributes` or the validator/scope machinery. Promote them out of the `**opts` Hash into explicit kwargs: def requires(*attrs, using: nil, except: nil, **opts, &block) def optional(*attrs, using: nil, except: nil, **opts, &block) The signature now self-documents the using/except contract instead of burying it in opts, the `if using` guard drops one Hash lookup, and the `opts` Hash that flows into `validate_attributes` is "validator-bound kwargs only" — easier to reason about what propagates downstream. Fully back-compat: Ruby kwarg matching captures `using:` / `except:` before the `**opts` splat, so existing call sites (`requires :all, using: docs`, `optional :all, except: %i[…], using: docs`, etc.) keep working unchanged. The unrelated `:except_values` validator option (consumed by `Grape::Validations::ValidationsSpec`) is a different key and is unaffected. Matches the recent kwargs-hoisting pattern in #2723 (`desc`) and #2728 (`auth` / `http_basic` / `http_digest`). Co-Authored-By: Claude Opus 4.7 (1M context) --- CHANGELOG.md | 1 + lib/grape/dsl/parameters.rb | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c579e464..b7614921a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,7 @@ * [#2733](https://github.com/ruby-grape/grape/pull/2733): Drop the dead `active_support/core_ext/hash/reverse_merge` require; call `ActiveSupport::HashWithIndifferentAccess.new(...)` directly at call sites - [@ericproulx](https://github.com/ericproulx). * [#2734](https://github.com/ruby-grape/grape/pull/2734): Extract `options_route_enabled` from the Endpoint options Hash into a dedicated `attr_accessor` - [@ericproulx](https://github.com/ericproulx). * [#2736](https://github.com/ruby-grape/grape/pull/2736): Collapse `Endpoint#run_validators` rescue branches via `ValidationErrors` flatten; `ValidationErrors#initialize` keyword renamed `errors:` → `exceptions:` - [@ericproulx](https://github.com/ericproulx). +* [#2746](https://github.com/ruby-grape/grape/pull/2746): Hoist `using:` / `except:` from `**opts` to explicit kwargs on `DSL::Parameters#requires` and `#optional` - [@ericproulx](https://github.com/ericproulx). * Your contribution here. #### Fixes diff --git a/lib/grape/dsl/parameters.rb b/lib/grape/dsl/parameters.rb index e298a747d..babb10d18 100644 --- a/lib/grape/dsl/parameters.rb +++ b/lib/grape/dsl/parameters.rb @@ -122,11 +122,11 @@ def use(*names, **options) # requires :name, type: String # end # end - def requires(*attrs, **opts, &block) + def requires(*attrs, using: nil, except: nil, **opts, &block) opts[:presence] = { value: true, message: opts[:message] } opts = @group.deep_merge(opts) if @group - return require_required_and_optional_fields(attrs.first, using: opts[:using], except: opts[:except]) if opts[:using] + return require_required_and_optional_fields(attrs.first, using:, except:) if using validate_attributes(attrs, **opts, &block) block ? new_scope(attrs.first, type: opts[:type], as: opts[:as], &block) : push_declared_params(attrs, as: opts[:as]) @@ -136,7 +136,7 @@ def requires(*attrs, **opts, &block) # endpoint. # @param (see #requires) # @option (see #requires) - def optional(*attrs, **opts, &block) + def optional(*attrs, using: nil, except: nil, **opts, &block) type = opts[:type] opts = @group.deep_merge(opts) if @group @@ -146,7 +146,7 @@ def optional(*attrs, **opts, &block) raise Grape::Exceptions::UnsupportedGroupType unless Grape::Validations::Types.group?(type) end - return require_optional_fields(attrs.first, using: opts[:using], except: opts[:except]) if opts[:using] + return require_optional_fields(attrs.first, using:, except:) if using validate_attributes(attrs, **opts, &block) block ? new_scope(attrs.first, type: opts[:type], as: opts[:as], optional: true, &block) : push_declared_params(attrs, as: opts[:as])