quo 0.5.3 → 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 -69
- 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 +101 -213
- 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 -20
- 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)
|
@@ -130,38 +134,6 @@ composed = query1 + query2 # or Quo::Query.compose(query1, query2) or query1.com
|
|
130
134
|
composed.first
|
131
135
|
```
|
132
136
|
|
133
|
-
```ruby
|
134
|
-
class CompanyToBeApproved < Quo::TypedQuery
|
135
|
-
def query
|
136
|
-
Registration
|
137
|
-
.left_joins(:approval)
|
138
|
-
.where(approvals: {completed_at: nil})
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
|
-
class CompanyInUsCityAndState < Quo::TypedQuery
|
143
|
-
param :state, Literal::Union[String, Array[String]]
|
144
|
-
param :city, optional(Literal::Union[String, Array[String]])
|
145
|
-
|
146
|
-
def query
|
147
|
-
q = Registration
|
148
|
-
.joins(company: :address)
|
149
|
-
.where(addresses: {state: state})
|
150
|
-
q = q.where(addresses: {city: city}) if city
|
151
|
-
q
|
152
|
-
end
|
153
|
-
end
|
154
|
-
|
155
|
-
|
156
|
-
query1 = CompanyToBeApproved.new
|
157
|
-
query_partialy_applied = CompanyInUsState.with(state: "California")
|
158
|
-
query2 = query_partialy_applied.with(city: ["San Francisco", "Los Angeles"]).operation
|
159
|
-
|
160
|
-
composed = query1 + query2
|
161
|
-
# '+' composes the queries and returns a new prepared query
|
162
|
-
composed.first
|
163
|
-
```
|
164
|
-
|
165
137
|
This effectively executes:
|
166
138
|
|
167
139
|
```ruby
|
@@ -170,14 +142,13 @@ Registration
|
|
170
142
|
.joins(company: :address)
|
171
143
|
.where(approvals: {completed_at: nil})
|
172
144
|
.where(addresses: {state: options[:state]})
|
173
|
-
.limit(1)
|
174
145
|
```
|
175
146
|
|
176
147
|
It is also possible to compose with an `ActiveRecord::Relation`. This can be useful in a Query object itself to help
|
177
148
|
build up the `query` relation. For example:
|
178
149
|
|
179
150
|
```ruby
|
180
|
-
class RegistrationToBeApproved < Quo::
|
151
|
+
class RegistrationToBeApproved < Quo::RelationBackedQuery
|
181
152
|
def query
|
182
153
|
done = Registration.where(step: "complete")
|
183
154
|
approved = CompanyToBeApproved.new
|
@@ -194,13 +165,13 @@ query = RegistrationToBeApproved.new + Registration.where(blocked: false)
|
|
194
165
|
Also you can use joins:
|
195
166
|
|
196
167
|
```ruby
|
197
|
-
class TagByName < Quo::
|
168
|
+
class TagByName < Quo::RelationBackedQuery
|
198
169
|
def query
|
199
170
|
Tag.where(name: options[:name])
|
200
171
|
end
|
201
172
|
end
|
202
173
|
|
203
|
-
class CategoryByName < Quo::
|
174
|
+
class CategoryByName < Quo::RelationBackedQuery
|
204
175
|
def query
|
205
176
|
Category.where(name: options[:name])
|
206
177
|
end
|
@@ -213,18 +184,18 @@ tags.compose(for_category, :category) # perform join on tag association `categor
|
|
213
184
|
# equivalent to Tag.joins(:category).where(name: "Intel").where(categories: {name: "CPUs"})
|
214
185
|
```
|
215
186
|
|
216
|
-
|
187
|
+
Collection backed queries can also be composed (see below sections for more details).
|
217
188
|
|
218
|
-
### Quo::
|
189
|
+
### Quo::ComposedQuery
|
219
190
|
|
220
|
-
The new instance of `Quo::
|
191
|
+
The new instance of `Quo::ComposedQuery` from a compose process, retains references to the original entities that were
|
221
192
|
composed. These are then used to create a more useful output from `to_s`, so that it is easier to understand what the
|
222
193
|
merged query is actually made up of:
|
223
194
|
|
224
195
|
```ruby
|
225
196
|
q = FooQuery.new + BarQuery.new
|
226
197
|
puts q
|
227
|
-
# > "Quo::
|
198
|
+
# > "Quo::ComposedQuery[FooQuery, BarQuery]"
|
228
199
|
```
|
229
200
|
|
230
201
|
## Query Objects & Pagination
|
@@ -234,39 +205,40 @@ Specify extra options to enable pagination:
|
|
234
205
|
* `page`: the current page number to fetch
|
235
206
|
* `page_size`: the number of elements to fetch in the page
|
236
207
|
|
237
|
-
### `Quo::
|
208
|
+
### `Quo::CollectionBackedQuery` & `Quo::CollectionBackedQuery` objects
|
238
209
|
|
239
|
-
`Quo::
|
240
|
-
|
241
|
-
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.
|
242
214
|
|
243
215
|
```ruby
|
244
|
-
class
|
216
|
+
class MyCollectionBackedQuery < Quo::CollectionBackedQuery
|
245
217
|
def collection
|
246
218
|
[1, 2, 3]
|
247
219
|
end
|
248
220
|
end
|
249
|
-
q =
|
250
|
-
q.
|
221
|
+
q = MyCollectionBackedQuery.new
|
222
|
+
q.collection? # is it a collection under the hood? Yes it is!
|
251
223
|
q.count # '3'
|
252
224
|
```
|
253
225
|
|
254
226
|
Sometimes it is useful to create similar Queries without needing to create a explicit subclass of your own. For this
|
255
|
-
use `Quo::
|
227
|
+
use `Quo::CollectionBackedQuery`:
|
256
228
|
|
257
229
|
```ruby
|
258
|
-
q = Quo::
|
259
|
-
q.
|
230
|
+
q = Quo::CollectionBackedQuery.wrap([1, 2, 3])
|
231
|
+
q.collection? # true
|
260
232
|
q.count # '3'
|
261
233
|
```
|
262
234
|
|
263
|
-
`Quo::
|
235
|
+
`Quo::CollectionBackedQuery` also uses `total_count` option value as the specified 'total count', useful when the data is
|
264
236
|
actually just a page of the data and not the total count.
|
265
237
|
|
266
|
-
Example of an
|
238
|
+
Example of an CollectionBackedQuery used to wrap a page of enumerable data:
|
267
239
|
|
268
240
|
```ruby
|
269
|
-
Quo::
|
241
|
+
Quo::CollectionBackedQuery.wrap(my_data, total_count: 100, page: current_page)
|
270
242
|
```
|
271
243
|
|
272
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
|
@@ -277,7 +249,7 @@ results are returned from the other queries. An loaded or eager query will force
|
|
277
249
|
Examples of composition of eager loaded queries
|
278
250
|
|
279
251
|
```ruby
|
280
|
-
class CachedTags < Quo::
|
252
|
+
class CachedTags < Quo::RelationBackedQuery
|
281
253
|
def query
|
282
254
|
@tags ||= Tag.where(active: true).to_a
|
283
255
|
end
|
@@ -289,7 +261,7 @@ composed.last
|
|
289
261
|
composed.first
|
290
262
|
# => #<Tag id: ...>
|
291
263
|
|
292
|
-
Quo::
|
264
|
+
Quo::CollectionBackedQuery.new([3, 4]).compose(Quo::CollectionBackedQuery.new([1, 2])).last
|
293
265
|
# => 2
|
294
266
|
Quo::Query.compose([1, 2], [3, 4]).last
|
295
267
|
# => 4
|
@@ -319,7 +291,7 @@ maybe desirable.
|
|
319
291
|
|
320
292
|
The spec helper method `stub_query(query_class, {results: ..., with: ...})` can do this for you.
|
321
293
|
|
322
|
-
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`.
|
323
295
|
The `with` option is passed to the Query object on initialisation and used when setting up the method stub on the
|
324
296
|
query class.
|
325
297
|
|
@@ -332,7 +304,7 @@ expect(TagQuery.new(name: "Something").first).to eql t1
|
|
332
304
|
|
333
305
|
*Note that*
|
334
306
|
|
335
|
-
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
|
336
308
|
important or where you are doing a composition of queries backed by relations!
|
337
309
|
|
338
310
|
If `compose` will be used then `Quo::Query.compose` needs to be stubbed. Something might be possible to make this
|
@@ -371,11 +343,7 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/steveg
|
|
371
343
|
|
372
344
|
## Inspired by `rectify`
|
373
345
|
|
374
|
-
Note this implementation is
|
375
|
-
|
376
|
-
See https://github.com/andypike/rectify#query-objects for more information.
|
377
|
-
|
378
|
-
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.
|
379
347
|
|
380
348
|
## License
|
381
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
|