Skip to content

Commit b5ac983

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 b5ac983

File tree

4 files changed

+110
-2
lines changed

4 files changed

+110
-2
lines changed

lib/active_resource.rb

+1
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

+2-2
Original file line numberDiff line numberDiff line change
@@ -1037,12 +1037,12 @@ def last(*args)
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>
10391039
def all(*args)
1040-
find(:all, *args)
1040+
WhereClause.new(self, *args)
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

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

test/cases/finder_test.rb

+60
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)