From 58ef5dd377da55f2d39dc98d400ff531e118f1cd Mon Sep 17 00:00:00 2001 From: Eric Proulx Date: Thu, 10 Sep 2026 21:09:28 +0200 Subject: [PATCH] Read the versioners' version options off instance variables `vendor`, `strict`, `parameter` and `cascade` were delegated through `version_options` into `config`, so each read went two Forwardable frames and two Data readers deep, about 150 ns, for a value fixed when the middleware was built. The header versioner reads `vendor` and `strict` on every request, the accept-version one `strict`, and the param one `parameter` twice. They are now plain readers over instance variables set in `#initialize`, the pattern the formatter adopted for its own options. The extra variables ride along on the per-request `dup`; the path versioner, which reads none of them, stays in the same object slot. Co-Authored-By: Claude Opus 5 --- CHANGELOG.md | 1 + lib/grape/middleware/versioner/base.rb | 11 ++++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fdc7d3a3e..26a317ac3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ * [#2918](https://github.com/ruby-grape/grape/pull/2918): Skip the dry-types round trip when a value already is the declared type - [@ericproulx](https://github.com/ericproulx). * [#2917](https://github.com/ruby-grape/grape/pull/2917): Read path captures out of the router's union match instead of re-running the route's pattern - [@ericproulx](https://github.com/ericproulx). * [#2921](https://github.com/ruby-grape/grape/pull/2921): Pin the router's request-time isolation regressions through requests instead of its instance variables - [@ericproulx](https://github.com/ericproulx). +* [#2923](https://github.com/ruby-grape/grape/pull/2923): Read the versioners' `vendor`, `strict`, `parameter` and `cascade` off instance variables instead of two delegators per request - [@ericproulx](https://github.com/ericproulx). * Your contribution here. ### 4.0.0 (2026-09-07) diff --git a/lib/grape/middleware/versioner/base.rb b/lib/grape/middleware/versioner/base.rb index 646bcbf23..d13b048ac 100644 --- a/lib/grape/middleware/versioner/base.rb +++ b/lib/grape/middleware/versioner/base.rb @@ -27,11 +27,20 @@ def self.inherited(klass) attr_reader :available_media_types, :error_headers, :versions + # Read off ivars rather than delegated through +version_options+ into + # +config+: the versioners ask for +vendor+, +strict+ or +parameter+ on + # every request, and each read went two Forwardable frames and two Data + # readers deep for a value fixed when the middleware was built. + attr_reader :cascade, :parameter, :strict, :vendor + def_delegators :config, :mount_path, :prefix, :version_options - def_delegators :version_options, :cascade, :parameter, :strict, :vendor def initialize(app, **options) super + @cascade = version_options.cascade + @parameter = version_options.parameter + @strict = version_options.strict + @vendor = version_options.vendor @versions = config.versions&.map(&:to_s) # making sure versions are strings to ease potential match @error_headers = cascade ? CASCADE_PASS_HEADER : {} @available_media_types = build_available_media_types