Skip to content

Commit d86b0d0

Browse files
committed
Introduce ActiveResource::WhereClause
Closes [#408][] Introduce a simple chainable `WhereClause` class inspired by [Active Record][]. All methods (including those that integrate with [Enumerable][]) are delegated to `WhereClause#all`, which itself delegates to `Base.find`. By merging parameters through `.where`-chaining, this commit supports deferred fetching: ```ruby people = Person.where(id: 2).where(name: "david") # => GET /people.json?id=2&name=david people = Person.where(id: 2).all(params: { name: "david" }) # => GET /people.json?id=2&name=david people = Person.all(from: "/records.json").where(id: 2) # => GET /records.json?id=2 ``` [#408]: #408 [Active Record]: https://github.com/rails/rails/blob/main/activerecord/lib/active_record/relation/where_clause.rb [Enumerable]: https://ruby-doc.org/3.4.1/Enumerable.html
1 parent 9c8a2ee commit d86b0d0

File tree

4 files changed

+112
-3
lines changed

4 files changed

+112
-3
lines changed

lib/active_resource.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ module ActiveResource
4646
autoload :InheritingHash
4747
autoload :Validations
4848
autoload :Collection
49+
autoload :WhereClause
4950

5051
if ActiveSupport::VERSION::STRING >= "7.1"
5152
def self.deprecator

lib/active_resource/base.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1036,13 +1036,13 @@ def last(*args)
10361036

10371037
# This is an alias for find(:all). You can pass in all the same
10381038
# arguments to this method as you can to <tt>find(:all)</tt>
1039-
def all(*args)
1040-
find(:all, *args)
1039+
def all(options = {})
1040+
WhereClause.new(self, options)
10411041
end
10421042

10431043
def where(clauses = {})
10441044
raise ArgumentError, "expected a clauses Hash, got #{clauses.inspect}" unless clauses.is_a? Hash
1045-
find(:all, params: clauses)
1045+
all(params: clauses)
10461046
end
10471047

10481048

lib/active_resource/where_clause.rb

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# frozen_string_literal: true
2+
3+
module ActiveResource
4+
class WhereClause < BasicObject
5+
delegate :find, to: :@resource_class
6+
delegate_missing_to :resources
7+
8+
def initialize(resource_class, options)
9+
@resource_class = resource_class
10+
@options = options
11+
@resources = nil
12+
@loaded = false
13+
end
14+
15+
def where(clauses = {})
16+
all(params: clauses)
17+
end
18+
19+
def all(options = {})
20+
WhereClause.new(@resource_class, @options.deep_merge(options))
21+
end
22+
23+
def load
24+
unless @loaded
25+
@resources = find(:all, @options)
26+
@loaded = true
27+
end
28+
29+
self
30+
end
31+
32+
def reload
33+
reset
34+
load
35+
end
36+
37+
private
38+
def resources
39+
load
40+
@resources
41+
end
42+
43+
def reset
44+
@resources = nil
45+
@loaded = false
46+
end
47+
end
48+
end

test/cases/finder_test.rb

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,66 @@ def test_where_with_clauses
6363
assert_kind_of StreetAddress, addresses.first
6464
end
6565

66+
def test_where_with_multiple_where_clauses
67+
ActiveResource::HttpMock.respond_to.get "/people.json?id=2&name=david", {}, @people_david
68+
69+
people = Person.where(id: 2).where(name: "david")
70+
assert_equal 1, people.size
71+
assert_kind_of Person, people.first
72+
assert_equal 2, people.first.id
73+
assert_equal "David", people.first.name
74+
end
75+
76+
def test_where_chained_from_all
77+
ActiveResource::HttpMock.respond_to.get "/records.json?id=2", {}, @people_david
78+
79+
people = Person.all(from: "/records.json").where(id: 2)
80+
assert_equal 1, people.size
81+
assert_kind_of Person, people.first
82+
assert_equal 2, people.first.id
83+
assert_equal "David", people.first.name
84+
end
85+
86+
def test_where_with_chained_into_all
87+
ActiveResource::HttpMock.respond_to.get "/records.json?id=2&name=david", {}, @people_david
88+
89+
people = Person.where(id: 2).all(from: "/records.json", params: { name: "david" })
90+
assert_equal 1, people.size
91+
assert_kind_of Person, people.first
92+
assert_equal 2, people.first.id
93+
assert_equal "David", people.first.name
94+
end
95+
96+
def test_where_loading
97+
ActiveResource::HttpMock.respond_to.get "/people.json?id=2", {}, @people_david
98+
people = Person.where(id: 2)
99+
100+
assert_changes -> { ActiveResource::HttpMock.requests.count }, from: 0, to: 1 do
101+
people.load
102+
end
103+
assert_no_changes -> { ActiveResource::HttpMock.requests.count }, from: 1 do
104+
10.times { people.load }
105+
end
106+
end
107+
108+
def test_where_reloading
109+
ActiveResource::HttpMock.respond_to.get "/people.json?id=2", {}, @people_david
110+
people = Person.where(id: 2)
111+
112+
assert_changes -> { ActiveResource::HttpMock.requests.count }, from: 0, to: 1 do
113+
assert_equal 1, people.size
114+
end
115+
assert_no_changes -> { ActiveResource::HttpMock.requests.count }, from: 1 do
116+
assert_equal 1, people.size
117+
end
118+
assert_changes -> { ActiveResource::HttpMock.requests.count }, from: 1, to: 2 do
119+
people.reload
120+
end
121+
assert_no_changes -> { ActiveResource::HttpMock.requests.count }, from: 2 do
122+
assert_equal 1, people.size
123+
end
124+
end
125+
66126
def test_where_with_clause_in
67127
ActiveResource::HttpMock.respond_to { |m| m.get "/people.json?id%5B%5D=2", {}, @people_david }
68128
people = Person.where(id: [2])

0 commit comments

Comments
 (0)