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.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/.standard.yml +4 -1
  3. data/Appraisals +11 -0
  4. data/CHANGELOG.md +78 -0
  5. data/Gemfile +6 -4
  6. data/LICENSE.txt +1 -1
  7. data/README.md +37 -36
  8. data/Steepfile +0 -2
  9. data/gemfiles/rails_7.0.gemfile +15 -0
  10. data/gemfiles/rails_7.1.gemfile +15 -0
  11. data/gemfiles/rails_7.2.gemfile +15 -0
  12. data/lib/quo/collection_backed_query.rb +87 -0
  13. data/lib/quo/collection_results.rb +44 -0
  14. data/lib/quo/composed_query.rb +168 -0
  15. data/lib/quo/engine.rb +11 -0
  16. data/lib/quo/minitest/helpers.rb +41 -0
  17. data/lib/quo/preloadable.rb +46 -0
  18. data/lib/quo/query.rb +97 -214
  19. data/lib/quo/relation_backed_query.rb +177 -0
  20. data/lib/quo/relation_results.rb +58 -0
  21. data/lib/quo/results.rb +48 -44
  22. data/lib/quo/rspec/helpers.rb +31 -9
  23. data/lib/quo/testing/collection_backed_fake.rb +29 -0
  24. data/lib/quo/testing/relation_backed_fake.rb +52 -0
  25. data/lib/quo/version.rb +3 -1
  26. data/lib/quo.rb +22 -30
  27. data/rbs_collection.yaml +0 -2
  28. data/sig/generated/quo/collection_backed_query.rbs +39 -0
  29. data/sig/generated/quo/collection_results.rbs +30 -0
  30. data/sig/generated/quo/composed_query.rbs +83 -0
  31. data/sig/generated/quo/engine.rbs +6 -0
  32. data/sig/generated/quo/preloadable.rbs +29 -0
  33. data/sig/generated/quo/query.rbs +98 -0
  34. data/sig/generated/quo/relation_backed_query.rbs +90 -0
  35. data/sig/generated/quo/relation_results.rbs +38 -0
  36. data/sig/generated/quo/results.rbs +39 -0
  37. data/sig/generated/quo/version.rbs +5 -0
  38. data/sig/generated/quo.rbs +9 -0
  39. metadata +67 -30
  40. data/lib/quo/eager_query.rb +0 -51
  41. data/lib/quo/loaded_query.rb +0 -18
  42. data/lib/quo/merged_query.rb +0 -36
  43. data/lib/quo/query_composer.rb +0 -78
  44. data/lib/quo/railtie.rb +0 -7
  45. data/lib/quo/utilities/callstack.rb +0 -21
  46. data/lib/quo/utilities/compose.rb +0 -18
  47. data/lib/quo/utilities/sanitize.rb +0 -19
  48. data/lib/quo/utilities/wrap.rb +0 -23
  49. data/lib/quo/wrapped_query.rb +0 -18
  50. data/sig/quo/eager_query.rbs +0 -15
  51. data/sig/quo/loaded_query.rbs +0 -7
  52. data/sig/quo/merged_query.rbs +0 -19
  53. data/sig/quo/query.rbs +0 -83
  54. data/sig/quo/query_composer.rbs +0 -32
  55. data/sig/quo/results.rbs +0 -22
  56. data/sig/quo/utilities/callstack.rbs +0 -7
  57. data/sig/quo/utilities/compose.rbs +0 -8
  58. data/sig/quo/utilities/sanitize.rbs +0 -9
  59. data/sig/quo/utilities/wrap.rbs +0 -11
  60. data/sig/quo/wrapped_query.rbs +0 -11
  61. data/sig/quo.rbs +0 -41
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 78b41f6d815238ed48ed00790498c1d6d7e8c099c0312fe82a441dcbe68d667f
4
- data.tar.gz: 84d11fe9c86ef448d17229f0544a1966f55597ce1a8522e4ef92b626fea24537
3
+ metadata.gz: 7dcdc5faff0797696ddb8db26baebe37f388a020943f7764e4857aaab8ef4c76
4
+ data.tar.gz: 550da1aaaa2bc4d24d82f3e7f78908f22ccd28913b8d9c6358cbc9bbc6ee8fa3
5
5
  SHA512:
6
- metadata.gz: dc89984c05233e3300c62149c457bec4e079c9d2dd646011c5fa348aaee3a66e59e627fcc93051ac992bd167df536b27d51784e241996dd548dde84b25ec3bde
7
- data.tar.gz: 1c3b41c2f541e96121b07bb3ce640846b3e308fcd018625d211b2f96a43ff4fe9cc0372d3bf8602c7fb22594c9935bb72bdd48041bde5e5eb391e6c3969a3ecc
6
+ metadata.gz: 69a94d4d6c4844e6b27e409a662dd76a9ca0dee26fd229a2e861ad377ef2a247ec8499633565d119aa5bc6adeca8930015f33b6ea69e4673d9609f906a1029e5
7
+ data.tar.gz: d511d04d2212029428e74b2771407e7a3adf566c193a3075fffee3d1c3de7d3e9c8aaca8d39330198264be2ead5df7e574ebd90581600d27d5576eef8718cd0b
data/.standard.yml CHANGED
@@ -1,3 +1,6 @@
1
1
  # For available configuration options, see:
2
2
  # https://github.com/testdouble/standard
3
- ruby_version: 2.6
3
+ ruby_version: 3.1
4
+ ignore:
5
+ - "**/*":
6
+ - "Layout/LeadingCommentSpace"
data/Appraisals ADDED
@@ -0,0 +1,11 @@
1
+ appraise "rails-7.0" do
2
+ gem "rails", "~> 7.0"
3
+ end
4
+
5
+ appraise "rails-7.1" do
6
+ gem "rails", "~> 7.1"
7
+ end
8
+
9
+ appraise "rails-7.2" do
10
+ gem "rails", "~> 7.2"
11
+ end
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", "~> 1.3"
17
+ gem "standard", require: false
18
+
19
+ gem "steep", require: false
18
20
 
19
- gem "steep", "~> 1.2"
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 Array (an 'eager loaded' query)
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::MergedQuery`.
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 with each 'eager' query object are concatenated with `+`
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::Query
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::Query
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::Query
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::Query
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::Query
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
- Eager loaded queries can also be composed (see below sections for more details).
187
+ Collection backed queries can also be composed (see below sections for more details).
184
188
 
185
- ### Quo::MergedQuery
189
+ ### Quo::ComposedQuery
186
190
 
187
- The new instance of `Quo::MergedQuery` from a compose process, retains references to the original entities that were
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::MergedQuery[FooQuery, BarQuery]"
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::EagerQuery` & `Quo::LoadedQuery` objects
208
+ ### `Quo::CollectionBackedQuery` & `Quo::CollectionBackedQuery` objects
205
209
 
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.
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 MyEagerQuery < Quo::EagerQuery
216
+ class MyCollectionBackedQuery < Quo::CollectionBackedQuery
212
217
  def collection
213
218
  [1, 2, 3]
214
219
  end
215
220
  end
216
- q = MyEagerQuery.new
217
- q.eager? # is it 'eager'? Yes it is!
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::LoadedQuery`:
227
+ use `Quo::CollectionBackedQuery`:
223
228
 
224
229
  ```ruby
225
- q = Quo::LoadedQuery.new([1, 2, 3])
226
- q.eager? # is it 'eager'? Yes it is!
230
+ q = Quo::CollectionBackedQuery.wrap([1, 2, 3])
231
+ q.collection? # true
227
232
  q.count # '3'
228
233
  ```
229
234
 
230
- `Quo::EagerQuery` also uses `total_count` option value as the specified 'total count', useful when the data is
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 EagerQuery used to wrap a page of enumerable data:
238
+ Example of an CollectionBackedQuery used to wrap a page of enumerable data:
234
239
 
235
240
  ```ruby
236
- Quo::LoadedQuery.new(my_data, total_count: 100, page: current_page)
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::Query
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::LoadedQuery.new([3, 4]).compose(Quo::LoadedQuery.new([1, 2])).last
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 `LoadedQuery` instead with the given `results`.
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 EagerQuery, so will not work for cases were the actual type of the query instance is
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 loosely based on that in the `Rectify` gem; https://github.com/andypike/rectify.
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
@@ -32,6 +32,4 @@ target :lib do
32
32
  ignore "lib/quo/rspec/*.rb"
33
33
  ignore "lib/tasks/*"
34
34
  ignore "lib/quo/railtie.rb"
35
-
36
- library "forwardable"
37
35
  end
@@ -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