Skip to content

Commit d79a12b

Browse files
ericproulxclaude
andauthored
Build the cookie jar only on use and drop its deferral Proc (#2757)
Two related steps that make the response-cookie path lazy at the request boundary: - `build_response_cookies` runs on every request and went through `cookies` -> `request.cookies`, materializing the `Grape::Cookies` jar even when the handler never touched a cookie. Gate it on a new `Grape::Request#cookies?` predicate (true only once the jar exists), so a cookie-free request allocates no jar at all. - With the jar now built only when a cookie is actually read or written, its `-> { rack_cookies }` deferral Proc no longer earns its keep: any access immediately forces the parse, and a write (`[]=`) always did. Parse `rack_cookies` eagerly in the constructor and replace the `is_a?(Proc)` lazy reader with a plain `attr_reader`, dropping the closure allocation and the per-access branch. The `ActiveSupport::HashWithIndifferentAccess` wrapping is unchanged -- it backs Grape's string/symbol-indifferent cookie access. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent df9338f commit d79a12b

4 files changed

Lines changed: 14 additions & 7 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
* [#2754](https://github.com/ruby-grape/grape/pull/2754): Merge routing args in place in `Router#process_route` instead of allocating a new Hash via `merge` - [@ericproulx](https://github.com/ericproulx).
6060
* [#2753](https://github.com/ruby-grape/grape/pull/2753): Lazy-allocate `Grape::Validations::ParamScopeTracker`'s identity-keyed hashes so validating requests that never use the index / qualifying-params trackers allocate no hash - [@ericproulx](https://github.com/ericproulx).
6161
* [#2752](https://github.com/ruby-grape/grape/pull/2752): Skip per-request `ActiveSupport::Notifications` payload and dispatch when no subscriber is listening, via private `instrument_<event>` guards on `Endpoint`/`Middleware::Formatter` - [@ericproulx](https://github.com/ericproulx).
62+
* [#2757](https://github.com/ruby-grape/grape/pull/2757): Build the `Grape::Cookies` jar only when a cookie is read or written (via a new `Grape::Request#cookies?` predicate gating response-cookie flushing), and drop the jar's now-redundant lazy-parse `Proc` - [@ericproulx](https://github.com/ericproulx).
6263
* [#2756](https://github.com/ruby-grape/grape/pull/2756): Tighten dependency lower bounds to their compatibility floors (`rack >= 2.2.4`, `zeitwerk >= 2.6`, `dry-configurable >= 1.0`) - [@ericproulx](https://github.com/ericproulx).
6364
* Your contribution here.
6465

lib/grape/cookies.rb

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class Cookies
1313
def_delegators :cookies, :[], :each
1414

1515
def initialize(rack_cookies)
16-
@cookies = rack_cookies
16+
@cookies = ActiveSupport::HashWithIndifferentAccess.new(rack_cookies)
1717
@send_cookies = nil
1818
end
1919

@@ -37,11 +37,7 @@ def delete(name, **opts)
3737

3838
private
3939

40-
def cookies
41-
return @cookies unless @cookies.is_a?(Proc)
42-
43-
@cookies = ActiveSupport::HashWithIndifferentAccess.new(@cookies.call)
44-
end
40+
attr_reader :cookies
4541

4642
def send_cookies
4743
@send_cookies ||= Set.new

lib/grape/endpoint.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,8 @@ def build_helpers
385385
end
386386

387387
def build_response_cookies
388+
return unless request.cookies?
389+
388390
response_cookies do |name, value|
389391
cookie_value = value.is_a?(Hash) ? value : { value: }
390392
Rack::Utils.set_cookie_header! header, name, cookie_value

lib/grape/request.rb

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,15 @@ def headers
153153
end
154154

155155
def cookies
156-
@cookies ||= Grape::Cookies.new(-> { rack_cookies })
156+
@cookies ||= Grape::Cookies.new(rack_cookies)
157+
end
158+
159+
# True once the cookie jar has been materialized (a cookie was read or
160+
# written this request). Lets the endpoint skip response-cookie flushing,
161+
# and the Grape::Cookies allocation it triggers, when no cookie was
162+
# touched on the request.
163+
def cookies?
164+
!@cookies.nil?
157165
end
158166

159167
private

0 commit comments

Comments
 (0)