From 6ac2cf2910f5193757219627af4292f3b9ebfe0a Mon Sep 17 00:00:00 2001 From: "Daniel (dB.) Doubrovkine" Date: Wed, 26 Aug 2026 17:57:08 -0400 Subject: [PATCH] Fix Link caching stale resource after a mutating request (#107) Link#http_method previously cached every HTTP response (GET, POST, PUT, PATCH, DELETE) into @resource, and #_resource returned @resource if set. This meant that after a _post/_put/_patch/_delete, subsequent calls that rely on #_resource (method_missing delegation, #each, etc.) would see the mutation's response instead of fetching a fresh resource via GET, requiring users to instantiate a new client to see up-to-date data. Only cache the response for GET requests now, and clear the delegate_method cache after every request so it is recomputed against the current resource. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CHANGELOG.md | 1 + lib/hyperclient/link.rb | 17 +++++++++++++---- test/hyperclient/link_test.rb | 13 +++++++++++++ 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f9ee186..3cef61a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * [#319](https://github.com/codegram/hyperclient/pull/319): Fixed `Resource#to_h` returning `nil` instead of the same result as `#to_hash` - [@dblock](https://github.com/dblock). * [#320](https://github.com/codegram/hyperclient/pull/320): Documented how to handle non-`hal+json` responses via the Faraday response middleware's `content_type` matcher - [@dblock](https://github.com/dblock). * [#321](https://github.com/codegram/hyperclient/pull/321): Added Ruby 4.0 to the CI test matrix - [@dblock](https://github.com/dblock). +* [#322](https://github.com/codegram/hyperclient/pull/322): Fixed `Link` caching a mutating request's (`_post`/`_put`/`_patch`/`_delete`) response as its resource, causing subsequent reads (e.g. `#each`, `method_missing`) to return stale data instead of a fresh `_get` - [@dblock](https://github.com/dblock). * Your contribution here. ### 2.0.0 (2024/02/01) diff --git a/lib/hyperclient/link.rb b/lib/hyperclient/link.rb index 7bb7113..864e29b 100644 --- a/lib/hyperclient/link.rb +++ b/lib/hyperclient/link.rb @@ -190,10 +190,19 @@ def _uri_template end def http_method(method, body = nil) - @resource = begin - response = @entry_point.connection.run_request(method, _url, body, nil) - Resource.new(response.body, @entry_point, response) - end + response = @entry_point.connection.run_request(method, _url, body, nil) + resource = Resource.new(response.body, @entry_point, response) + + # Only cache GET responses. Caching the response of a mutating + # request (POST/PUT/PATCH/DELETE) as `@resource` would make + # subsequent delegated calls (e.g. `#each`, `method_missing`) see + # the stale mutation response instead of fetching a fresh + # resource, requiring users to create a new client between a + # `_post` and a `_get`. See #107. + @resource = resource if method == :get + @delegate = nil + + resource end end end diff --git a/test/hyperclient/link_test.rb b/test/hyperclient/link_test.rb index 9c6e73a..2c166b6 100644 --- a/test/hyperclient/link_test.rb +++ b/test/hyperclient/link_test.rb @@ -151,6 +151,19 @@ module Hyperclient link._resource end + + it 'fetches a fresh resource after a mutating request instead of returning the cached response (#107)' do + link = Link.new('key', { 'href' => '/productions/1' }, entry_point) + + stub_request(entry_point.connection) do |stub| + stub.post('http://api.example.org/productions/1') { [201, {}, { 'status' => 'created' }] } + stub.get('http://api.example.org/productions/1') { [200, {}, { 'status' => 'fetched' }] } + end + + link._post('foo' => 'bar') + + _(link._resource.status).must_equal 'fetched' + end end describe 'get' do