quo 0.6.0 → 1.0.0.alpha1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.standard.yml +4 -1
- data/Appraisals +11 -0
- data/CHANGELOG.md +78 -0
- data/Gemfile +6 -4
- data/LICENSE.txt +1 -1
- data/README.md +37 -36
- data/Steepfile +0 -2
- data/gemfiles/rails_7.0.gemfile +15 -0
- data/gemfiles/rails_7.1.gemfile +15 -0
- data/gemfiles/rails_7.2.gemfile +15 -0
- data/lib/quo/collection_backed_query.rb +87 -0
- data/lib/quo/collection_results.rb +44 -0
- data/lib/quo/composed_query.rb +168 -0
- data/lib/quo/engine.rb +11 -0
- data/lib/quo/minitest/helpers.rb +41 -0
- data/lib/quo/preloadable.rb +46 -0
- data/lib/quo/query.rb +97 -214
- data/lib/quo/relation_backed_query.rb +177 -0
- data/lib/quo/relation_results.rb +58 -0
- data/lib/quo/results.rb +48 -44
- data/lib/quo/rspec/helpers.rb +31 -9
- data/lib/quo/testing/collection_backed_fake.rb +29 -0
- data/lib/quo/testing/relation_backed_fake.rb +52 -0
- data/lib/quo/version.rb +3 -1
- data/lib/quo.rb +22 -30
- data/rbs_collection.yaml +0 -2
- data/sig/generated/quo/collection_backed_query.rbs +39 -0
- data/sig/generated/quo/collection_results.rbs +30 -0
- data/sig/generated/quo/composed_query.rbs +83 -0
- data/sig/generated/quo/engine.rbs +6 -0
- data/sig/generated/quo/preloadable.rbs +29 -0
- data/sig/generated/quo/query.rbs +98 -0
- data/sig/generated/quo/relation_backed_query.rbs +90 -0
- data/sig/generated/quo/relation_results.rbs +38 -0
- data/sig/generated/quo/results.rbs +39 -0
- data/sig/generated/quo/version.rbs +5 -0
- data/sig/generated/quo.rbs +9 -0
- metadata +67 -30
- data/lib/quo/eager_query.rb +0 -51
- data/lib/quo/loaded_query.rb +0 -18
- data/lib/quo/merged_query.rb +0 -36
- data/lib/quo/query_composer.rb +0 -78
- data/lib/quo/railtie.rb +0 -7
- data/lib/quo/utilities/callstack.rb +0 -21
- data/lib/quo/utilities/compose.rb +0 -18
- data/lib/quo/utilities/sanitize.rb +0 -19
- data/lib/quo/utilities/wrap.rb +0 -23
- data/lib/quo/wrapped_query.rb +0 -18
- data/sig/quo/eager_query.rbs +0 -15
- data/sig/quo/loaded_query.rbs +0 -7
- data/sig/quo/merged_query.rbs +0 -19
- data/sig/quo/query.rbs +0 -83
- data/sig/quo/query_composer.rbs +0 -32
- data/sig/quo/results.rbs +0 -22
- data/sig/quo/utilities/callstack.rbs +0 -7
- data/sig/quo/utilities/compose.rbs +0 -8
- data/sig/quo/utilities/sanitize.rbs +0 -9
- data/sig/quo/utilities/wrap.rbs +0 -11
- data/sig/quo/wrapped_query.rbs +0 -11
- data/sig/quo.rbs +0 -41
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7dcdc5faff0797696ddb8db26baebe37f388a020943f7764e4857aaab8ef4c76
|
4
|
+
data.tar.gz: 550da1aaaa2bc4d24d82f3e7f78908f22ccd28913b8d9c6358cbc9bbc6ee8fa3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 69a94d4d6c4844e6b27e409a662dd76a9ca0dee26fd229a2e861ad377ef2a247ec8499633565d119aa5bc6adeca8930015f33b6ea69e4673d9609f906a1029e5
|
7
|
+
data.tar.gz: d511d04d2212029428e74b2771407e7a3adf566c193a3075fffee3d1c3de7d3e9c8aaca8d39330198264be2ead5df7e574ebd90581600d27d5576eef8718cd0b
|
data/.standard.yml
CHANGED
data/Appraisals
ADDED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,83 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
|
4
|
+
## [1.0.0.rc1] - Unreleased
|
5
|
+
|
6
|
+
### Breaking Changes
|
7
|
+
|
8
|
+
Nearly everything has had changes. Porting will require some effort.
|
9
|
+
|
10
|
+
- Quo now depends on `literal`, meaning attributes (options) to queries are typed and explicit
|
11
|
+
- Composing query objects now allows you to compose query classes rather than just instances of query objects
|
12
|
+
- `MergedQuery`, `EagerQuery` & `LoadedQuery` have been removed
|
13
|
+
- `Query` is now an abstract base class for `RelationBackedQuery` and `CollectionBackedQuery`
|
14
|
+
- The API of `Query` has been reduced/simplified significantly
|
15
|
+
- `Query` classes only build queries, to actually execute/take actions on them you need to call `#results` and get a `Results` object
|
16
|
+
- `preload`ing behaviour is now a separate concern from `Query` and is handled by `Preloadable` module.
|
17
|
+
- Drop support for Ruby <= 3.1 and Rails < 7.0
|
18
|
+
- Gem is now a Rails engine and relies on autoloading
|
19
|
+
|
20
|
+
### Changed
|
21
|
+
|
22
|
+
- Update docs, dependencies, and tests
|
23
|
+
- Use appraisals for testing
|
24
|
+
|
25
|
+
### Added
|
26
|
+
|
27
|
+
- Helpers `stub_query` and `mock_query` for Minitest
|
28
|
+
|
29
|
+
## [0.5.0] - 2022-12-23
|
30
|
+
|
31
|
+
### Changed
|
32
|
+
|
33
|
+
- Merged and Wrapped queries should not have factory methods as they are not meant to be constructed directly
|
34
|
+
- Create new LoadedQuery which separates the concern of "preloaded" Query from EagerQuery which represents a query which is loaded and memoized
|
35
|
+
|
36
|
+
## [0.4.0] - 2022-12-23
|
37
|
+
|
38
|
+
### Changed
|
39
|
+
|
40
|
+
- Some redundant nil checks (either safe navigation operator or conditionals) to make type check pass
|
41
|
+
- Fix for type of transform method which takes optional index as second arg
|
42
|
+
- group_by can take a block
|
43
|
+
- Change last and first methods to just take a limit value
|
44
|
+
- Add new configuration options for page size limit and default and fix typing for enumerable
|
45
|
+
- Rename Enumerator to Results and Query#enumerator to #results
|
46
|
+
- Change EagerQuery initializer to take collection as positional param
|
47
|
+
|
48
|
+
## [0.3.1] - 2022-12-22
|
49
|
+
|
50
|
+
### Changed
|
51
|
+
|
52
|
+
- Convenience methods on Query
|
53
|
+
- Implement group_by on enumerator to transform values in resulting groups
|
54
|
+
- Add WrappedQuery instead of Query taking a scope param
|
55
|
+
- Change `initialize` method of MergedQuery
|
56
|
+
|
57
|
+
## [0.3.0] - 2022-12-20
|
58
|
+
|
59
|
+
### Changed
|
60
|
+
|
61
|
+
- Make `joins` on compose a kwarg
|
62
|
+
|
63
|
+
## [0.2.0] - 2022-12-20
|
64
|
+
|
65
|
+
### Added
|
66
|
+
|
67
|
+
- Railtie for rake task
|
68
|
+
- Rake task which hackily looks for qo in the app and displays a list
|
69
|
+
- Prepare to add RBS types
|
70
|
+
|
71
|
+
### Changed
|
72
|
+
|
73
|
+
- Gem deps
|
74
|
+
- Query interface
|
75
|
+
|
76
|
+
### Added
|
77
|
+
|
78
|
+
- Test suite and dummy rails app
|
79
|
+
- Add Enumerator
|
80
|
+
|
3
81
|
## [0.1.0] - 2022-11-18
|
4
82
|
|
5
83
|
- Initial release
|
data/Gemfile
CHANGED
@@ -5,16 +5,18 @@ source "https://rubygems.org"
|
|
5
5
|
# Specify your gem's dependencies in quo.gemspec
|
6
6
|
gemspec
|
7
7
|
|
8
|
+
gem "rails", "~> 7.2"
|
9
|
+
|
8
10
|
group :development, :test do
|
9
11
|
gem "sqlite3"
|
10
12
|
|
11
|
-
gem "rails", ">= 6", "< 8"
|
12
|
-
|
13
13
|
gem "rake", "~> 13.0"
|
14
14
|
|
15
15
|
gem "minitest", "~> 5.0"
|
16
16
|
|
17
|
-
gem "standard",
|
17
|
+
gem "standard", require: false
|
18
|
+
|
19
|
+
gem "steep", require: false
|
18
20
|
|
19
|
-
gem "
|
21
|
+
gem "rbs-inline", "~> 0.8.0", require: false
|
20
22
|
end
|
data/LICENSE.txt
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
The MIT License (MIT)
|
2
2
|
|
3
|
-
Copyright (c) 2022 Stephen Ierodiaconou
|
3
|
+
Copyright (c) 2022-2024 Stephen Ierodiaconou
|
4
4
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
data/README.md
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
# Quo
|
1
|
+
# 'Quo' query objects for ActiveRecord
|
2
|
+
|
3
|
+
> Note: these docs are for pre-V1 and need updating. I'm working on it!
|
2
4
|
|
3
5
|
Quo query objects can help you abstract ActiveRecord DB queries into reusable and composable objects with a chainable
|
4
6
|
interface.
|
@@ -13,10 +15,12 @@ The core implementation provides the following functionality:
|
|
13
15
|
* provides a number of utility methods that operate on the underlying collection (eg `exists?`)
|
14
16
|
* provides a `+` (`compose`) method which merges two query object instances (see section below for details!)
|
15
17
|
* can specify a mapping or transform method to `transform` to perform on results
|
16
|
-
* in development outputs the callstack that led to the execution of the query
|
17
18
|
* acts as a callable which executes the underlying query with `.first`
|
18
19
|
* can return an `Enumerable` of results
|
19
20
|
|
21
|
+
|
22
|
+
`Quo::Query` subclasses are the builders, they retain configuration of the queries, and prepare the underlying query or data collections. Query objects then return `Quo::Results` which take the built queries and then take action on them, such as to fetch data or to count records.
|
23
|
+
|
20
24
|
## Creating a Quo query object
|
21
25
|
|
22
26
|
The query object must inherit from `Quo::Query` and provide an implementation for the `query` method.
|
@@ -24,7 +28,7 @@ The query object must inherit from `Quo::Query` and provide an implementation fo
|
|
24
28
|
The `query` method must return either:
|
25
29
|
|
26
30
|
- an `ActiveRecord::Relation`
|
27
|
-
- an
|
31
|
+
- an Enumerable (like a 'collection backed' query)
|
28
32
|
- or another `Quo::Query` instance.
|
29
33
|
|
30
34
|
Remember that the query object should be useful in composition with other query objects. Thus it should not directly
|
@@ -81,7 +85,7 @@ This allows you to compose together Query objects which return relations which a
|
|
81
85
|
them correctly with the appropriate joins. Note with the alias you cant neatly specify optional parameters for joins
|
82
86
|
on relations.
|
83
87
|
|
84
|
-
Note that the compose process creates a new query object instance, which is a instance of a `Quo::
|
88
|
+
Note that the compose process creates a new query object instance, which is a instance of a `Quo::ComposedQuery`.
|
85
89
|
|
86
90
|
Consider the following cases:
|
87
91
|
|
@@ -95,7 +99,7 @@ wrapped around a new 'composed' `ActiveRecords::Relation`.
|
|
95
99
|
In case (2) the query object with a `ActiveRecords::Relation` inside is executed, and the result is then concatenated
|
96
100
|
to the array-like with `+`
|
97
101
|
|
98
|
-
In case (3) the values contained
|
102
|
+
In case (3) the values contained are concatenated with `+`
|
99
103
|
|
100
104
|
*Note that*
|
101
105
|
|
@@ -106,7 +110,7 @@ query object or and `ActiveRecord::Relation`. However `Quo::Query.compose(left,
|
|
106
110
|
### Examples
|
107
111
|
|
108
112
|
```ruby
|
109
|
-
class CompanyToBeApproved < Quo::
|
113
|
+
class CompanyToBeApproved < Quo::RelationBackedQuery
|
110
114
|
def query
|
111
115
|
Registration
|
112
116
|
.left_joins(:approval)
|
@@ -114,7 +118,7 @@ class CompanyToBeApproved < Quo::Query
|
|
114
118
|
end
|
115
119
|
end
|
116
120
|
|
117
|
-
class CompanyInUsState < Quo::
|
121
|
+
class CompanyInUsState < Quo::RelationBackedQuery
|
118
122
|
def query
|
119
123
|
Registration
|
120
124
|
.joins(company: :address)
|
@@ -144,7 +148,7 @@ It is also possible to compose with an `ActiveRecord::Relation`. This can be use
|
|
144
148
|
build up the `query` relation. For example:
|
145
149
|
|
146
150
|
```ruby
|
147
|
-
class RegistrationToBeApproved < Quo::
|
151
|
+
class RegistrationToBeApproved < Quo::RelationBackedQuery
|
148
152
|
def query
|
149
153
|
done = Registration.where(step: "complete")
|
150
154
|
approved = CompanyToBeApproved.new
|
@@ -161,13 +165,13 @@ query = RegistrationToBeApproved.new + Registration.where(blocked: false)
|
|
161
165
|
Also you can use joins:
|
162
166
|
|
163
167
|
```ruby
|
164
|
-
class TagByName < Quo::
|
168
|
+
class TagByName < Quo::RelationBackedQuery
|
165
169
|
def query
|
166
170
|
Tag.where(name: options[:name])
|
167
171
|
end
|
168
172
|
end
|
169
173
|
|
170
|
-
class CategoryByName < Quo::
|
174
|
+
class CategoryByName < Quo::RelationBackedQuery
|
171
175
|
def query
|
172
176
|
Category.where(name: options[:name])
|
173
177
|
end
|
@@ -180,18 +184,18 @@ tags.compose(for_category, :category) # perform join on tag association `categor
|
|
180
184
|
# equivalent to Tag.joins(:category).where(name: "Intel").where(categories: {name: "CPUs"})
|
181
185
|
```
|
182
186
|
|
183
|
-
|
187
|
+
Collection backed queries can also be composed (see below sections for more details).
|
184
188
|
|
185
|
-
### Quo::
|
189
|
+
### Quo::ComposedQuery
|
186
190
|
|
187
|
-
The new instance of `Quo::
|
191
|
+
The new instance of `Quo::ComposedQuery` from a compose process, retains references to the original entities that were
|
188
192
|
composed. These are then used to create a more useful output from `to_s`, so that it is easier to understand what the
|
189
193
|
merged query is actually made up of:
|
190
194
|
|
191
195
|
```ruby
|
192
196
|
q = FooQuery.new + BarQuery.new
|
193
197
|
puts q
|
194
|
-
# > "Quo::
|
198
|
+
# > "Quo::ComposedQuery[FooQuery, BarQuery]"
|
195
199
|
```
|
196
200
|
|
197
201
|
## Query Objects & Pagination
|
@@ -201,39 +205,40 @@ Specify extra options to enable pagination:
|
|
201
205
|
* `page`: the current page number to fetch
|
202
206
|
* `page_size`: the number of elements to fetch in the page
|
203
207
|
|
204
|
-
### `Quo::
|
208
|
+
### `Quo::CollectionBackedQuery` & `Quo::CollectionBackedQuery` objects
|
205
209
|
|
206
|
-
`Quo::
|
207
|
-
|
208
|
-
execute immediately. Subclass
|
210
|
+
`Quo::CollectionBackedQuery` is a subclass of `Quo::Query` which can be used to create query objects which are backed
|
211
|
+
by a collection (ie an enumerable such as an Array). This is useful for encapsulating data that doesn't come from an
|
212
|
+
ActiveRecord query or queries that execute immediately. Subclass this and override `collection` to return the data you
|
213
|
+
want to encapsulate.
|
209
214
|
|
210
215
|
```ruby
|
211
|
-
class
|
216
|
+
class MyCollectionBackedQuery < Quo::CollectionBackedQuery
|
212
217
|
def collection
|
213
218
|
[1, 2, 3]
|
214
219
|
end
|
215
220
|
end
|
216
|
-
q =
|
217
|
-
q.
|
221
|
+
q = MyCollectionBackedQuery.new
|
222
|
+
q.collection? # is it a collection under the hood? Yes it is!
|
218
223
|
q.count # '3'
|
219
224
|
```
|
220
225
|
|
221
226
|
Sometimes it is useful to create similar Queries without needing to create a explicit subclass of your own. For this
|
222
|
-
use `Quo::
|
227
|
+
use `Quo::CollectionBackedQuery`:
|
223
228
|
|
224
229
|
```ruby
|
225
|
-
q = Quo::
|
226
|
-
q.
|
230
|
+
q = Quo::CollectionBackedQuery.wrap([1, 2, 3])
|
231
|
+
q.collection? # true
|
227
232
|
q.count # '3'
|
228
233
|
```
|
229
234
|
|
230
|
-
`Quo::
|
235
|
+
`Quo::CollectionBackedQuery` also uses `total_count` option value as the specified 'total count', useful when the data is
|
231
236
|
actually just a page of the data and not the total count.
|
232
237
|
|
233
|
-
Example of an
|
238
|
+
Example of an CollectionBackedQuery used to wrap a page of enumerable data:
|
234
239
|
|
235
240
|
```ruby
|
236
|
-
Quo::
|
241
|
+
Quo::CollectionBackedQuery.wrap(my_data, total_count: 100, page: current_page)
|
237
242
|
```
|
238
243
|
|
239
244
|
If a loaded query is `compose`d with other Query objects then it will be seen as an array-like, and concatenated to whatever
|
@@ -244,7 +249,7 @@ results are returned from the other queries. An loaded or eager query will force
|
|
244
249
|
Examples of composition of eager loaded queries
|
245
250
|
|
246
251
|
```ruby
|
247
|
-
class CachedTags < Quo::
|
252
|
+
class CachedTags < Quo::RelationBackedQuery
|
248
253
|
def query
|
249
254
|
@tags ||= Tag.where(active: true).to_a
|
250
255
|
end
|
@@ -256,7 +261,7 @@ composed.last
|
|
256
261
|
composed.first
|
257
262
|
# => #<Tag id: ...>
|
258
263
|
|
259
|
-
Quo::
|
264
|
+
Quo::CollectionBackedQuery.new([3, 4]).compose(Quo::CollectionBackedQuery.new([1, 2])).last
|
260
265
|
# => 2
|
261
266
|
Quo::Query.compose([1, 2], [3, 4]).last
|
262
267
|
# => 4
|
@@ -286,7 +291,7 @@ maybe desirable.
|
|
286
291
|
|
287
292
|
The spec helper method `stub_query(query_class, {results: ..., with: ...})` can do this for you.
|
288
293
|
|
289
|
-
It stubs `.new` on the Query object and returns instances of `
|
294
|
+
It stubs `.new` on the Query object and returns instances of `CollectionBackedQuery` instead with the given `results`.
|
290
295
|
The `with` option is passed to the Query object on initialisation and used when setting up the method stub on the
|
291
296
|
query class.
|
292
297
|
|
@@ -299,7 +304,7 @@ expect(TagQuery.new(name: "Something").first).to eql t1
|
|
299
304
|
|
300
305
|
*Note that*
|
301
306
|
|
302
|
-
This returns an instance of
|
307
|
+
This returns an instance of CollectionBackedQuery, so will not work for cases were the actual type of the query instance is
|
303
308
|
important or where you are doing a composition of queries backed by relations!
|
304
309
|
|
305
310
|
If `compose` will be used then `Quo::Query.compose` needs to be stubbed. Something might be possible to make this
|
@@ -338,11 +343,7 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/steveg
|
|
338
343
|
|
339
344
|
## Inspired by `rectify`
|
340
345
|
|
341
|
-
Note this implementation is
|
342
|
-
|
343
|
-
See https://github.com/andypike/rectify#query-objects for more information.
|
344
|
-
|
345
|
-
Thanks to Andy Pike for the inspiration.
|
346
|
+
Note this implementation is inspired by the `Rectify` gem; https://github.com/andypike/rectify. Thanks to Andy Pike for the inspiration.
|
346
347
|
|
347
348
|
## License
|
348
349
|
|
data/Steepfile
CHANGED
@@ -0,0 +1,15 @@
|
|
1
|
+
# This file was generated by Appraisal
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
gem "rails", "~> 7.0"
|
6
|
+
|
7
|
+
group :development, :test do
|
8
|
+
gem "sqlite3"
|
9
|
+
gem "rake", "~> 13.0"
|
10
|
+
gem "minitest", "~> 5.0"
|
11
|
+
gem "standard"
|
12
|
+
gem "steep"
|
13
|
+
end
|
14
|
+
|
15
|
+
gemspec path: "../"
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# This file was generated by Appraisal
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
gem "rails", "~> 7.1"
|
6
|
+
|
7
|
+
group :development, :test do
|
8
|
+
gem "sqlite3"
|
9
|
+
gem "rake", "~> 13.0"
|
10
|
+
gem "minitest", "~> 5.0"
|
11
|
+
gem "standard"
|
12
|
+
gem "steep"
|
13
|
+
end
|
14
|
+
|
15
|
+
gemspec path: "../"
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# This file was generated by Appraisal
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
gem "rails", "~> 7.2"
|
6
|
+
|
7
|
+
group :development, :test do
|
8
|
+
gem "sqlite3"
|
9
|
+
gem "rake", "~> 13.0"
|
10
|
+
gem "minitest", "~> 5.0"
|
11
|
+
gem "standard"
|
12
|
+
gem "steep"
|
13
|
+
end
|
14
|
+
|
15
|
+
gemspec path: "../"
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# rbs_inline: enabled
|
4
|
+
|
5
|
+
module Quo
|
6
|
+
class CollectionBackedQuery < Query
|
7
|
+
prop :total_count, _Nilable(Integer), reader: false
|
8
|
+
|
9
|
+
# Wrap an enumerable collection or a block that returns an enumerable collection
|
10
|
+
# @rbs data: untyped, props: Symbol => untyped, block: () -> untyped
|
11
|
+
# @rbs return: Quo::CollectionBackedQuery
|
12
|
+
def self.wrap(data = nil, props: {}, &block)
|
13
|
+
klass = Class.new(self) do
|
14
|
+
props.each do |name, property|
|
15
|
+
if property.is_a?(Literal::Property)
|
16
|
+
prop name, property.type, property.kind, reader: property.reader, writer: property.writer, default: property.default
|
17
|
+
else
|
18
|
+
prop name, property
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
if block
|
23
|
+
klass.define_method(:collection, &block)
|
24
|
+
elsif data
|
25
|
+
klass.define_method(:collection) { data }
|
26
|
+
else
|
27
|
+
raise ArgumentError, "either a query or a block must be provided"
|
28
|
+
end
|
29
|
+
# klass.set_temporary_name = "quo::Wrapper" # Ruby 3.3+
|
30
|
+
klass
|
31
|
+
end
|
32
|
+
|
33
|
+
# @rbs return: Object & Enumerable[untyped]
|
34
|
+
def collection
|
35
|
+
raise NotImplementedError, "Collection backed query objects must define a 'collection' method"
|
36
|
+
end
|
37
|
+
|
38
|
+
# The default implementation of `query` just calls `collection`, however you can also
|
39
|
+
# override this method to return an ActiveRecord::Relation or any other query-like object as usual in a Query object.
|
40
|
+
# @rbs return: Object & Enumerable[untyped]
|
41
|
+
def query
|
42
|
+
collection
|
43
|
+
end
|
44
|
+
|
45
|
+
def results
|
46
|
+
Quo::CollectionResults.new(self, transformer: transformer, total_count: @total_count)
|
47
|
+
end
|
48
|
+
|
49
|
+
# @rbs override
|
50
|
+
def relation?
|
51
|
+
false
|
52
|
+
end
|
53
|
+
|
54
|
+
# @rbs override
|
55
|
+
def collection?
|
56
|
+
true
|
57
|
+
end
|
58
|
+
|
59
|
+
# @rbs override
|
60
|
+
def to_collection
|
61
|
+
self
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def validated_query
|
67
|
+
query
|
68
|
+
end
|
69
|
+
|
70
|
+
# @rbs return: Object & Enumerable[untyped]
|
71
|
+
def underlying_query
|
72
|
+
validated_query
|
73
|
+
end
|
74
|
+
|
75
|
+
# The configured query is the underlying query with paging
|
76
|
+
def configured_query #: Object & Enumerable[untyped]
|
77
|
+
q = underlying_query
|
78
|
+
return q unless paged?
|
79
|
+
|
80
|
+
if q.respond_to?(:[])
|
81
|
+
q[offset, sanitised_page_size]
|
82
|
+
else
|
83
|
+
q
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# rbs_inline: enabled
|
4
|
+
|
5
|
+
module Quo
|
6
|
+
class CollectionResults < Results
|
7
|
+
# @rbs override
|
8
|
+
def initialize(query, transformer: nil, total_count: nil)
|
9
|
+
raise ArgumentError, "Query must be a CollectionBackedQuery" unless query.is_a?(Quo::CollectionBackedQuery)
|
10
|
+
@total_count = total_count
|
11
|
+
@query = query
|
12
|
+
@configured_query = query.unwrap
|
13
|
+
@transformer = transformer
|
14
|
+
end
|
15
|
+
|
16
|
+
# Are there any results for this query?
|
17
|
+
def exists? #: bool
|
18
|
+
@configured_query.present?
|
19
|
+
end
|
20
|
+
|
21
|
+
def empty? #: bool
|
22
|
+
!exists?
|
23
|
+
end
|
24
|
+
|
25
|
+
# Gets the count of all results ignoring the current page and page size (if set).
|
26
|
+
# Optionally return the `total_count` option if it has been set.
|
27
|
+
# This is useful when the total count is known and not equal to size
|
28
|
+
# of wrapped collection.
|
29
|
+
# @rbs override
|
30
|
+
def total_count #: Integer
|
31
|
+
@total_count || @query.unwrap_unpaginated.size
|
32
|
+
end
|
33
|
+
|
34
|
+
# Gets the actual count of elements in the page of results (assuming paging is being used, otherwise the count of
|
35
|
+
# all results)
|
36
|
+
def page_count #: Integer
|
37
|
+
@configured_query.size
|
38
|
+
end
|
39
|
+
|
40
|
+
# @rbs @query: Quo::CollectionBackedQuery
|
41
|
+
# @rbs @transformer: (^(untyped, ?Integer) -> untyped)?
|
42
|
+
# @rbs @configured_query: Object & Enumerable[untyped]
|
43
|
+
end
|
44
|
+
end
|