Skip to content

Promote route metadata to Route keyword arguments#2779

Merged
dblock merged 1 commit into
endpoint_api_kwargfrom
route_attributes_kwargs
Jul 5, 2026
Merged

Promote route metadata to Route keyword arguments#2779
dblock merged 1 commit into
endpoint_api_kwargfrom
route_attributes_kwargs

Conversation

@ericproulx

@ericproulx ericproulx commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Stacked on #2778#2777#2776#2775#2774 — review/merge those first. The base auto-retargets as the stack lands.

Summary

version, namespace, prefix, requirements, anchor and settings were computed in Endpoint#prepare_default_route_attributes, merged into the route's options Hash, and read back through BaseRoute's def_delegators :@options. They're route metadata — not part of the user-declared options bag grape-swagger consumes off route.options.

They're now passed to Grape::Router::Route as explicit keyword arguments and exposed as plain readers on BaseRoute.

Changes

  • BaseRoute#initialize takes version:/namespace:/prefix:/requirements:/anchor:/settings: keywords (all defaulting to nil), stored in ivars with attr_readers. :requirements/:anchor/:settings dropped from the def_delegators :@options line, which is now just description + the documentation DSL_METHODS.
  • Route#initialize forwards them to super via **route_attributes so its own signature stays lean.
  • Endpoint#to_routes computes each as a local and passes config.route_options straight through. prepare_default_route_attributes — and the intermediate default_route_options/complete_route_options — is deleted.

Behavior

The readers are unchanged, so grape-swagger (which uses route.prefix and route.settings) is unaffected, and nothing in Grape read these off route.options directly. route.options now holds only what was declared for the route.

requirements and anchor can be supplied as route options (e.g. a mount's anchor: false), so any explicitly declared value still appears in route.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 :format from Endpoint::Options

Nothing ever passed format: to Grape::Endpoint.new (not the DSL, not any spec), so config.format was always nil — a fossil carried over from the pre-Data options[:format], which was also always nil. The (.:format)/(.json) route suffix comes from the path built by Grape::Path, not from Pattern's format capture, so removing it is behavior-identical (confirmed: format :json still yields /x(.json)).

Grape::Router::Pattern#initialize keeps format: 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 meaningless nil.

Upgrading

Documented in UPGRADING.md: use the route.<attr> readers; route.options no longer carries the computed version/namespace/prefix/settings.

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown

Danger Report

No issues found.

View run

`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
ericproulx force-pushed the route_attributes_kwargs branch from 329274b to 845e415 Compare July 4, 2026 15:09
@dblock
dblock merged commit c0c7129 into endpoint_api_kwarg Jul 5, 2026
69 checks passed
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants