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
@@ -0,0 +1,177 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# rbs_inline: enabled
|
4
|
+
|
5
|
+
require "literal"
|
6
|
+
|
7
|
+
module Quo
|
8
|
+
class RelationBackedQuery < Query
|
9
|
+
# @rbs query: ActiveRecord::Relation | Quo::Query
|
10
|
+
# @rbs props: Hash[Symbol, untyped]
|
11
|
+
# @rbs &block: () -> ActiveRecord::Relation | Quo::Query | Object & Enumerable[untyped]
|
12
|
+
# @rbs return: Quo::RelationBackedQuery
|
13
|
+
def self.wrap(query = nil, props: {}, &block)
|
14
|
+
raise ArgumentError, "either a query or a block must be provided" unless query || block
|
15
|
+
|
16
|
+
klass = Class.new(self) do
|
17
|
+
props.each do |name, property|
|
18
|
+
if property.is_a?(Literal::Property)
|
19
|
+
prop name, property.type, property.kind, reader: property.reader, writer: property.writer, default: property.default
|
20
|
+
else
|
21
|
+
prop name, property
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
if block
|
26
|
+
klass.define_method(:query, &block)
|
27
|
+
else
|
28
|
+
klass.define_method(:query) { query }
|
29
|
+
end
|
30
|
+
klass
|
31
|
+
end
|
32
|
+
|
33
|
+
# @rbs conditions: untyped?
|
34
|
+
# @rbs return: String
|
35
|
+
def self.sanitize_sql_for_conditions(conditions)
|
36
|
+
ActiveRecord::Base.sanitize_sql_for_conditions(conditions)
|
37
|
+
end
|
38
|
+
|
39
|
+
# @rbs string: String
|
40
|
+
# @rbs return: String
|
41
|
+
def self.sanitize_sql_string(string)
|
42
|
+
sanitize_sql_for_conditions(["'%s'", string])
|
43
|
+
end
|
44
|
+
|
45
|
+
# @rbs value: untyped
|
46
|
+
# @rbs return: String
|
47
|
+
def self.sanitize_sql_parameter(value)
|
48
|
+
sanitize_sql_for_conditions(["?", value])
|
49
|
+
end
|
50
|
+
|
51
|
+
# These store options related to building the underlying query, we don't want to expose these as public properties
|
52
|
+
# @rbs!
|
53
|
+
# @_rel_group: untyped?
|
54
|
+
# @_rel_distinct: bool?
|
55
|
+
# @_rel_order: untyped?
|
56
|
+
# @_rel_limit: untyped?
|
57
|
+
# @_rel_preload: untyped?
|
58
|
+
# @_rel_includes: untyped?
|
59
|
+
# @_rel_select: untyped?
|
60
|
+
prop :_rel_group, _Nilable(_Any), reader: false, writer: false
|
61
|
+
prop :_rel_distinct, _Nilable(_Boolean), reader: false, writer: false
|
62
|
+
prop :_rel_order, _Nilable(_Any), reader: false, writer: false
|
63
|
+
prop :_rel_limit, _Nilable(_Any), reader: false, writer: false
|
64
|
+
prop :_rel_preload, _Nilable(_Any), reader: false, writer: false
|
65
|
+
prop :_rel_includes, _Nilable(_Any), reader: false, writer: false
|
66
|
+
prop :_rel_select, _Nilable(_Any), reader: false, writer: false
|
67
|
+
|
68
|
+
# Methods to prepare the query
|
69
|
+
|
70
|
+
# SQL 'SELECT' configuration, calls to underlying AR relation
|
71
|
+
# @rbs *options: untyped
|
72
|
+
# @rbs return: Quo::Query
|
73
|
+
def select(*options)
|
74
|
+
copy(_rel_select: options)
|
75
|
+
end
|
76
|
+
|
77
|
+
# SQL 'LIMIT' configuration, calls to underlying AR relation
|
78
|
+
# @rbs limit: untyped
|
79
|
+
# @rbs return: Quo::Query
|
80
|
+
def limit(limit)
|
81
|
+
copy(_rel_limit: limit)
|
82
|
+
end
|
83
|
+
|
84
|
+
# SQL 'ORDER BY' configuration, calls to underlying AR relation
|
85
|
+
# @rbs options: untyped
|
86
|
+
# @rbs return: Quo::Query
|
87
|
+
def order(options)
|
88
|
+
copy(_rel_order: options)
|
89
|
+
end
|
90
|
+
|
91
|
+
# SQL 'GROUP BY' configuration, calls to underlying AR relation
|
92
|
+
# @rbs *options: untyped
|
93
|
+
# @rbs return: Quo::Query
|
94
|
+
def group(*options)
|
95
|
+
copy(_rel_group: options)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Configures underlying AR relation to include associations
|
99
|
+
# @rbs *options: untyped
|
100
|
+
# @rbs return: Quo::Query
|
101
|
+
def includes(*options)
|
102
|
+
copy(_rel_includes: options)
|
103
|
+
end
|
104
|
+
|
105
|
+
# Configures underlying AR relation to preload associations
|
106
|
+
# @rbs *options: untyped
|
107
|
+
# @rbs return: Quo::Query
|
108
|
+
def preload(*options)
|
109
|
+
copy(_rel_preload: options)
|
110
|
+
end
|
111
|
+
|
112
|
+
# Calls to underlying AR distinct method
|
113
|
+
# @rbs enabled: bool
|
114
|
+
# @rbs return: Quo::Query
|
115
|
+
def distinct(enabled = true)
|
116
|
+
copy(_rel_distinct: enabled)
|
117
|
+
end
|
118
|
+
|
119
|
+
# Should these also be exposed? whats the point of exposing AR relation directly?
|
120
|
+
def eager_load
|
121
|
+
end
|
122
|
+
|
123
|
+
def joins
|
124
|
+
end
|
125
|
+
|
126
|
+
def left_outer_joins
|
127
|
+
end
|
128
|
+
|
129
|
+
# Delegate methods that let us get the model class (available on AR relations)
|
130
|
+
# @rbs def model: () -> (untyped | nil)
|
131
|
+
# @rbs def klass: () -> (untyped | nil)
|
132
|
+
delegate :model, :klass, to: :underlying_query
|
133
|
+
|
134
|
+
# @rbs return: Quo::CollectionBackedQuery
|
135
|
+
def to_collection(total_count: nil)
|
136
|
+
Quo.collection_backed_query_base_class.wrap(results.to_a).new(total_count:)
|
137
|
+
end
|
138
|
+
|
139
|
+
def results #: Quo::Results
|
140
|
+
Quo::RelationResults.new(self, transformer: transformer)
|
141
|
+
end
|
142
|
+
|
143
|
+
# Return the SQL string for this query if its a relation type query object
|
144
|
+
def to_sql #: String
|
145
|
+
configured_query.to_sql if relation?
|
146
|
+
end
|
147
|
+
|
148
|
+
private
|
149
|
+
|
150
|
+
def validated_query
|
151
|
+
query.tap do |q|
|
152
|
+
raise ArgumentError, "#query must return an ActiveRecord Relation or a Quo::Query instance" unless query.nil? || q.is_a?(::ActiveRecord::Relation) || q.is_a?(Quo::Query)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# The underlying query is essentially the configured query with optional extras setup
|
157
|
+
def underlying_query #: ActiveRecord::Relation
|
158
|
+
rel = quo_unwrap_unpaginated_query(validated_query)
|
159
|
+
|
160
|
+
rel = rel.group(@_rel_group) if @_rel_group.present?
|
161
|
+
rel = rel.distinct if @_rel_distinct
|
162
|
+
rel = rel.order(@_rel_order) if @_rel_order.present?
|
163
|
+
rel = rel.limit(@_rel_limit) if @_rel_limit.present?
|
164
|
+
rel = rel.preload(@_rel_preload) if @_rel_preload.present?
|
165
|
+
rel = rel.includes(@_rel_includes) if @_rel_includes.present?
|
166
|
+
@_rel_select.present? ? rel.select(@_rel_select) : rel
|
167
|
+
end
|
168
|
+
|
169
|
+
# The configured query is the underlying query with paging
|
170
|
+
def configured_query #: ActiveRecord::Relation
|
171
|
+
q = underlying_query
|
172
|
+
return q unless paged?
|
173
|
+
|
174
|
+
q.offset(offset).limit(sanitised_page_size)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# rbs_inline: enabled
|
4
|
+
|
5
|
+
module Quo
|
6
|
+
class RelationResults < Results
|
7
|
+
# @rbs query: Quo::Query
|
8
|
+
# @rbs transformer: (^(untyped, ?Integer) -> untyped)?
|
9
|
+
# @rbs return: void
|
10
|
+
def initialize(query, transformer: nil)
|
11
|
+
raise ArgumentError, "Query must be a RelationBackedQuery" unless query.is_a?(Quo::RelationBackedQuery)
|
12
|
+
@query = query
|
13
|
+
@configured_query = query.unwrap
|
14
|
+
@transformer = transformer
|
15
|
+
end
|
16
|
+
|
17
|
+
# Are there any results for this query?
|
18
|
+
def exists? #: bool
|
19
|
+
return @configured_query.exists? if @query.relation?
|
20
|
+
@configured_query.present?
|
21
|
+
end
|
22
|
+
|
23
|
+
# Gets the count of all results ignoring the current page and page size (if set).
|
24
|
+
def total_count #: Integer
|
25
|
+
count_query(@query.unwrap_unpaginated)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Gets the actual count of elements in the page of results (assuming paging is being used, otherwise the count of
|
29
|
+
# all results)
|
30
|
+
def page_count #: Integer
|
31
|
+
count_query(@configured_query)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
# @rbs @query: Quo::RelationBackedQuery
|
37
|
+
# @rbs @configured_query: ActiveRecord::Relation
|
38
|
+
|
39
|
+
# Note we reselect the query as this prevents query errors if the SELECT clause is not compatible with COUNT
|
40
|
+
# (SQLException: wrong number of arguments to function COUNT()). We do this in two ways, either with the primary key
|
41
|
+
# or with Arel.star. The primary key is the most compatible way to count, but if the query does not have a primary
|
42
|
+
# we fallback. The fallback "*" wont work in certain situations though, specifically if we have a limit() on the query
|
43
|
+
# which Arel constructs as a subquery. In this case we will get a SQL error as the generated SQL contains
|
44
|
+
# `SELECT COUNT(count_column) FROM (SELECT * AS count_column FROM ...) subquery_for_count` where the error is:
|
45
|
+
# `ActiveRecord::StatementInvalid: SQLite3::SQLException: near "AS": syntax error`
|
46
|
+
# Either way DB engines know how to count efficiently.
|
47
|
+
# @rbs query: ActiveRecord::Relation
|
48
|
+
# @rbs return: Integer
|
49
|
+
def count_query(query)
|
50
|
+
pk = query.model.primary_key
|
51
|
+
if pk
|
52
|
+
query.reselect(pk).count
|
53
|
+
else
|
54
|
+
query.reselect(Arel.star).count
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/quo/results.rb
CHANGED
@@ -1,85 +1,89 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
require_relative "./utilities/callstack"
|
3
|
+
# rbs_inline: enabled
|
5
4
|
|
6
5
|
module Quo
|
7
6
|
class Results
|
8
|
-
|
9
|
-
|
7
|
+
def empty? #: bool
|
8
|
+
!exists?
|
9
|
+
end
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
@transformer = transformer
|
11
|
+
# Alias for total_count
|
12
|
+
def count #: Integer
|
13
|
+
total_count
|
15
14
|
end
|
16
15
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
:any?,
|
22
|
-
:none?,
|
23
|
-
:one?,
|
24
|
-
:count
|
16
|
+
# Alias for total_count
|
17
|
+
def size #: Integer
|
18
|
+
total_count
|
19
|
+
end
|
25
20
|
|
21
|
+
# Alias for page_count
|
22
|
+
def page_size #: Integer
|
23
|
+
page_count
|
24
|
+
end
|
25
|
+
|
26
|
+
# @rbs &block: (untyped, *untyped) -> untyped
|
27
|
+
# @rbs return: Hash[untyped, Array[untyped]]
|
26
28
|
def group_by(&block)
|
27
|
-
|
28
|
-
grouped = unwrapped.group_by do |*block_args|
|
29
|
+
grouped = @configured_query.group_by do |*block_args|
|
29
30
|
x = block_args.first
|
30
|
-
transformed =
|
31
|
+
transformed = transform? ? @transformer.call(x) : x
|
31
32
|
block ? block.call(transformed, *(block_args[1..] || [])) : transformed
|
32
33
|
end
|
33
34
|
|
34
35
|
grouped.tap do |groups|
|
35
36
|
groups.transform_values! do |values|
|
36
|
-
transformer ? values.map { |x| transformer.call(x) } : values
|
37
|
+
@transformer ? values.map { |x| @transformer.call(x) } : values
|
37
38
|
end
|
38
39
|
end
|
39
40
|
end
|
40
41
|
|
41
42
|
# Delegate other enumerable methods to underlying collection but also transform
|
43
|
+
# @rbs override
|
42
44
|
def method_missing(method, *args, **kwargs, &block)
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
end
|
52
|
-
else
|
53
|
-
raw = unwrapped.send(method, *args, **kwargs)
|
54
|
-
# FIXME: consider how to handle applying a transformer to a Enumerator...
|
55
|
-
return raw if raw.is_a?(Quo::Results) || raw.is_a?(::Enumerator)
|
56
|
-
transform_results(raw)
|
45
|
+
return super unless respond_to_missing?(method)
|
46
|
+
|
47
|
+
if block
|
48
|
+
@configured_query.send(method, *args, **kwargs) do |*block_args|
|
49
|
+
x = block_args.first
|
50
|
+
transformed = transform? ? @transformer.call(x) : x
|
51
|
+
other_args = block_args[1..] || []
|
52
|
+
block.call(transformed, *other_args)
|
57
53
|
end
|
58
54
|
else
|
59
|
-
|
55
|
+
raw = @configured_query.send(method, *args, **kwargs)
|
56
|
+
# FIXME: consider how to handle applying a transformer to a Enumerator...
|
57
|
+
return raw if raw.is_a?(Quo::RelationResults) || raw.is_a?(::Enumerator)
|
58
|
+
transform_results(raw)
|
60
59
|
end
|
61
60
|
end
|
62
61
|
|
62
|
+
# @rbs name: Symbol
|
63
|
+
# @rbs include_private: bool
|
64
|
+
# @rbs return: bool
|
63
65
|
def respond_to_missing?(name, include_private = false)
|
64
|
-
|
66
|
+
@configured_query.respond_to?(name, include_private)
|
67
|
+
end
|
68
|
+
|
69
|
+
def transform? #: bool
|
70
|
+
@transformer.present?
|
65
71
|
end
|
66
72
|
|
67
73
|
private
|
68
74
|
|
69
|
-
|
75
|
+
# @rbs @transformer: (^(untyped, ?Integer) -> untyped)?
|
70
76
|
|
77
|
+
# @rbs results: untyped
|
78
|
+
# @rbs return: untyped
|
71
79
|
def transform_results(results)
|
72
|
-
return results unless
|
80
|
+
return results unless transform?
|
73
81
|
|
74
82
|
if results.is_a?(Enumerable)
|
75
|
-
results.map.with_index { |item, i| transformer.call(item, i) }
|
83
|
+
results.map.with_index { |item, i| @transformer.call(item, i) }
|
76
84
|
else
|
77
|
-
transformer.call(results)
|
85
|
+
@transformer.call(results)
|
78
86
|
end
|
79
87
|
end
|
80
|
-
|
81
|
-
def enumerable_methods_supported
|
82
|
-
[:find_each] + Enumerable.instance_methods
|
83
|
-
end
|
84
88
|
end
|
85
89
|
end
|
data/lib/quo/rspec/helpers.rb
CHANGED
@@ -1,18 +1,40 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative "../testing/collection_backed_fake"
|
4
|
+
require_relative "../testing/relation_backed_fake"
|
5
|
+
|
3
6
|
module Quo
|
4
7
|
module Rspec
|
5
8
|
module Helpers
|
6
|
-
def
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
9
|
+
def fake_query(query_class, with: nil, results: [], total_count: nil, page_count: nil, &block)
|
10
|
+
# make it so that results of instances of this class return a fake Result object
|
11
|
+
# of the right type which returns the results passed in
|
12
|
+
if query_class < Quo::CollectionBackedQuery
|
13
|
+
klass = Class.new(Quo::Testing::CollectionBackedFake) do
|
14
|
+
if query_class < Quo::Preloadable
|
15
|
+
include Quo::Preloadable
|
16
|
+
|
17
|
+
def query
|
18
|
+
collection
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
fake = ->(*kwargs) {
|
23
|
+
klass.new(results: results, total_count: total_count, page_count: page_count)
|
24
|
+
}
|
25
|
+
expectation = allow(query_class).to receive(:new)
|
26
|
+
expectation = expectation.with(with) if with
|
27
|
+
expectation.and_invoke(fake)
|
28
|
+
elsif query_class < Quo::RelationBackedQuery
|
29
|
+
fake = ->(*kwargs) {
|
30
|
+
Quo::Testing::RelationBackedFake.new(results: results, total_count: total_count, page_count: page_count)
|
31
|
+
}
|
32
|
+
expectation = allow(query_class).to receive(:new)
|
33
|
+
expectation = expectation.with(with) if with
|
34
|
+
expectation.and_invoke(fake)
|
35
|
+
else
|
36
|
+
raise ArgumentError, "Not a Query class: #{query_class}"
|
14
37
|
end
|
15
|
-
allow(query_class).to receive(:new) { ::Quo::LoadedQuery.new(results) }
|
16
38
|
end
|
17
39
|
end
|
18
40
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# rbs_inline: enabled
|
4
|
+
|
5
|
+
module Quo
|
6
|
+
module Testing
|
7
|
+
class CollectionBackedFake < Quo.collection_backed_query_base_class
|
8
|
+
prop :results, _Any, reader: false
|
9
|
+
prop :page_count, _Nilable(Integer), reader: false
|
10
|
+
|
11
|
+
def collection
|
12
|
+
@results
|
13
|
+
end
|
14
|
+
|
15
|
+
def results
|
16
|
+
klass = Class.new(CollectionResults) do
|
17
|
+
def page_count
|
18
|
+
@query.page_count
|
19
|
+
end
|
20
|
+
end
|
21
|
+
klass.new(self)
|
22
|
+
end
|
23
|
+
|
24
|
+
def page_count
|
25
|
+
@page_count || validated_query.size
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# rbs_inline: enabled
|
4
|
+
|
5
|
+
module Quo
|
6
|
+
module Testing
|
7
|
+
class RelationBackedFake < Quo.relation_backed_query_base_class
|
8
|
+
prop :results, _Any, reader: false
|
9
|
+
prop :page_count, _Nilable(Integer), reader: false
|
10
|
+
prop :total_count, _Nilable(Integer), reader: false
|
11
|
+
|
12
|
+
def query
|
13
|
+
@results
|
14
|
+
end
|
15
|
+
|
16
|
+
def results
|
17
|
+
klass = Class.new(RelationResults) do
|
18
|
+
def page_count
|
19
|
+
@query.page_count
|
20
|
+
end
|
21
|
+
|
22
|
+
def total_count
|
23
|
+
@query.total_count
|
24
|
+
end
|
25
|
+
end
|
26
|
+
klass.new(self)
|
27
|
+
end
|
28
|
+
|
29
|
+
def page_count
|
30
|
+
@page_count || validated_query.size
|
31
|
+
end
|
32
|
+
|
33
|
+
def total_count
|
34
|
+
@total_count || validated_query.size
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def validated_query
|
40
|
+
query
|
41
|
+
end
|
42
|
+
|
43
|
+
def underlying_query
|
44
|
+
validated_query
|
45
|
+
end
|
46
|
+
|
47
|
+
def configured_query
|
48
|
+
validated_query
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/lib/quo/version.rb
CHANGED
data/lib/quo.rb
CHANGED
@@ -1,40 +1,32 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# rbs_inline: enabled
|
4
|
+
|
3
5
|
require_relative "quo/version"
|
4
|
-
|
5
|
-
require_relative "quo/query"
|
6
|
-
require_relative "quo/eager_query"
|
7
|
-
require_relative "quo/loaded_query"
|
8
|
-
require_relative "quo/merged_query"
|
9
|
-
require_relative "quo/wrapped_query"
|
10
|
-
require_relative "quo/query_composer"
|
11
|
-
require_relative "quo/results"
|
6
|
+
require "quo/engine"
|
12
7
|
|
13
8
|
module Quo
|
14
|
-
|
15
|
-
def configuration
|
16
|
-
@configuration ||= Configuration.new
|
17
|
-
end
|
9
|
+
extend ActiveSupport::Autoload
|
18
10
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
11
|
+
autoload :Query
|
12
|
+
autoload :Preloadable
|
13
|
+
autoload :RelationBackedQuery
|
14
|
+
autoload :Results
|
15
|
+
autoload :RelationResults
|
16
|
+
autoload :CollectionResults
|
17
|
+
autoload :ComposedQuery
|
18
|
+
autoload :CollectionBackedQuery
|
24
19
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
20
|
+
mattr_accessor :relation_backed_query_base_class, default: "Quo::RelationBackedQuery"
|
21
|
+
mattr_accessor :collection_backed_query_base_class, default: "Quo::CollectionBackedQuery"
|
22
|
+
mattr_accessor :max_page_size, default: 200
|
23
|
+
mattr_accessor :default_page_size, default: 20
|
24
|
+
|
25
|
+
def self.relation_backed_query_base_class #: Quo::RelationBackedQuery
|
26
|
+
@@relation_backed_query_base_class.constantize
|
27
|
+
end
|
31
28
|
|
32
|
-
|
33
|
-
|
34
|
-
@query_show_callstack_size = 10
|
35
|
-
@logger = nil
|
36
|
-
@max_page_size = 200
|
37
|
-
@default_page_size = 20
|
38
|
-
end
|
29
|
+
def self.collection_backed_query_base_class #: Quo::CollectionBackedQuery
|
30
|
+
@@collection_backed_query_base_class.constantize
|
39
31
|
end
|
40
32
|
end
|
data/rbs_collection.yaml
CHANGED
@@ -0,0 +1,39 @@
|
|
1
|
+
# Generated from lib/quo/collection_backed_query.rb with RBS::Inline
|
2
|
+
|
3
|
+
module Quo
|
4
|
+
class CollectionBackedQuery < Query
|
5
|
+
# Wrap an enumerable collection or a block that returns an enumerable collection
|
6
|
+
# @rbs data: untyped, props: Symbol => untyped, block: () -> untyped
|
7
|
+
# @rbs return: Quo::CollectionBackedQuery
|
8
|
+
def self.wrap: (?untyped data, ?props: untyped) ?{ (?) -> untyped } -> Quo::CollectionBackedQuery
|
9
|
+
|
10
|
+
# @rbs return: Object & Enumerable[untyped]
|
11
|
+
def collection: () -> (Object & Enumerable[untyped])
|
12
|
+
|
13
|
+
# The default implementation of `query` just calls `collection`, however you can also
|
14
|
+
# override this method to return an ActiveRecord::Relation or any other query-like object as usual in a Query object.
|
15
|
+
# @rbs return: Object & Enumerable[untyped]
|
16
|
+
def query: () -> (Object & Enumerable[untyped])
|
17
|
+
|
18
|
+
def results: () -> untyped
|
19
|
+
|
20
|
+
# @rbs override
|
21
|
+
def relation?: ...
|
22
|
+
|
23
|
+
# @rbs override
|
24
|
+
def collection?: ...
|
25
|
+
|
26
|
+
# @rbs override
|
27
|
+
def to_collection: ...
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def validated_query: () -> untyped
|
32
|
+
|
33
|
+
# @rbs return: Object & Enumerable[untyped]
|
34
|
+
def underlying_query: () -> (Object & Enumerable[untyped])
|
35
|
+
|
36
|
+
# The configured query is the underlying query with paging
|
37
|
+
def configured_query: () -> (Object & Enumerable[untyped])
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# Generated from lib/quo/collection_results.rb with RBS::Inline
|
2
|
+
|
3
|
+
module Quo
|
4
|
+
class CollectionResults < Results
|
5
|
+
# @rbs override
|
6
|
+
def initialize: ...
|
7
|
+
|
8
|
+
# Are there any results for this query?
|
9
|
+
def exists?: () -> bool
|
10
|
+
|
11
|
+
def empty?: () -> bool
|
12
|
+
|
13
|
+
# Gets the count of all results ignoring the current page and page size (if set).
|
14
|
+
# Optionally return the `total_count` option if it has been set.
|
15
|
+
# This is useful when the total count is known and not equal to size
|
16
|
+
# of wrapped collection.
|
17
|
+
# @rbs override
|
18
|
+
def total_count: ...
|
19
|
+
|
20
|
+
# Gets the actual count of elements in the page of results (assuming paging is being used, otherwise the count of
|
21
|
+
# all results)
|
22
|
+
def page_count: () -> Integer
|
23
|
+
|
24
|
+
@query: Quo::CollectionBackedQuery
|
25
|
+
|
26
|
+
@transformer: (^(untyped, ?Integer) -> untyped)?
|
27
|
+
|
28
|
+
@configured_query: Object & Enumerable[untyped]
|
29
|
+
end
|
30
|
+
end
|