From e220153986f9a5e3b9230d358e1f844bce50eb6b Mon Sep 17 00:00:00 2001 From: Sean Doyle Date: Thu, 6 Nov 2025 17:20:18 -0500 Subject: [PATCH] Revert to immediately retrieved collections by default Closes #460 Introduce both the `config.active_resource.lazy_collections` configuration, as well as the `Base.lazy_collections` class attribute. Restore the original behavior of immediately retrieving collections by defaulting the initial value to `false`, but deprecate the behavior and encourage migrating to setting the value to `true`. --- README.md | 23 +++++++++++++++++++++++ lib/active_resource/base.rb | 8 +++++++- lib/active_resource/railtie.rb | 1 + test/cases/finder_test.rb | 32 +++++++++++++++++++++++++++----- test/cases/notifications_test.rb | 2 +- test/fixtures/person.rb | 5 +++++ 6 files changed, 64 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 515f832edf..23f5b771af 100644 --- a/README.md +++ b/README.md @@ -192,6 +192,29 @@ people = Person.where(last_name: "Durden") people.first # => 'Tyler' ...> ``` +Collections are retrieved immediately by default. To defer the loading of the +records until their first access, set `Base.lazy_collections = true`: + +```ruby +Person.lazy_collections = true + +# Builds a lazy collection with an initial filter +people = Person.where(first_name: "Tyler") + +# Modifies the lazy collection with an additional filter +people = people.where(last_name: "Durden") + +# Expects a response of +# +# [ +# {"id":1,"first_name":"Tyler","last_name":"Durden"}, +# ] +# +# for GET http://api.people.com:3000/people.json?first_name=Tyler&last_name=Durden + +people.first # => 'Tyler' ...> +``` + ### 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..81c32314f7 100644 --- a/lib/active_resource/base.rb +++ b/lib/active_resource/base.rb @@ -380,6 +380,8 @@ def self.logger=(logger) class_attribute :connection_class self.connection_class = Connection + class_attribute :lazy_collections, instance_accessor: false, default: false + class << self include ThreadsafeAttributes threadsafe_attribute :_headers, :_connection, :_user, :_password, :_bearer_token, :_site, :_proxy @@ -1126,7 +1128,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/lib/active_resource/railtie.rb b/lib/active_resource/railtie.rb index 800f1208e2..d7c91c2d8b 100644 --- a/lib/active_resource/railtie.rb +++ b/lib/active_resource/railtie.rb @@ -8,6 +8,7 @@ class Railtie < Rails::Railtie config.eager_load_namespaces << ActiveResource config.active_resource = ActiveSupport::OrderedOptions.new + config.active_resource.lazy_collections = false initializer "active_resource.set_configs" do |app| ActiveSupport.on_load(:active_resource) do diff --git a/test/cases/finder_test.rb b/test/cases/finder_test.rb index d2cad8e30d..40816d29c6 100644 --- a/test/cases/finder_test.rb +++ b/test/cases/finder_test.rb @@ -61,6 +61,28 @@ def test_all assert_equal "David", all.last.name end + def test_all_immediately_finds_by_default + requests = ActiveResource::HttpMock.requests + + collection = Person.all + + assert_equal [ "/people.json" ], requests.map(&:path) + assert_kind_of Person, collection.first + end + + def test_all_with_lazy_collections + requests = ActiveResource::HttpMock.requests + + collection = DeferredPerson.all + + assert_empty requests.map(&:path) + + resource = collection.first + + assert_equal [ "/people.json" ], requests.map(&:path) + assert_kind_of Person, resource + end + def test_all_with_params all = StreetAddress.all(params: { person_id: 1 }) assert_equal 1, all.size @@ -84,7 +106,7 @@ def test_where_with_clauses def test_where_with_multiple_where_clauses ActiveResource::HttpMock.respond_to.get "/people.json?id=2&name=david", {}, @people_david - people = Person.where(id: 2).where(name: "david") + people = DeferredPerson.where(id: 2).where(name: "david") assert_equal 1, people.size assert_kind_of Person, people.first assert_equal 2, people.first.id @@ -94,7 +116,7 @@ def test_where_with_multiple_where_clauses def test_where_chained_from_all ActiveResource::HttpMock.respond_to.get "/records.json?id=2", {}, @people_david - people = Person.all(from: "/records.json").where(id: 2) + people = DeferredPerson.all(from: "/records.json").where(id: 2) assert_equal 1, people.size assert_kind_of Person, people.first assert_equal 2, people.first.id @@ -104,7 +126,7 @@ def test_where_chained_from_all def test_where_with_chained_into_all ActiveResource::HttpMock.respond_to.get "/records.json?id=2&name=david", {}, @people_david - people = Person.where(id: 2).all(from: "/records.json", params: { name: "david" }) + people = DeferredPerson.where(id: 2).all(from: "/records.json", params: { name: "david" }) assert_equal 1, people.size assert_kind_of Person, people.first assert_equal 2, people.first.id @@ -113,7 +135,7 @@ def test_where_with_chained_into_all def test_where_loading ActiveResource::HttpMock.respond_to.get "/people.json?id=2", {}, @people_david - people = Person.where(id: 2) + people = DeferredPerson.where(id: 2) assert_changes -> { ActiveResource::HttpMock.requests.count }, from: 0, to: 1 do people.load @@ -125,7 +147,7 @@ def test_where_loading def test_where_reloading ActiveResource::HttpMock.respond_to.get "/people.json?id=2", {}, @people_david - people = Person.where(id: 2) + people = DeferredPerson.where(id: 2) assert_changes -> { ActiveResource::HttpMock.requests.count }, from: 0, to: 1 do assert_equal 1, people.size diff --git a/test/cases/notifications_test.rb b/test/cases/notifications_test.rb index e8f3ba695d..11cf8ab405 100644 --- a/test/cases/notifications_test.rb +++ b/test/cases/notifications_test.rb @@ -15,7 +15,7 @@ def setup end def test_get_request_with_params - payload = capture_notifications { Person.where(name: "Matz").load } + payload = capture_notifications { Person.where(name: "Matz") } assert_equal :get, payload[:method] assert_equal "http://37s.sunrise.i:3000/people.json?name=Matz", payload[:request_uri] diff --git a/test/fixtures/person.rb b/test/fixtures/person.rb index ad0c20ada8..a80a5e785e 100644 --- a/test/fixtures/person.rb +++ b/test/fixtures/person.rb @@ -4,6 +4,11 @@ class Person < ActiveResource::Base self.site = "http://37s.sunrise.i:3000" end +class DeferredPerson < Person + self.element_name = "person" + self.lazy_collections = true +end + module External class Person < ActiveResource::Base self.site = "http://atq.caffeine.intoxication.it"