Promote route metadata to Route keyword arguments#2779
Merged
Conversation
Danger ReportNo issues found. |
`version`, `namespace`, `prefix`, `requirements`, `anchor` and `settings` were computed in `Endpoint#prepare_default_route_attributes`, merged into the route options Hash, and read back through `Grape::Router::BaseRoute`'s `def_delegators :@options`. They are route metadata, not part of the user-declared options bag grape-swagger consumes. They are now passed to `Grape::Router::Route` as explicit keyword arguments and exposed as plain readers on `BaseRoute`. `Endpoint#to_routes` computes each as a local and passes `config.route_options` straight through, so `prepare_default_route_attributes` (and the intermediate `default_route_options`/`complete_route_options`) is gone. `Route` forwards the attributes to `super` via `**route_attributes` to keep its signature lean. `route.options` now carries only what was declared for the route; the `@options` delegation is down to `description` plus the documentation `DSL_METHODS`. The readers are unchanged, so grape-swagger (which reads `route.prefix` and `route.settings`) is unaffected. `requirements` and `anchor` can still be declared as route options, so any explicit value stays in `route.options`; the effective value comes from the reader. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ericproulx
force-pushed
the
route_attributes_kwargs
branch
from
July 4, 2026 15:09
329274b to
845e415
Compare
ericproulx
added a commit
that referenced
this pull request
Jul 5, 2026
* Pass http_methods to Grape::Endpoint as a keyword instead of via options `method` was pulled out of the endpoint's `**options` Hash, normalized in place (`@options[:method] = Array(...)`), and renamed to `http_methods` on the `Options` value object purely to dodge the `Object#method` shadow on the generated Data accessor. Expose it directly as a required `http_methods:` keyword on `Grape::Endpoint#initialize` and pass it straight into `Options`, whose field is now genuinely named `http_methods` (no rename, no shadow). The value stays in `config` so endpoint equality — `config == other.config`, which keeps same-path/different-verb endpoints distinct — is unchanged. The endpoint's public `options` Hash no longer carries the redundant `:method` entry; nothing read it, and grape-swagger reads the verb from `route.request_method`. Documented in UPGRADING. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Pass path to Grape::Endpoint as a keyword instead of via options `path` was pulled from the endpoint's `**options` Hash and normalized in place (`@options[:path] = Array(...)`), duplicating the normalization `Options` already does. Expose it as a required `path:` keyword on `Grape::Endpoint#initialize` and pass it straight into `Options`, which stays the single normalized source of truth. `path` remains in `config` because it is load-bearing for route building (`config.path`) and for endpoint equality (`config == other.config`, which keeps same-path/different-verb endpoints distinct). The endpoint's public `options` Hash no longer carries `:path`. Its only reader was an internal test; `route.path` (the compiled pattern) is the public path API and is what grape-swagger already uses. Documented in UPGRADING. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Add mounted_app reader and app: keyword to Grape::Endpoint `app` (the Rack app or Grape API mounted at an endpoint) was reachable only by digging into the public options bag as `endpoint.options[:app]`. Introduce a first-class `mounted_app` reader (returning `config.app`, `nil` for a plain block endpoint) and accept `app:` as an explicit keyword on `Grape::Endpoint#initialize`. Grape's own mount-refresh dedup now reads `endpoint.mounted_app` instead of `options[:app]`. This is additive and non-breaking: `options[:app]` is still populated exactly as before for downstream consumers (e.g. grape-swagger), which can migrate to `mounted_app` before it is eventually dropped from the options Hash. Also adds the previously-missing coverage for the `refresh_already_mounted` dedup path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Rename Grape::Endpoint's for: keyword to api: and expose #api `for` identified the API an endpoint belongs to, but it is a reserved word: it can only reach `Options` through `**rest` and its value can't be referenced as a local. Rename it to `api:`, an explicit keyword on `Grape::Endpoint#initialize`, and drop the `**rest` workaround from `Options`. `api` stays in `config` because it is load-bearing: the endpoint reaches the mounting API's live `configuration` (used by remountable APIs and read lazily at request time) through it, so it cannot be captured at construction. `logger` and the new public `#api` reader are exposed via `Forwardable` delegation to the config value object. The owning API is no longer carried on the endpoint's public options Hash (`endpoint.options[:for]` is gone); use `endpoint.api` instead. Only in-repo reader was one spec. Documented in UPGRADING. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Promote route metadata to Route keyword arguments `version`, `namespace`, `prefix`, `requirements`, `anchor` and `settings` were computed in `Endpoint#prepare_default_route_attributes`, merged into the route options Hash, and read back through `Grape::Router::BaseRoute`'s `def_delegators :@options`. They are route metadata, not part of the user-declared options bag grape-swagger consumes. They are now passed to `Grape::Router::Route` as explicit keyword arguments and exposed as plain readers on `BaseRoute`. `Endpoint#to_routes` computes each as a local and passes `config.route_options` straight through, so `prepare_default_route_attributes` (and the intermediate `default_route_options`/`complete_route_options`) is gone. `Route` forwards the attributes to `super` via `**route_attributes` to keep its signature lean. `route.options` now carries only what was declared for the route; the `@options` delegation is down to `description` plus the documentation `DSL_METHODS`. The readers are unchanged, so grape-swagger (which reads `route.prefix` and `route.settings`) is unaffected. `requirements` and `anchor` can still be declared as route options, so any explicit value stays in `route.options`; the effective value comes from the reader. Also drop the vestigial `:format` member from the endpoint's `Options`. Nothing ever passed `format:` to `Grape::Endpoint.new`, so `config.format` was always `nil`; the format capture comes from the path suffix built by `Grape::Path`, not from `Pattern`'s `format`. `Pattern#initialize` keeps `format:` (now optional) since it remains a valid capture constraint used by external callers. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ericproulx
added a commit
that referenced
this pull request
Jul 5, 2026
The route-metadata promotion (#2779) also dropped the always-nil `:format` member from the endpoint's internal `Options`, but that part was only noted in the commit/PR body. Record it in the CHANGELOG line and add an UPGRADING note: `Grape::Endpoint.new(format:)` now raises instead of being ignored, and `Grape::Router::Pattern#initialize` keeps `format:` as an optional keyword. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ericproulx
added a commit
that referenced
this pull request
Jul 5, 2026
`Pattern#initialize` took a `format:` keyword and, in `extract_capture`, constrained the mustermann `:format` capture with it. But nothing ever passed a non-nil value: the endpoint stopped passing `format:` when the vestigial `Grape::Endpoint::Options` `format` member was removed (#2779), and grape-swagger's helper passes `format: nil`. It was also redundant — a route's `:format` capture comes from the path suffix built by `Grape::Path` (`(.json)` for a specific format, `(.:format)` otherwise), not from the pattern. Drop the keyword and the dead capture branch. `DEFAULT_CAPTURES` still lists `format` (the path-derived capture) and `map_str` still serves `version`, so matching is unchanged. `Grape::Router::Pattern.new(..., format: …)` now raises `unknown keyword: :format`; corrected the UPGRADING note that previously described it as an optional keyword. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ericproulx
added a commit
that referenced
this pull request
Jul 5, 2026
`Pattern#initialize` took a `format:` keyword and, in `extract_capture`, constrained the mustermann `:format` capture with it. But nothing ever passed a non-nil value: the endpoint stopped passing `format:` when the vestigial `Grape::Endpoint::Options` `format` member was removed (#2779), and grape-swagger's helper passes `format: nil`. It was also redundant — a route's `:format` capture comes from the path suffix built by `Grape::Path` (`(.json)` for a specific format, `(.:format)` otherwise), not from the pattern. Drop the keyword and the dead capture branch. `DEFAULT_CAPTURES` still lists `format` (the path-derived capture) and `map_str` still serves `version`, so matching is unchanged. `Grape::Router::Pattern.new(..., format: …)` now raises `unknown keyword: :format`; corrected the UPGRADING note that previously described it as an optional keyword. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
version,namespace,prefix,requirements,anchorandsettingswere computed inEndpoint#prepare_default_route_attributes, merged into the route's options Hash, and read back throughBaseRoute'sdef_delegators :@options. They're route metadata — not part of the user-declared options bag grape-swagger consumes offroute.options.They're now passed to
Grape::Router::Routeas explicit keyword arguments and exposed as plain readers onBaseRoute.Changes
BaseRoute#initializetakesversion:/namespace:/prefix:/requirements:/anchor:/settings:keywords (all defaulting tonil), stored in ivars withattr_readers.:requirements/:anchor/:settingsdropped from thedef_delegators :@optionsline, which is now justdescription+ the documentationDSL_METHODS.Route#initializeforwards them tosupervia**route_attributesso its own signature stays lean.Endpoint#to_routescomputes each as a local and passesconfig.route_optionsstraight through.prepare_default_route_attributes— and the intermediatedefault_route_options/complete_route_options— is deleted.Behavior
The readers are unchanged, so grape-swagger (which uses
route.prefixandroute.settings) is unaffected, and nothing in Grape read these offroute.optionsdirectly.route.optionsnow holds only what was declared for the route.requirementsandanchorcan be supplied as route options (e.g. a mount'sanchor: false), so any explicitly declared value still appears inroute.options; the effective value always comes from the reader. Verified byte-identical to master for a Rack-app mount (route.anchor == false).Also: drop the vestigial
:formatfromEndpoint::OptionsNothing ever passed
format:toGrape::Endpoint.new(not the DSL, not any spec), soconfig.formatwas alwaysnil— a fossil carried over from the pre-Dataoptions[:format], which was also alwaysnil. The(.:format)/(.json)route suffix comes from the path built byGrape::Path, not fromPattern'sformatcapture, so removing it is behavior-identical (confirmed:format :jsonstill yields/x(.json)).Grape::Router::Pattern#initializekeepsformat:but it's now optional (format: nil) — it remains a valid capture constraint that external callers (e.g. grape-swagger's test helper) pass explicitly; the endpoint just stops passing the meaninglessnil.Upgrading
Documented in
UPGRADING.md: use theroute.<attr>readers;route.optionsno longer carries the computedversion/namespace/prefix/settings.🤖 Generated with Claude Code