quo 0.6.0 → 1.0.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
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