From 34e7e3c4de45951835b729feaaadcee23c2ea96b Mon Sep 17 00:00:00 2001 From: Edouard CHIN Date: Thu, 6 Nov 2025 22:26:03 +0100 Subject: [PATCH] Add a way to temporarily opt-out to `#all` now lazily making requests: - ### Problem In 9372d5a68e62c81464773a2e32a98a989bed26b6 we made a change that defer the http request submission until the collection is used. ```ruby # Before Product.all #=> HTTP request is made # After products = Product.all #=> No HTTP request is made products.each { ... } #=> HTTP request is made here ``` This create issues for codepaths that used to expect the `Product.all` to make the http request and this feels like a breaking change that should go through a deprecation cycle (too late now but let's have a way to opt-out to the new behaviour). ### Context We have a safety net in our application that does something like this: ```ruby products = allow_explicit_http_connections do Product.all end products.each { ... } ``` This is now broken because we make the request outside of the explictly allowed resiliency block. ### Solution Introduce a way to opt out to the new behaviour and trigger a deprecation warning. --- README.md | 10 ++++++++++ lib/active_resource/base.rb | 17 ++++++++++++++++- test/cases/finder_test.rb | 24 ++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 515f832edf..a23f1418bb 100644 --- a/README.md +++ b/README.md @@ -192,6 +192,16 @@ people = Person.where(last_name: "Durden") people.first # => 'Tyler' ...> ``` +##### A note on finding all resources + +Previously, Active Resource would eagerly load resources when `Person.all` was called. This behaviour is now +deprecated and Active Resource now lazy loads collections. The http request is deferred until the collection is +explicitly accessed. + +You can opt-in to the new behaviour by setting the `ActiveResource::Base.lazy_collections = false`. +Note that this setting is temporary to allow your application to progressivly transition to the new behaviour. +This setting will be removed in the next versions of Active Resource. + ### Create Creating a new resource submits the JSON form of the resource as the body of the request and expects diff --git a/lib/active_resource/base.rb b/lib/active_resource/base.rb index edf92e5c25..0eb46da1fb 100644 --- a/lib/active_resource/base.rb +++ b/lib/active_resource/base.rb @@ -371,6 +371,7 @@ def self.logger=(logger) @@logger = logger end + class_attribute :lazy_collections, default: true, instance_accessor: false class_attribute :_query_format class_attribute :_format class_attribute :_collection_parser @@ -384,6 +385,16 @@ class << self include ThreadsafeAttributes threadsafe_attribute :_headers, :_connection, :_user, :_password, :_bearer_token, :_site, :_proxy + def new_lazy_collections=(value) + ActiveResource.deprecator.warn(<<~MSG) + ActiveResource::Base#lazy_collections= is deprecated with no replacement. + MSG + + self.old_lazy_collections = value + end + alias_method :old_lazy_collections=, :lazy_collections= + alias_method :lazy_collections=, :new_lazy_collections= + # Creates a schema for this resource - setting the attributes that are # known prior to fetching an instance from the remote system. # @@ -1126,7 +1137,11 @@ def last(*args) # This is an alias for find(:all). You can pass in all the same # arguments to this method as you can to find(:all) def all(*args) - WhereClause.new(self, *args) + if lazy_collections + WhereClause.new(self, *args) + else + find(:all, *args) + end end # This is an alias for all. You can pass in all the same diff --git a/test/cases/finder_test.rb b/test/cases/finder_test.rb index d2cad8e30d..09ec85d005 100644 --- a/test/cases/finder_test.rb +++ b/test/cases/finder_test.rb @@ -61,6 +61,30 @@ def test_all assert_equal "David", all.last.name end + def test_all_loads_the_collection_lazily + all = Person.all + assert_empty(ActiveResource::HttpMock.requests) + + assert_equal(2, all.size) + assert_not_empty(ActiveResource::HttpMock.requests) + end + + def test_all_loads_the_collection_eagerly + previous_value = ActiveResource::Base.lazy_collections + + assert_deprecated(/ActiveResource::Base#lazy_collections= is deprecated/, ActiveResource.deprecator) do + ActiveResource::Base.lazy_collections = false + end + + all = Person.all + assert_not_empty(ActiveResource::HttpMock.requests) + assert_equal(2, all.size) + ensure + assert_deprecated(/ActiveResource::Base#lazy_collections= is deprecated/, ActiveResource.deprecator) do + ActiveResource::Base.lazy_collections = previous_value + end + end + def test_all_with_params all = StreetAddress.all(params: { person_id: 1 }) assert_equal 1, all.size