Skip to content

Split the router's miss and cascade paths and rename its methods - #3047

Merged
ericproulx merged 1 commit into
masterfrom
refactor/router-miss-and-cascade
Oct 31, 2026
Merged

ericproulx merged 1 commit into
masterfrom
refactor/router-miss-and-cascade

Conversation

@ericproulx

Copy link
Copy Markdown
Contributor

Replaces #3044, which merged into #3043's branch after #3043 had already merged, so it never reached master. The code is identical to #3044, replayed onto current master.

Summary

No behaviour change.

#settle and #neighbours handled two situations, no route matching and a route declining, and told them apart with a cascaded flag that was true exactly when a route had matched. That flag becomes two paths:

def settle(input, method, env, response, exact_route)
  return after_miss(input, method, env) unless response

  final_response(response) || after_cascade(input, method, env, exact_route)
end
  • #after_miss tries the allowed methods (OPTIONS first), the ANY routes, then the 405.
  • #after_cascade tries the siblings, then the ANY routes, and never looks for allowed methods.

#halt?, a predicate that closed a body as a side effect, is gone. The two lookups that repeated routes.detect now go through #matched_route.

Renames

Before After Why
transaction, bucket_transaction, union_transaction dispatch, dispatch_to_bucket, dispatch_to_union Nothing atomic or rolled back here
match?(input, '*') star_route_for(input) Returns a route, and '*' was its only argument
greedy_match? allowed_methods_route_for Returns a route
neighbour_match? neighbour_route_for Returns a route
last_neighbor_route allowed_methods_route "Last" was stale, and the spelling differed from neighbour
Router::GreedyRoute Router::AllowedMethodsRoute It matches on the path whatever the method, and only ever answers with the methods the path allows

Router::GreedyRoute is the only name that isn't private, and it's undocumented and never handed to users. GitHub code search finds it only in this repo across the ruby-grape org, grape-swagger included, so there's no alias and no UPGRADING entry. @neutral_map and @neighbour_map keep their names.

Measured

Measured on #3044, against #3043, and not rerun here: one worktree, order alternated each round, 100 CRUD resources plus two header versions.

Scenario Calls per request Allocations
GET static 182 → 181 10 → 10
GET with a param 223 → 222 15 → 15
GET 404 60 → 57 4 → 4
POST 405 407 → 404 30 → 30
OPTIONS 445 → 443 13 → 13
GET v2, declined by v1 3185 → 3181 429 → 429

Throughput is flat. A first 7-round run read −3.9% on POST 405 without JIT and −3.8% on GET with a param under YJIT, so those two were rerun for 9 rounds with an A/A control:

Mode Scenario Head vs base A/A
No JIT GET with a param +0.2% (5/9) −0.3% (4/9)
No JIT POST 405 +0.4% (6/9) −0.4% (3/9)
YJIT GET with a param −0.2% (4/9) −1.0% (4/9)
YJIT POST 405 +1.8% (8/9) +1.1% (5/9)

Test plan

🤖 Generated with Claude Code

#settle and #neighbours handled two situations, no route matching and a route
declining, and told them apart with a `cascaded` flag. That flag was true
exactly when a route had matched, so it is replaced by two paths: #after_miss
looks for the allowed methods and the ANY routes, #after_cascade tries the
siblings and the ANY routes. #halt?, a predicate that closed a body as a side
effect, goes with it.

The names said something else, so:

- `transaction` is `dispatch`: there is no atomicity or rollback in it.
- `match?`, `greedy_match?` and `neighbour_match?` return a route, so they are
  `star_route_for`, `allowed_methods_route_for` and `neighbour_route_for`.
- `GreedyRoute` is `AllowedMethodsRoute`: it matches on the path whatever the
  method, and only ever answers with the methods the path allows. Nothing in
  the ruby-grape org uses the constant outside this repo.

The two lookups that repeated `routes.detect` go through `matched_route`.

This is #3044, which merged into #3043's branch after #3043 had already merged,
so it never reached master.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ericproulx
ericproulx force-pushed the refactor/router-miss-and-cascade branch from 6acb8c0 to e1b05cd Compare October 31, 2026 17:20
@github-actions

github-actions Bot commented Oct 31, 2026 •

Copy link
Copy Markdown

Danger Report

No issues found.

View run

@ericproulx
ericproulx merged commit 36a3d55 into master Oct 31, 2026
69 checks passed
@ericproulx
ericproulx deleted the refactor/router-miss-and-cascade branch October 31, 2026 17:28
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.

1 participant