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.
- checksums.yaml +4 -4
- data/.standard.yml +4 -1
- data/Appraisals +11 -0
- data/CHANGELOG.md +78 -0
- data/Gemfile +6 -4
- data/LICENSE.txt +1 -1
- data/README.md +37 -36
- data/Steepfile +0 -2
- data/gemfiles/rails_7.0.gemfile +15 -0
- data/gemfiles/rails_7.1.gemfile +15 -0
- data/gemfiles/rails_7.2.gemfile +15 -0
- data/lib/quo/collection_backed_query.rb +87 -0
- data/lib/quo/collection_results.rb +44 -0
- data/lib/quo/composed_query.rb +168 -0
- data/lib/quo/engine.rb +11 -0
- data/lib/quo/minitest/helpers.rb +41 -0
- data/lib/quo/preloadable.rb +46 -0
- data/lib/quo/query.rb +97 -214
- data/lib/quo/relation_backed_query.rb +177 -0
- data/lib/quo/relation_results.rb +58 -0
- data/lib/quo/results.rb +48 -44
- data/lib/quo/rspec/helpers.rb +31 -9
- data/lib/quo/testing/collection_backed_fake.rb +29 -0
- data/lib/quo/testing/relation_backed_fake.rb +52 -0
- data/lib/quo/version.rb +3 -1
- data/lib/quo.rb +22 -30
- data/rbs_collection.yaml +0 -2
- data/sig/generated/quo/collection_backed_query.rbs +39 -0
- data/sig/generated/quo/collection_results.rbs +30 -0
- data/sig/generated/quo/composed_query.rbs +83 -0
- data/sig/generated/quo/engine.rbs +6 -0
- data/sig/generated/quo/preloadable.rbs +29 -0
- data/sig/generated/quo/query.rbs +98 -0
- data/sig/generated/quo/relation_backed_query.rbs +90 -0
- data/sig/generated/quo/relation_results.rbs +38 -0
- data/sig/generated/quo/results.rbs +39 -0
- data/sig/generated/quo/version.rbs +5 -0
- data/sig/generated/quo.rbs +9 -0
- metadata +67 -30
- data/lib/quo/eager_query.rb +0 -51
- data/lib/quo/loaded_query.rb +0 -18
- data/lib/quo/merged_query.rb +0 -36
- data/lib/quo/query_composer.rb +0 -78
- data/lib/quo/railtie.rb +0 -7
- data/lib/quo/utilities/callstack.rb +0 -21
- data/lib/quo/utilities/compose.rb +0 -18
- data/lib/quo/utilities/sanitize.rb +0 -19
- data/lib/quo/utilities/wrap.rb +0 -23
- data/lib/quo/wrapped_query.rb +0 -18
- data/sig/quo/eager_query.rbs +0 -15
- data/sig/quo/loaded_query.rbs +0 -7
- data/sig/quo/merged_query.rbs +0 -19
- data/sig/quo/query.rbs +0 -83
- data/sig/quo/query_composer.rbs +0 -32
- data/sig/quo/results.rbs +0 -22
- data/sig/quo/utilities/callstack.rbs +0 -7
- data/sig/quo/utilities/compose.rbs +0 -8
- data/sig/quo/utilities/sanitize.rbs +0 -9
- data/sig/quo/utilities/wrap.rbs +0 -11
- data/sig/quo/wrapped_query.rbs +0 -11
- data/sig/quo.rbs +0 -41
@@ -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
|