quo 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +18 -24
- data/lib/quo/eager_query.rb +11 -28
- data/lib/quo/loaded_query.rb +18 -0
- data/lib/quo/merged_query.rb +0 -18
- data/lib/quo/query.rb +9 -5
- data/lib/quo/query_composer.rb +4 -2
- data/lib/quo/rspec/helpers.rb +2 -2
- data/lib/quo/utilities/wrap.rb +1 -1
- data/lib/quo/version.rb +1 -1
- data/lib/quo/wrapped_query.rb +4 -16
- data/lib/quo.rb +1 -0
- data/sig/quo/eager_query.rbs +4 -7
- data/sig/quo/loaded_query.rbs +7 -0
- data/sig/quo.rbs +1 -0
- metadata +3 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 071f43a86ac28245731999ddf6f605c89802b71e3d51139d44263214ad25dbd5
|
4
|
+
data.tar.gz: 5eaf5d72a701679c599e5d07f60e8f1c7c9eb1f2e00a7f8ad1503270bc9cda64
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ed91b9a82feb1c8f165672ffa4d2b1212ea7e8780cb85980f82353eca7fdfb247e67101fb6156ceaad65be94f27406daa4a5832e4950ab0204f7a9dc326329a7
|
7
|
+
data.tar.gz: 503525140bd10ed28817e61dc0a2de8ef4e60ca32805427b72763a039982fd069a147ce8eb18d91896ac83202645a12092a8b186b530c97322f465baeeba687a
|
data/README.md
CHANGED
@@ -201,50 +201,44 @@ Specify extra options to enable pagination:
|
|
201
201
|
* `page`: the current page number to fetch
|
202
202
|
* `page_size`: the number of elements to fetch in the page
|
203
203
|
|
204
|
-
|
204
|
+
### `Quo::EagerQuery` & `Quo::LoadedQuery` objects
|
205
205
|
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
For example, the Query below executes the query inside on the first call and memoises the resulting data. Note
|
211
|
-
however that this then means that this Query is not reusuable to `merge` with other ActiveRecord queries. If it is
|
212
|
-
`compose`d with other Query objects then it will be seen as an array-like, and concatenated to whatever it is being
|
213
|
-
joined to.
|
206
|
+
`Quo::EagerQuery` is a subclass of `Quo::Query` which can be used to create query objects which are 'eager loaded' by
|
207
|
+
default. This is useful for encapsulating data that doesn't come from an ActiveRecord query or queries that
|
208
|
+
execute immediately. Subclass EasyQuery and override `collection` to return the data you want to encapsulate.
|
214
209
|
|
215
210
|
```ruby
|
216
|
-
class
|
217
|
-
def
|
218
|
-
|
211
|
+
class MyEagerQuery < Quo::EagerQuery
|
212
|
+
def collection
|
213
|
+
[1, 2, 3]
|
219
214
|
end
|
220
215
|
end
|
221
|
-
|
222
|
-
q = CachedTags.new(active: false)
|
216
|
+
q = MyEagerQuery.new
|
223
217
|
q.eager? # is it 'eager'? Yes it is!
|
224
|
-
q.count #
|
218
|
+
q.count # '3'
|
225
219
|
```
|
226
220
|
|
227
|
-
|
228
|
-
|
229
|
-
`Quo::EagerQuery` is a subclass of `Quo::Query` which takes a data value on instantiation and returns it on calls to `query`
|
221
|
+
Sometimes it is useful to create similar Queries without needing to create a explicit subclass of your own. For this
|
222
|
+
use `Quo::LoadedQuery`:
|
230
223
|
|
231
224
|
```ruby
|
232
|
-
q = Quo::
|
225
|
+
q = Quo::LoadedQuery.new([1, 2, 3])
|
233
226
|
q.eager? # is it 'eager'? Yes it is!
|
234
227
|
q.count # '3'
|
235
228
|
```
|
236
229
|
|
237
|
-
This is useful to create eager loaded Queries without needing to create a explicit subclass of your own.
|
238
|
-
|
239
230
|
`Quo::EagerQuery` also uses `total_count` option value as the specified 'total count', useful when the data is
|
240
231
|
actually just a page of the data and not the total count.
|
241
232
|
|
242
233
|
Example of an EagerQuery used to wrap a page of enumerable data:
|
243
234
|
|
244
235
|
```ruby
|
245
|
-
Quo::
|
236
|
+
Quo::LoadedQuery.new(my_data, total_count: 100, page: current_page)
|
246
237
|
```
|
247
238
|
|
239
|
+
If a loaded query is `compose`d with other Query objects then it will be seen as an array-like, and concatenated to whatever
|
240
|
+
results are returned from the other queries. An loaded or eager query will force all other queries to be eager loaded.
|
241
|
+
|
248
242
|
### Composition
|
249
243
|
|
250
244
|
Examples of composition of eager loaded queries
|
@@ -262,7 +256,7 @@ composed.last
|
|
262
256
|
composed.first
|
263
257
|
# => #<Tag id: ...>
|
264
258
|
|
265
|
-
Quo::
|
259
|
+
Quo::LoadedQuery.new([3, 4]).compose(Quo::LoadedQuery.new([1, 2])).last
|
266
260
|
# => 2
|
267
261
|
Quo::Query.compose([1, 2], [3, 4]).last
|
268
262
|
# => 4
|
@@ -292,7 +286,7 @@ maybe desirable.
|
|
292
286
|
|
293
287
|
The spec helper method `stub_query(query_class, {results: ..., with: ...})` can do this for you.
|
294
288
|
|
295
|
-
It stubs `.new` on the Query object and returns instances of `
|
289
|
+
It stubs `.new` on the Query object and returns instances of `LoadedQuery` instead with the given `results`.
|
296
290
|
The `with` option is passed to the Query object on initialisation and used when setting up the method stub on the
|
297
291
|
query class.
|
298
292
|
|
data/lib/quo/eager_query.rb
CHANGED
@@ -2,45 +2,22 @@
|
|
2
2
|
|
3
3
|
module Quo
|
4
4
|
class EagerQuery < Quo::Query
|
5
|
-
class << self
|
6
|
-
def call(**options)
|
7
|
-
build_from_options(options).first
|
8
|
-
end
|
9
|
-
|
10
|
-
def call!(**options)
|
11
|
-
build_from_options(options).first!
|
12
|
-
end
|
13
|
-
|
14
|
-
def build_from_options(options)
|
15
|
-
collection = options[:collection]
|
16
|
-
raise ArgumentError, "EagerQuery needs a collection" unless collection
|
17
|
-
new(collection, **options.except(:collection))
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
def initialize(collection, **options)
|
22
|
-
@collection = Array.wrap(collection)
|
23
|
-
super(**options)
|
24
|
-
end
|
25
|
-
|
26
|
-
def copy(**options)
|
27
|
-
self.class.new(@collection, **@options.merge(options))
|
28
|
-
end
|
29
|
-
|
30
5
|
# Optionally return the `total_count` option if it has been set.
|
31
6
|
# This is useful when the total count is known and not equal to size
|
32
7
|
# of wrapped collection.
|
33
8
|
def count
|
34
9
|
options[:total_count] || super
|
35
10
|
end
|
36
|
-
alias_method :total_count, :count
|
37
|
-
alias_method :size, :count
|
38
11
|
|
39
12
|
# Is this query object paged? (when no total count)
|
40
13
|
def paged?
|
41
14
|
options[:total_count].nil? && current_page.present?
|
42
15
|
end
|
43
16
|
|
17
|
+
def collection
|
18
|
+
raise NotImplementedError, "EagerQuery objects must define a 'collection' method"
|
19
|
+
end
|
20
|
+
|
44
21
|
def query
|
45
22
|
preload_includes(collection) if options[:includes]
|
46
23
|
collection
|
@@ -56,7 +33,13 @@ module Quo
|
|
56
33
|
|
57
34
|
private
|
58
35
|
|
59
|
-
|
36
|
+
def underlying_query
|
37
|
+
unwrap_relation(query)
|
38
|
+
end
|
39
|
+
|
40
|
+
def unwrap_relation(query)
|
41
|
+
query.is_a?(Quo::Query) ? query.unwrap : query
|
42
|
+
end
|
60
43
|
|
61
44
|
def preload_includes(records, preload = nil)
|
62
45
|
::ActiveRecord::Associations::Preloader.new(
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Quo
|
4
|
+
class LoadedQuery < Quo::EagerQuery
|
5
|
+
def initialize(collection, **options)
|
6
|
+
@collection = collection
|
7
|
+
super(**options)
|
8
|
+
end
|
9
|
+
|
10
|
+
def copy(**options)
|
11
|
+
self.class.new(@collection, **@options.merge(options))
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
attr_reader :collection
|
17
|
+
end
|
18
|
+
end
|
data/lib/quo/merged_query.rb
CHANGED
@@ -2,24 +2,6 @@
|
|
2
2
|
|
3
3
|
module Quo
|
4
4
|
class MergedQuery < Quo::Query
|
5
|
-
class << self
|
6
|
-
def call(**options)
|
7
|
-
build_from_options(options).first
|
8
|
-
end
|
9
|
-
|
10
|
-
def call!(**options)
|
11
|
-
build_from_options(options).first!
|
12
|
-
end
|
13
|
-
|
14
|
-
def build_from_options(options)
|
15
|
-
merged_query = options[:merged_query]
|
16
|
-
left = options[:left]
|
17
|
-
right = options[:right]
|
18
|
-
raise ArgumentError, "MergedQuery needs the merged result and operands" unless merged_query && left && right
|
19
|
-
new(merged_query, left, right, **options)
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
5
|
def initialize(merged_query, left, right, **options)
|
24
6
|
@merged_query = merged_query
|
25
7
|
@left = left
|
data/lib/quo/query.rb
CHANGED
@@ -101,8 +101,11 @@ module Quo
|
|
101
101
|
elsif !res.nil?
|
102
102
|
transformer&.call(query_with_logging.first(limit))
|
103
103
|
end
|
104
|
-
|
104
|
+
elsif limit
|
105
105
|
query_with_logging.first(limit)
|
106
|
+
else
|
107
|
+
# Array#first will not take nil as a limit
|
108
|
+
query_with_logging.first
|
106
109
|
end
|
107
110
|
end
|
108
111
|
|
@@ -121,8 +124,10 @@ module Quo
|
|
121
124
|
elsif !res.nil?
|
122
125
|
transformer&.call(res)
|
123
126
|
end
|
124
|
-
|
127
|
+
elsif limit
|
125
128
|
query_with_logging.last(limit)
|
129
|
+
else
|
130
|
+
query_with_logging.last
|
126
131
|
end
|
127
132
|
end
|
128
133
|
|
@@ -132,9 +137,8 @@ module Quo
|
|
132
137
|
transform? ? arr.map.with_index { |r, i| transformer&.call(r, i) } : arr
|
133
138
|
end
|
134
139
|
|
135
|
-
# Convert to EagerQuery, and load all data
|
136
140
|
def to_eager(more_opts = {})
|
137
|
-
Quo::
|
141
|
+
Quo::LoadedQuery.new(to_a, **options.merge(more_opts))
|
138
142
|
end
|
139
143
|
alias_method :load, :to_eager
|
140
144
|
|
@@ -282,7 +286,7 @@ module Quo
|
|
282
286
|
end
|
283
287
|
|
284
288
|
def test_eager(rel)
|
285
|
-
rel.is_a?(Enumerable) && !test_relation(rel)
|
289
|
+
rel.is_a?(Quo::LoadedQuery) || (rel.is_a?(Enumerable) && !test_relation(rel))
|
286
290
|
end
|
287
291
|
|
288
292
|
def test_relation(rel)
|
data/lib/quo/query_composer.rb
CHANGED
@@ -39,10 +39,12 @@ module Quo
|
|
39
39
|
apply_joins(unwrapped_left, joins).merge(unwrapped_right)
|
40
40
|
elsif left_relation_right_enumerable?
|
41
41
|
unwrapped_left.to_a + unwrapped_right
|
42
|
-
elsif left_enumerable_right_relation?
|
42
|
+
elsif left_enumerable_right_relation? && unwrapped_left.respond_to?(:+)
|
43
43
|
unwrapped_left + unwrapped_right.to_a
|
44
|
-
|
44
|
+
elsif unwrapped_left.respond_to?(:+)
|
45
45
|
unwrapped_left + unwrapped_right
|
46
|
+
else
|
47
|
+
raise ArgumentError, "Cannot merge #{left.class} with #{right.class}"
|
46
48
|
end
|
47
49
|
end
|
48
50
|
|
data/lib/quo/rspec/helpers.rb
CHANGED
@@ -9,10 +9,10 @@ module Quo
|
|
9
9
|
unless with.nil?
|
10
10
|
return(
|
11
11
|
allow(query_class).to receive(:new)
|
12
|
-
.with(with) { ::Quo::
|
12
|
+
.with(with) { ::Quo::LoadedQuery.new(results) }
|
13
13
|
)
|
14
14
|
end
|
15
|
-
allow(query_class).to receive(:new) { ::Quo::
|
15
|
+
allow(query_class).to receive(:new) { ::Quo::LoadedQuery.new(results) }
|
16
16
|
end
|
17
17
|
end
|
18
18
|
end
|
data/lib/quo/utilities/wrap.rb
CHANGED
data/lib/quo/version.rb
CHANGED
data/lib/quo/wrapped_query.rb
CHANGED
@@ -2,27 +2,15 @@
|
|
2
2
|
|
3
3
|
module Quo
|
4
4
|
class WrappedQuery < Quo::Query
|
5
|
-
class << self
|
6
|
-
def call(**options)
|
7
|
-
build_from_options(**options).first
|
8
|
-
end
|
9
|
-
|
10
|
-
def call!(**options)
|
11
|
-
build_from_options(**options).first!
|
12
|
-
end
|
13
|
-
|
14
|
-
def build_from_options(**options)
|
15
|
-
query = options[:wrapped_query]
|
16
|
-
raise ArgumentError, "WrappedQuery needs a scope" unless query
|
17
|
-
new(query, **options)
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
5
|
def initialize(wrapped_query, **options)
|
22
6
|
@wrapped_query = wrapped_query
|
23
7
|
super(**options)
|
24
8
|
end
|
25
9
|
|
10
|
+
def copy(**options)
|
11
|
+
self.class.new(query, **@options.merge(options))
|
12
|
+
end
|
13
|
+
|
26
14
|
def query
|
27
15
|
@wrapped_query
|
28
16
|
end
|
data/lib/quo.rb
CHANGED
@@ -4,6 +4,7 @@ require_relative "quo/version"
|
|
4
4
|
require_relative "quo/railtie" if defined?(Rails)
|
5
5
|
require_relative "quo/query"
|
6
6
|
require_relative "quo/eager_query"
|
7
|
+
require_relative "quo/loaded_query"
|
7
8
|
require_relative "quo/merged_query"
|
8
9
|
require_relative "quo/wrapped_query"
|
9
10
|
require_relative "quo/query_composer"
|
data/sig/quo/eager_query.rbs
CHANGED
@@ -1,18 +1,15 @@
|
|
1
1
|
module Quo
|
2
2
|
class EagerQuery < Quo::Query
|
3
|
-
def
|
4
|
-
def
|
5
|
-
def self.build_from_options: (queryOptions) -> EagerQuery
|
3
|
+
def collection: () -> loadedQueryOrEnumerable
|
4
|
+
def query: () -> loadedQueryOrEnumerable
|
6
5
|
|
7
|
-
def initialize: (enumerable, **untyped options) -> void
|
8
|
-
def query: () -> enumerable
|
9
6
|
def relation?: () -> false
|
10
7
|
def eager?: () -> true
|
11
8
|
|
12
9
|
private
|
13
10
|
|
14
|
-
attr_reader collection: enumerable
|
15
|
-
|
16
11
|
def preload_includes: (untyped records, ?untyped? preload) -> untyped
|
12
|
+
def underlying_query: () -> enumerable
|
13
|
+
def unwrap_relation: (loadedQueryOrEnumerable collection) -> enumerable
|
17
14
|
end
|
18
15
|
end
|
data/sig/quo.rbs
CHANGED
@@ -13,6 +13,7 @@ module Quo
|
|
13
13
|
type queryOrRel = query | ActiveRecord::Relation
|
14
14
|
type enumerable = Object & Enumerable[untyped]
|
15
15
|
type relOrEnumerable = ActiveRecord::Relation | enumerable
|
16
|
+
type loadedQueryOrEnumerable = LoadedQuery | EagerQuery | enumerable
|
16
17
|
type composable = query | relOrEnumerable
|
17
18
|
|
18
19
|
# TODO: how can we do the known options, eg `page` and then allow anything else?
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: quo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stephen Ierodiaconou
|
@@ -66,6 +66,7 @@ files:
|
|
66
66
|
- Steepfile
|
67
67
|
- lib/quo.rb
|
68
68
|
- lib/quo/eager_query.rb
|
69
|
+
- lib/quo/loaded_query.rb
|
69
70
|
- lib/quo/merged_query.rb
|
70
71
|
- lib/quo/query.rb
|
71
72
|
- lib/quo/query_composer.rb
|
@@ -82,6 +83,7 @@ files:
|
|
82
83
|
- rbs_collection.yaml
|
83
84
|
- sig/quo.rbs
|
84
85
|
- sig/quo/eager_query.rbs
|
86
|
+
- sig/quo/loaded_query.rbs
|
85
87
|
- sig/quo/merged_query.rbs
|
86
88
|
- sig/quo/query.rbs
|
87
89
|
- sig/quo/query_composer.rbs
|