quo 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +3 -3
- data/Steepfile +4 -1
- data/lib/quo/eager_query.rb +23 -3
- data/lib/quo/merged_query.rb +30 -17
- data/lib/quo/query.rb +45 -26
- data/lib/quo/query_composer.rb +31 -41
- data/lib/quo/{enumerator.rb → results.rb} +26 -19
- data/lib/quo/rspec/helpers.rb +2 -2
- data/lib/quo/utilities/callstack.rb +6 -5
- data/lib/quo/utilities/wrap.rb +2 -2
- data/lib/quo/version.rb +1 -1
- data/lib/quo/wrapped_query.rb +30 -0
- data/lib/quo.rb +9 -2
- data/sig/quo/eager_query.rbs +18 -0
- data/sig/quo/merged_query.rbs +19 -0
- data/sig/quo/query.rbs +83 -0
- data/sig/quo/query_composer.rbs +32 -0
- data/sig/quo/results.rbs +22 -0
- data/sig/quo/utilities/callstack.rbs +7 -0
- data/sig/quo/utilities/compose.rbs +8 -0
- data/sig/quo/utilities/sanitize.rbs +9 -0
- data/sig/quo/utilities/wrap.rbs +11 -0
- data/sig/quo/wrapped_query.rbs +11 -0
- data/sig/quo.rbs +28 -193
- metadata +14 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 61bf7495c0092bf641ff6e85bed8dc0073ea61e15a5292f0825f50eca4348a92
|
4
|
+
data.tar.gz: c10cc10dfe8ce323e4395640604a1c6358333bb7ea26b1c109d43eee67ed2d73
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e8e4105330e725058ca4c94f2c58229a0018e9379c38ac189cc2dbd007163468b845dd9632d727f824a35d4bc8cebbb7e40f5d24267277b0c1cbe0b580abb100
|
7
|
+
data.tar.gz: 7180ea2d74f1849628a1635c381685c5d2841c27229868280cd59a580dae759d61cc5236fba06b4044a93b667a23f670f4ba5960720165ccb1a6a103a4d06f37
|
data/README.md
CHANGED
@@ -229,7 +229,7 @@ q.count # array size
|
|
229
229
|
`Quo::EagerQuery` is a subclass of `Quo::Query` which takes a data value on instantiation and returns it on calls to `query`
|
230
230
|
|
231
231
|
```ruby
|
232
|
-
q = Quo::EagerQuery.new(
|
232
|
+
q = Quo::EagerQuery.new([1, 2, 3])
|
233
233
|
q.eager? # is it 'eager'? Yes it is!
|
234
234
|
q.count # '3'
|
235
235
|
```
|
@@ -242,7 +242,7 @@ actually just a page of the data and not the total count.
|
|
242
242
|
Example of an EagerQuery used to wrap a page of enumerable data:
|
243
243
|
|
244
244
|
```ruby
|
245
|
-
Quo::EagerQuery.new(
|
245
|
+
Quo::EagerQuery.new(my_data, total_count: 100, page: current_page)
|
246
246
|
```
|
247
247
|
|
248
248
|
### Composition
|
@@ -262,7 +262,7 @@ composed.last
|
|
262
262
|
composed.first
|
263
263
|
# => #<Tag id: ...>
|
264
264
|
|
265
|
-
Quo::EagerQuery.new([3, 4]).compose(Quo::EagerQuery.new(
|
265
|
+
Quo::EagerQuery.new([3, 4]).compose(Quo::EagerQuery.new([1, 2])).last
|
266
266
|
# => 2
|
267
267
|
Quo::Query.compose([1, 2], [3, 4]).last
|
268
268
|
# => 4
|
data/Steepfile
CHANGED
data/lib/quo/eager_query.rb
CHANGED
@@ -2,9 +2,29 @@
|
|
2
2
|
|
3
3
|
module Quo
|
4
4
|
class EagerQuery < Quo::Query
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
class << self
|
6
|
+
def call(**options)
|
7
|
+
build_from_options(options).first
|
8
|
+
end
|
9
|
+
|
10
|
+
def call!(**options)
|
11
|
+
build_from_options(options).first!
|
12
|
+
end
|
13
|
+
|
14
|
+
def build_from_options(options)
|
15
|
+
collection = options[:collection]
|
16
|
+
raise ArgumentError, "EagerQuery needs a collection" unless collection
|
17
|
+
new(collection, **options.except(:collection))
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(collection, **options)
|
22
|
+
@collection = Array.wrap(collection)
|
23
|
+
super(**options)
|
24
|
+
end
|
25
|
+
|
26
|
+
def copy(**options)
|
27
|
+
self.class.new(@collection, **@options.merge(options))
|
8
28
|
end
|
9
29
|
|
10
30
|
# Optionally return the `total_count` option if it has been set.
|
data/lib/quo/merged_query.rb
CHANGED
@@ -2,39 +2,52 @@
|
|
2
2
|
|
3
3
|
module Quo
|
4
4
|
class MergedQuery < Quo::Query
|
5
|
-
|
6
|
-
|
5
|
+
class << self
|
6
|
+
def call(**options)
|
7
|
+
build_from_options(options).first
|
8
|
+
end
|
9
|
+
|
10
|
+
def call!(**options)
|
11
|
+
build_from_options(options).first!
|
12
|
+
end
|
13
|
+
|
14
|
+
def build_from_options(options)
|
15
|
+
merged_query = options[:merged_query]
|
16
|
+
left = options[:left]
|
17
|
+
right = options[:right]
|
18
|
+
raise ArgumentError, "MergedQuery needs the merged result and operands" unless merged_query && left && right
|
19
|
+
new(merged_query, left, right, **options)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize(merged_query, left, right, **options)
|
24
|
+
@merged_query = merged_query
|
25
|
+
@left = left
|
26
|
+
@right = right
|
7
27
|
super(**options)
|
8
28
|
end
|
9
29
|
|
10
30
|
def query
|
11
|
-
@
|
31
|
+
@merged_query
|
12
32
|
end
|
13
33
|
|
14
|
-
def
|
15
|
-
left
|
16
|
-
right = operand_desc(source_queries_right)
|
17
|
-
"Quo::MergedQuery[#{left}, #{right}]"
|
34
|
+
def copy(**options)
|
35
|
+
self.class.new(query, left, right, **@options.merge(options))
|
18
36
|
end
|
19
37
|
|
20
|
-
|
21
|
-
|
22
|
-
def source_queries_left
|
23
|
-
source_queries&.first
|
38
|
+
def to_s
|
39
|
+
"Quo::MergedQuery[#{operand_desc(left)}, #{operand_desc(right)}]"
|
24
40
|
end
|
25
41
|
|
26
|
-
|
27
|
-
source_queries&.last
|
28
|
-
end
|
42
|
+
private
|
29
43
|
|
30
|
-
attr_reader :
|
44
|
+
attr_reader :left, :right
|
31
45
|
|
32
46
|
def operand_desc(operand)
|
33
|
-
return unless operand
|
34
47
|
if operand.is_a? Quo::MergedQuery
|
35
48
|
operand.to_s
|
36
49
|
else
|
37
|
-
operand.class.name
|
50
|
+
operand.class.name || "(anonymous)"
|
38
51
|
end
|
39
52
|
end
|
40
53
|
end
|
data/lib/quo/query.rb
CHANGED
@@ -28,14 +28,11 @@ module Quo
|
|
28
28
|
def initialize(**options)
|
29
29
|
@options = options
|
30
30
|
@current_page = options[:page]&.to_i || options[:current_page]&.to_i
|
31
|
-
@page_size = options[:page_size]&.to_i || 20
|
32
|
-
@scope = unwrap_relation(options[:scope])
|
31
|
+
@page_size = options[:page_size]&.to_i || Quo.configuration.default_page_size || 20
|
33
32
|
end
|
34
33
|
|
35
34
|
# Returns a active record query, or a Quo::Query instance
|
36
|
-
# You must provide an implementation of this of pass the 'scope' option on instantiation
|
37
35
|
def query
|
38
|
-
return @scope unless @scope.nil?
|
39
36
|
raise NotImplementedError, "Query objects must define a 'query' method"
|
40
37
|
end
|
41
38
|
|
@@ -96,58 +93,66 @@ module Quo
|
|
96
93
|
delegate :model, :klass, to: :underlying_query
|
97
94
|
|
98
95
|
# Get first elements
|
99
|
-
def first(
|
96
|
+
def first(limit = nil)
|
100
97
|
if transform?
|
101
|
-
res = query_with_logging.first(
|
98
|
+
res = query_with_logging.first(limit)
|
102
99
|
if res.is_a? Array
|
103
|
-
res.map.with_index { |r, i| transformer
|
100
|
+
res.map.with_index { |r, i| transformer&.call(r, i) }
|
104
101
|
elsif !res.nil?
|
105
|
-
transformer
|
102
|
+
transformer&.call(query_with_logging.first(limit))
|
106
103
|
end
|
107
104
|
else
|
108
|
-
query_with_logging.first(
|
105
|
+
query_with_logging.first(limit)
|
109
106
|
end
|
110
107
|
end
|
111
108
|
|
112
|
-
def first!(
|
113
|
-
item = first(
|
109
|
+
def first!(limit = nil)
|
110
|
+
item = first(limit)
|
114
111
|
raise ActiveRecord::RecordNotFound, "No item could be found!" unless item
|
115
112
|
item
|
116
113
|
end
|
117
114
|
|
118
115
|
# Get last elements
|
119
|
-
def last(
|
116
|
+
def last(limit = nil)
|
120
117
|
if transform?
|
121
|
-
res = query_with_logging.last(
|
118
|
+
res = query_with_logging.last(limit)
|
122
119
|
if res.is_a? Array
|
123
|
-
res.map.with_index { |r, i| transformer
|
120
|
+
res.map.with_index { |r, i| transformer&.call(r, i) }
|
124
121
|
elsif !res.nil?
|
125
|
-
transformer
|
122
|
+
transformer&.call(res)
|
126
123
|
end
|
127
124
|
else
|
128
|
-
query_with_logging.last(
|
125
|
+
query_with_logging.last(limit)
|
129
126
|
end
|
130
127
|
end
|
131
128
|
|
132
129
|
# Convert to array
|
133
130
|
def to_a
|
134
131
|
arr = query_with_logging.to_a
|
135
|
-
transform? ? arr.map.with_index { |r, i| transformer
|
132
|
+
transform? ? arr.map.with_index { |r, i| transformer&.call(r, i) } : arr
|
136
133
|
end
|
137
134
|
|
138
135
|
# Convert to EagerQuery, and load all data
|
139
136
|
def to_eager(more_opts = {})
|
140
|
-
Quo::EagerQuery.new(
|
137
|
+
Quo::EagerQuery.new(to_a, **options.merge(more_opts))
|
141
138
|
end
|
142
139
|
alias_method :load, :to_eager
|
143
140
|
|
144
|
-
|
145
|
-
|
146
|
-
Quo::Enumerator.new(self, transformer: transformer)
|
141
|
+
def results
|
142
|
+
Quo::Results.new(self, transformer: transformer)
|
147
143
|
end
|
148
144
|
|
149
|
-
# Some convenience methods for
|
150
|
-
delegate :each,
|
145
|
+
# Some convenience methods for working with results
|
146
|
+
delegate :each,
|
147
|
+
:map,
|
148
|
+
:flat_map,
|
149
|
+
:reduce,
|
150
|
+
:reject,
|
151
|
+
:filter,
|
152
|
+
:find,
|
153
|
+
:include?,
|
154
|
+
:each_with_object,
|
155
|
+
to: :results
|
151
156
|
|
152
157
|
# Set a block used to transform data after query fetching
|
153
158
|
def transform(&block)
|
@@ -202,7 +207,7 @@ module Quo
|
|
202
207
|
private
|
203
208
|
|
204
209
|
def formatted_queries?
|
205
|
-
Quo.configuration
|
210
|
+
!!Quo.configuration.formatted_query_log
|
206
211
|
end
|
207
212
|
|
208
213
|
# 'trim' a query, ie remove comments and remove newlines
|
@@ -221,7 +226,11 @@ module Quo
|
|
221
226
|
|
222
227
|
def offset
|
223
228
|
per_page = sanitised_page_size
|
224
|
-
page = current_page
|
229
|
+
page = if current_page && current_page&.positive?
|
230
|
+
current_page
|
231
|
+
else
|
232
|
+
1
|
233
|
+
end
|
225
234
|
per_page * (page - 1)
|
226
235
|
end
|
227
236
|
|
@@ -233,7 +242,17 @@ module Quo
|
|
233
242
|
end
|
234
243
|
|
235
244
|
def sanitised_page_size
|
236
|
-
|
245
|
+
if page_size && page_size.positive?
|
246
|
+
given_size = page_size.to_i
|
247
|
+
max_page_size = Quo.configuration.max_page_size || 200
|
248
|
+
if given_size > max_page_size
|
249
|
+
max_page_size
|
250
|
+
else
|
251
|
+
given_size
|
252
|
+
end
|
253
|
+
else
|
254
|
+
Quo.configuration.default_page_size || 20
|
255
|
+
end
|
237
256
|
end
|
238
257
|
|
239
258
|
def query_with_logging
|
data/lib/quo/query_composer.rb
CHANGED
@@ -5,36 +5,44 @@ module Quo
|
|
5
5
|
def initialize(left, right, joins = nil)
|
6
6
|
@left = left
|
7
7
|
@right = right
|
8
|
+
@unwrapped_left = unwrap_relation(left)
|
9
|
+
@unwrapped_right = unwrap_relation(right)
|
10
|
+
@left_relation = @unwrapped_left.is_a?(::ActiveRecord::Relation)
|
11
|
+
@right_relation = @unwrapped_right.is_a?(::ActiveRecord::Relation)
|
8
12
|
@joins = joins
|
9
13
|
end
|
10
14
|
|
11
15
|
def compose
|
12
|
-
combined = merge
|
13
16
|
Quo::MergedQuery.new(
|
14
|
-
|
17
|
+
merge_left_and_right,
|
18
|
+
left,
|
19
|
+
right,
|
20
|
+
**merged_options
|
15
21
|
)
|
16
22
|
end
|
17
23
|
|
18
24
|
private
|
19
25
|
|
20
|
-
attr_reader :left, :right, :joins
|
26
|
+
attr_reader :left, :right, :joins, :unwrapped_left, :unwrapped_right
|
21
27
|
|
22
|
-
def
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
28
|
+
def left_relation?
|
29
|
+
@left_relation
|
30
|
+
end
|
31
|
+
|
32
|
+
def right_relation?
|
33
|
+
@right_relation
|
34
|
+
end
|
27
35
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
elsif
|
33
|
-
|
34
|
-
elsif
|
35
|
-
|
36
|
+
def merge_left_and_right
|
37
|
+
# FIXME: Skipping type checks here, as not sure how to make this type check with RBS
|
38
|
+
__skip__ = if both_relations?
|
39
|
+
apply_joins(unwrapped_left, joins).merge(unwrapped_right)
|
40
|
+
elsif left_relation_right_enumerable?
|
41
|
+
unwrapped_left.to_a + unwrapped_right
|
42
|
+
elsif left_enumerable_right_relation?
|
43
|
+
unwrapped_left + unwrapped_right.to_a
|
36
44
|
else
|
37
|
-
|
45
|
+
unwrapped_left + unwrapped_right
|
38
46
|
end
|
39
47
|
end
|
40
48
|
|
@@ -49,38 +57,20 @@ module Quo
|
|
49
57
|
query.is_a?(Quo::Query) ? query.unwrap : query
|
50
58
|
end
|
51
59
|
|
52
|
-
def relation_type?(query)
|
53
|
-
if query.is_a?(::Quo::Query)
|
54
|
-
query.relation?
|
55
|
-
else
|
56
|
-
query.is_a?(::ActiveRecord::Relation)
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
60
|
def apply_joins(left_rel, joins)
|
61
61
|
joins.present? ? left_rel.joins(joins) : left_rel
|
62
62
|
end
|
63
63
|
|
64
|
-
def both_relations?
|
65
|
-
|
66
|
-
end
|
67
|
-
|
68
|
-
def left_relation_right_eager?(left_rel_type, right_rel_type)
|
69
|
-
left_rel_type && !right_rel_type
|
70
|
-
end
|
71
|
-
|
72
|
-
def left_eager_right_relation?(left_rel_type, right_rel_type)
|
73
|
-
!left_rel_type && right_rel_type
|
64
|
+
def both_relations?
|
65
|
+
left_relation? && right_relation?
|
74
66
|
end
|
75
67
|
|
76
|
-
def
|
77
|
-
|
68
|
+
def left_relation_right_enumerable?
|
69
|
+
left_relation? && !right_relation?
|
78
70
|
end
|
79
71
|
|
80
|
-
def
|
81
|
-
|
82
|
-
"#{right.class.name}. You cannot compose queries where #query " \
|
83
|
-
"returns an ActiveRecord::Relation in one and an Enumerable in the other."
|
72
|
+
def left_enumerable_right_relation?
|
73
|
+
!left_relation? && right_relation?
|
84
74
|
end
|
85
75
|
end
|
86
76
|
end
|
@@ -4,7 +4,7 @@ require "forwardable"
|
|
4
4
|
require_relative "./utilities/callstack"
|
5
5
|
|
6
6
|
module Quo
|
7
|
-
class
|
7
|
+
class Results
|
8
8
|
extend Forwardable
|
9
9
|
include Quo::Utilities::Callstack
|
10
10
|
|
@@ -21,31 +21,38 @@ module Quo
|
|
21
21
|
:any?,
|
22
22
|
:none?,
|
23
23
|
:one?,
|
24
|
-
:
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
24
|
+
:count
|
25
|
+
|
26
|
+
def group_by(&block)
|
27
|
+
debug_callstack
|
28
|
+
grouped = unwrapped.group_by do |*block_args|
|
29
|
+
x = block_args.first
|
30
|
+
transformed = transformer ? transformer.call(x) : x
|
31
|
+
block ? block.call(transformed, *(block_args[1..] || [])) : transformed
|
32
|
+
end
|
33
|
+
|
34
|
+
grouped.tap do |groups|
|
35
|
+
groups.transform_values! do |values|
|
36
|
+
transformer ? values.map { |x| transformer.call(x) } : values
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
35
40
|
|
36
41
|
# Delegate other enumerable methods to underlying collection but also transform
|
37
|
-
def method_missing(method, *args, &block)
|
42
|
+
def method_missing(method, *args, **kwargs, &block)
|
38
43
|
if unwrapped.respond_to?(method)
|
39
44
|
debug_callstack
|
40
45
|
if block
|
41
|
-
unwrapped.send(method, *args) do |*block_args|
|
46
|
+
unwrapped.send(method, *args, **kwargs) do |*block_args|
|
42
47
|
x = block_args.first
|
43
|
-
transformed = transformer
|
44
|
-
|
48
|
+
transformed = transformer ? transformer.call(x) : x
|
49
|
+
other_args = block_args[1..] || []
|
50
|
+
block.call(transformed, *other_args)
|
45
51
|
end
|
46
52
|
else
|
47
|
-
raw = unwrapped.send(method, *args)
|
48
|
-
|
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)
|
49
56
|
transform_results(raw)
|
50
57
|
end
|
51
58
|
else
|
@@ -62,7 +69,7 @@ module Quo
|
|
62
69
|
attr_reader :transformer, :unwrapped
|
63
70
|
|
64
71
|
def transform_results(results)
|
65
|
-
return results unless transformer
|
72
|
+
return results unless transformer
|
66
73
|
|
67
74
|
if results.is_a?(Enumerable)
|
68
75
|
results.map.with_index { |item, i| transformer.call(item, i) }
|
data/lib/quo/rspec/helpers.rb
CHANGED
@@ -9,10 +9,10 @@ module Quo
|
|
9
9
|
unless with.nil?
|
10
10
|
return(
|
11
11
|
allow(query_class).to receive(:new)
|
12
|
-
.with(with) { ::Quo::EagerQuery.new(
|
12
|
+
.with(with) { ::Quo::EagerQuery.new(results) }
|
13
13
|
)
|
14
14
|
end
|
15
|
-
allow(query_class).to receive(:new) { ::Quo::EagerQuery.new(
|
15
|
+
allow(query_class).to receive(:new) { ::Quo::EagerQuery.new(results) }
|
16
16
|
end
|
17
17
|
end
|
18
18
|
end
|
@@ -4,14 +4,15 @@ module Quo
|
|
4
4
|
module Utilities
|
5
5
|
module Callstack
|
6
6
|
def debug_callstack
|
7
|
-
return unless
|
8
|
-
|
7
|
+
return unless Rails.env.development?
|
8
|
+
callstack_size = Quo.configuration.query_show_callstack_size
|
9
|
+
return unless callstack_size&.positive?
|
9
10
|
working_dir = Dir.pwd
|
10
11
|
exclude = %r{/(gems/|rubies/|query\.rb)}
|
11
12
|
stack = Kernel.caller.grep_v(exclude).map { |l| l.gsub(working_dir + "/", "") }
|
12
|
-
|
13
|
-
message = "\n[Query stack]: -> #{
|
14
|
-
message += " (truncated to #{
|
13
|
+
stack_to_display = stack[0..callstack_size]
|
14
|
+
message = "\n[Query stack]: -> #{stack_to_display&.join("\n &> ")}\n"
|
15
|
+
message += " (truncated to #{callstack_size} most recent)" if callstack_size && stack.size > callstack_size
|
15
16
|
Quo.configuration.logger&.info(message)
|
16
17
|
end
|
17
18
|
end
|
data/lib/quo/utilities/wrap.rb
CHANGED
@@ -13,9 +13,9 @@ module Quo
|
|
13
13
|
end
|
14
14
|
|
15
15
|
if query_rel_or_data.is_a? ActiveRecord::Relation
|
16
|
-
new(**options
|
16
|
+
Quo::WrappedQuery.new(query_rel_or_data, **options)
|
17
17
|
else
|
18
|
-
Quo::EagerQuery.new(**options
|
18
|
+
Quo::EagerQuery.new(query_rel_or_data, **options)
|
19
19
|
end
|
20
20
|
end
|
21
21
|
end
|
data/lib/quo/version.rb
CHANGED
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Quo
|
4
|
+
class WrappedQuery < Quo::Query
|
5
|
+
class << self
|
6
|
+
def call(**options)
|
7
|
+
build_from_options(**options).first
|
8
|
+
end
|
9
|
+
|
10
|
+
def call!(**options)
|
11
|
+
build_from_options(**options).first!
|
12
|
+
end
|
13
|
+
|
14
|
+
def build_from_options(**options)
|
15
|
+
query = options[:wrapped_query]
|
16
|
+
raise ArgumentError, "WrappedQuery needs a scope" unless query
|
17
|
+
new(query, **options)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(wrapped_query, **options)
|
22
|
+
@wrapped_query = wrapped_query
|
23
|
+
super(**options)
|
24
|
+
end
|
25
|
+
|
26
|
+
def query
|
27
|
+
@wrapped_query
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/quo.rb
CHANGED
@@ -5,8 +5,9 @@ require_relative "quo/railtie" if defined?(Rails)
|
|
5
5
|
require_relative "quo/query"
|
6
6
|
require_relative "quo/eager_query"
|
7
7
|
require_relative "quo/merged_query"
|
8
|
+
require_relative "quo/wrapped_query"
|
8
9
|
require_relative "quo/query_composer"
|
9
|
-
require_relative "quo/
|
10
|
+
require_relative "quo/results"
|
10
11
|
|
11
12
|
module Quo
|
12
13
|
class << self
|
@@ -21,12 +22,18 @@ module Quo
|
|
21
22
|
end
|
22
23
|
|
23
24
|
class Configuration
|
24
|
-
attr_accessor :formatted_query_log,
|
25
|
+
attr_accessor :formatted_query_log,
|
26
|
+
:query_show_callstack_size,
|
27
|
+
:logger,
|
28
|
+
:max_page_size,
|
29
|
+
:default_page_size
|
25
30
|
|
26
31
|
def initialize
|
27
32
|
@formatted_query_log = true
|
28
33
|
@query_show_callstack_size = 10
|
29
34
|
@logger = nil
|
35
|
+
@max_page_size = 200
|
36
|
+
@default_page_size = 20
|
30
37
|
end
|
31
38
|
end
|
32
39
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Quo
|
2
|
+
class EagerQuery < Quo::Query
|
3
|
+
def self.call: (**untyped) -> untyped
|
4
|
+
def self.call!: (**untyped) -> untyped
|
5
|
+
def self.build_from_options: (queryOptions) -> EagerQuery
|
6
|
+
|
7
|
+
def initialize: (enumerable, **untyped options) -> void
|
8
|
+
def query: () -> enumerable
|
9
|
+
def relation?: () -> false
|
10
|
+
def eager?: () -> true
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
attr_reader collection: enumerable
|
15
|
+
|
16
|
+
def preload_includes: (untyped records, ?untyped? preload) -> untyped
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Quo
|
2
|
+
class MergedQuery < Quo::Query
|
3
|
+
def self.build_from_options: (queryOptions) -> MergedQuery
|
4
|
+
|
5
|
+
def initialize: (relOrEnumerable merged, composable left, composable right, **untyped options) -> void
|
6
|
+
|
7
|
+
@merged_query: relOrEnumerable
|
8
|
+
|
9
|
+
def query: () -> relOrEnumerable
|
10
|
+
|
11
|
+
def to_s: () -> ::String
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
attr_reader left: composable
|
16
|
+
attr_reader right: composable
|
17
|
+
def operand_desc: (composable operand) -> String
|
18
|
+
end
|
19
|
+
end
|
data/sig/quo/query.rbs
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
module Quo
|
2
|
+
class Query
|
3
|
+
include Quo::Utilities::Callstack
|
4
|
+
extend Quo::Utilities::Compose
|
5
|
+
extend Quo::Utilities::Sanitize
|
6
|
+
extend Quo::Utilities::Wrap
|
7
|
+
|
8
|
+
@underlying_query: ActiveRecord::Relation
|
9
|
+
|
10
|
+
def self.call: (**untyped options) -> untyped
|
11
|
+
def self.call!: (**untyped options) -> untyped
|
12
|
+
|
13
|
+
@scope: ActiveRecord::Relation | nil
|
14
|
+
|
15
|
+
attr_reader current_page: Integer?
|
16
|
+
attr_reader page_size: Integer?
|
17
|
+
attr_reader options: Hash[untyped, untyped]
|
18
|
+
|
19
|
+
def initialize: (**untyped options) -> void
|
20
|
+
def query: () -> queryOrRel
|
21
|
+
def compose: (composable right, ?joins: untyped?) -> Quo::MergedQuery
|
22
|
+
alias + compose
|
23
|
+
|
24
|
+
def copy: (**untyped options) -> Quo::Query
|
25
|
+
|
26
|
+
def limit: (untyped limit) -> Quo::Query
|
27
|
+
def order: (untyped options) -> Quo::Query
|
28
|
+
def group: (*untyped options) -> Quo::Query
|
29
|
+
def includes: (*untyped options) -> Quo::Query
|
30
|
+
def preload: (*untyped options) -> Quo::Query
|
31
|
+
def select: (*untyped options) -> Quo::Query
|
32
|
+
|
33
|
+
def sum: (?untyped column_name) -> Numeric
|
34
|
+
def average: (untyped column_name) -> Numeric
|
35
|
+
def minimum: (untyped column_name) -> Numeric
|
36
|
+
def maximum: (untyped column_name) -> Numeric
|
37
|
+
def count: () -> Integer
|
38
|
+
|
39
|
+
alias total_count count
|
40
|
+
|
41
|
+
alias size count
|
42
|
+
def page_count: () -> Integer
|
43
|
+
def first: (?Integer? limit) -> untyped
|
44
|
+
def first!: (?Integer? limit) -> untyped
|
45
|
+
def last: (?Integer? limit) -> untyped
|
46
|
+
def to_a: () -> Array[untyped]
|
47
|
+
def to_eager: (?::Hash[untyped, untyped] more_opts) -> Quo::EagerQuery
|
48
|
+
alias load to_eager
|
49
|
+
def results: () -> Quo::Results
|
50
|
+
|
51
|
+
# Set a block used to transform data after query fetching
|
52
|
+
def transform: () ?{ () -> untyped } -> self
|
53
|
+
|
54
|
+
def exists?: () -> bool
|
55
|
+
def none?: () -> bool
|
56
|
+
alias empty? none?
|
57
|
+
def relation?: () -> bool
|
58
|
+
def eager?: () -> bool
|
59
|
+
def paged?: () -> bool
|
60
|
+
|
61
|
+
def model: () -> (untyped | nil)
|
62
|
+
def klass: () -> (untyped | nil)
|
63
|
+
|
64
|
+
def transform?: () -> bool
|
65
|
+
def to_sql: () -> (String | nil)
|
66
|
+
def unwrap: () -> ActiveRecord::Relation
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def formatted_queries?: () -> bool
|
71
|
+
def trim_query: (String sql) -> String
|
72
|
+
def format_query: (String sql_str) -> String
|
73
|
+
def transformer: () -> (nil | ^(untyped, ?Integer) -> untyped)
|
74
|
+
def offset: () -> Integer
|
75
|
+
def configured_query: () -> ActiveRecord::Relation
|
76
|
+
def sanitised_page_size: () -> Integer
|
77
|
+
def query_with_logging: () -> ActiveRecord::Relation
|
78
|
+
def underlying_query: () -> ActiveRecord::Relation
|
79
|
+
def unwrap_relation: (queryOrRel query) -> ActiveRecord::Relation
|
80
|
+
def test_eager: (composable rel) -> bool
|
81
|
+
def test_relation: (composable rel) -> bool
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Quo
|
2
|
+
class QueryComposer
|
3
|
+
@left_relation: bool
|
4
|
+
@right_relation: bool
|
5
|
+
|
6
|
+
def initialize: (composable left, composable right, ?untyped? joins) -> void
|
7
|
+
def compose: () -> Quo::MergedQuery
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
attr_reader left: composable
|
12
|
+
attr_reader right: composable
|
13
|
+
attr_reader joins: untyped
|
14
|
+
|
15
|
+
attr_reader unwrapped_left: relOrEnumerable
|
16
|
+
attr_reader unwrapped_right: relOrEnumerable
|
17
|
+
|
18
|
+
def left_relation?: -> bool
|
19
|
+
|
20
|
+
def merge_left_and_right: () -> relOrEnumerable
|
21
|
+
def merged_options: () -> ::Hash[untyped, untyped]
|
22
|
+
|
23
|
+
def right_relation?: -> bool
|
24
|
+
|
25
|
+
def unwrap_relation: (composable) -> relOrEnumerable
|
26
|
+
def relation_type?: (relOrEnumerable) -> bool
|
27
|
+
def apply_joins: (ActiveRecord::Relation left_rel, untyped joins) -> ActiveRecord::Relation
|
28
|
+
def both_relations?: () -> bool
|
29
|
+
def left_relation_right_enumerable?: () -> bool
|
30
|
+
def left_enumerable_right_relation?: () -> bool
|
31
|
+
end
|
32
|
+
end
|
data/sig/quo/results.rbs
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
module Quo
|
2
|
+
class Results
|
3
|
+
extend Forwardable
|
4
|
+
|
5
|
+
include Quo::Utilities::Callstack
|
6
|
+
|
7
|
+
def initialize: (Quo::Query query, ?transformer: (^(untyped, ?Integer) -> untyped)?) -> void
|
8
|
+
|
9
|
+
@query: Quo::Query
|
10
|
+
|
11
|
+
def group_by: () { (untyped, *untyped) -> untyped } -> Hash[untyped, Array[untyped]]
|
12
|
+
|
13
|
+
def respond_to_missing?: (Symbol name, ?bool include_private) -> bool
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
attr_reader transformer: (^(untyped, ?Integer) -> untyped)?
|
18
|
+
attr_reader unwrapped: relOrEnumerable
|
19
|
+
|
20
|
+
def transform_results: (untyped) -> untyped
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Quo
|
2
|
+
class WrappedQuery < Quo::Query
|
3
|
+
def self.build_from_options: (**untyped options) -> WrappedQuery
|
4
|
+
|
5
|
+
@wrapped_query: ActiveRecord::Relation
|
6
|
+
|
7
|
+
def initialize: (ActiveRecord::Relation query, **untyped options) -> void
|
8
|
+
|
9
|
+
def query: () -> ActiveRecord::Relation
|
10
|
+
end
|
11
|
+
end
|
data/sig/quo.rbs
CHANGED
@@ -1,205 +1,40 @@
|
|
1
|
-
module
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
def each: { (untyped) -> void } -> untyped
|
6
|
-
end
|
7
|
-
|
8
|
-
interface _Collection
|
9
|
-
include _Enumerable
|
10
|
-
def is_a?: (Class) -> bool
|
11
|
-
def class: -> Class
|
12
|
-
def +: (untyped) -> _Collection
|
13
|
-
end
|
14
|
-
|
15
|
-
type query = Quo::MergedQuery | Quo::EagerQuery | Quo::Query
|
16
|
-
type merge_composable = query | ActiveRecord::Relation
|
17
|
-
type enumerable = _Collection
|
18
|
-
type query_like = ActiveRecord::Relation | enumerable
|
19
|
-
type composable = query | query_like
|
20
|
-
|
21
|
-
module Utilities
|
22
|
-
module Callstack
|
23
|
-
def debug_callstack: () -> void
|
24
|
-
end
|
25
|
-
|
26
|
-
module Compose
|
27
|
-
# Combine two query-like or composeable entities:
|
28
|
-
# These can be Quo::Query, Quo::MergedQuery, Quo::EagerQuery and ActiveRecord::Relations.
|
29
|
-
# See the `README.md` docs for more details.
|
30
|
-
def compose: (composable query1, composable query2, ?joins: untyped?) -> Quo::MergedQuery
|
31
|
-
|
32
|
-
# Determines if the object `query` is something which can be composed with query objects
|
33
|
-
def composable_with?: (merge_composable query) -> bool
|
34
|
-
end
|
35
|
-
|
36
|
-
module Sanitize
|
37
|
-
def sanitize_sql_for_conditions: (untyped conditions) -> (untyped | nil)
|
38
|
-
|
39
|
-
def sanitize_sql_string: (untyped string) -> (untyped | nil)
|
40
|
-
|
41
|
-
def sanitize_sql_parameter: (untyped value) -> (untyped | nil)
|
1
|
+
module ActiveRecord
|
2
|
+
module Associations
|
3
|
+
class Preloader
|
4
|
+
def initialize: (records: untyped, associations: untyped, ?scope: untyped, ?available_records: Array[untyped], ?associate_by_default: bool) -> void
|
42
5
|
end
|
43
|
-
|
44
|
-
interface _Wrapable
|
45
|
-
def new: (**untyped options) -> query
|
46
|
-
end
|
47
|
-
|
48
|
-
module Wrap : _Wrapable
|
49
|
-
def wrap: (composable query_rel_or_data, **untyped options) -> query
|
50
|
-
end
|
51
|
-
|
52
|
-
end
|
53
|
-
|
54
|
-
class Enumerator
|
55
|
-
extend Forwardable
|
56
|
-
|
57
|
-
include Quo::Utilities::Callstack
|
58
|
-
|
59
|
-
def initialize: (Quo::Query query, ?transformer: ^(untyped, ?Integer) -> untyped) -> void
|
60
|
-
|
61
|
-
@query: Quo::Query
|
62
|
-
|
63
|
-
def respond_to_missing?: (Symbol name, ?bool include_private) -> bool
|
64
|
-
|
65
|
-
private
|
66
|
-
|
67
|
-
attr_reader transformer: ^(untyped, ?Integer) -> untyped
|
68
|
-
attr_reader unwrapped: ActiveRecord::Relation
|
69
|
-
|
70
|
-
def transform_results: (untyped) -> untyped
|
71
6
|
end
|
7
|
+
end
|
72
8
|
|
9
|
+
module Quo
|
10
|
+
VERSION: String
|
73
11
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
def self.call: (**untyped options) -> untyped
|
81
|
-
def self.call!: (**untyped options) -> untyped
|
82
|
-
def self.description: (?String) -> (String | nil)
|
83
|
-
self.@description: String | nil
|
84
|
-
|
85
|
-
@scope: ActiveRecord::Relation | nil
|
86
|
-
|
87
|
-
attr_reader current_page: (Integer | nil)
|
88
|
-
attr_reader page_size: (Integer | nil)
|
89
|
-
attr_reader options: Hash[untyped, untyped]
|
90
|
-
|
91
|
-
def initialize: (**untyped options) -> void
|
92
|
-
def query: () -> composable
|
93
|
-
def compose: (composable right, ?joins: untyped?) -> Quo::MergedQuery
|
94
|
-
alias + compose
|
95
|
-
|
96
|
-
def copy: (**untyped options) -> query
|
97
|
-
|
98
|
-
def limit: (untyped limit) -> query
|
99
|
-
def order: (untyped options) -> query
|
100
|
-
def group: (*untyped options) -> query
|
101
|
-
def includes: (*untyped options) -> query
|
102
|
-
def preload: (*untyped options) -> query
|
103
|
-
def select: (*untyped options) -> query
|
104
|
-
|
105
|
-
def sum: (?untyped column_name) -> Numeric
|
106
|
-
def average: (untyped column_name) -> Numeric
|
107
|
-
def minimum: (untyped column_name) -> Numeric
|
108
|
-
def maximum: (untyped column_name) -> Numeric
|
109
|
-
def count: () -> Integer
|
110
|
-
|
111
|
-
alias total_count count
|
112
|
-
|
113
|
-
alias size count
|
114
|
-
def page_count: () -> Integer
|
115
|
-
def first: (*untyped args) -> untyped
|
116
|
-
def first!: (*untyped args) -> untyped
|
117
|
-
def last: (*untyped args) -> untyped
|
118
|
-
def to_a: () -> Array[untyped]
|
119
|
-
def to_eager: (?::Hash[untyped, untyped] more_opts) -> Quo::EagerQuery
|
120
|
-
alias load to_eager
|
121
|
-
def enumerator: () -> Quo::Enumerator
|
122
|
-
|
123
|
-
# Set a block used to transform data after query fetching
|
124
|
-
def transform: () ?{ () -> untyped } -> self
|
125
|
-
|
126
|
-
def exists?: () -> bool
|
127
|
-
def none?: () -> bool
|
128
|
-
alias empty? none?
|
129
|
-
def relation?: () -> bool
|
130
|
-
def eager?: () -> bool
|
131
|
-
def paged?: () -> bool
|
132
|
-
|
133
|
-
def model: () -> (untyped | nil)
|
134
|
-
def klass: () -> (untyped | nil)
|
135
|
-
|
136
|
-
def transform?: () -> bool
|
137
|
-
def to_sql: () -> (String | nil)
|
138
|
-
def unwrap: () -> query_like
|
139
|
-
|
140
|
-
private
|
141
|
-
|
142
|
-
def formatted_queries?: () -> bool
|
143
|
-
def trim_query: (String sql) -> String
|
144
|
-
def format_query: (String sql_str) -> String
|
145
|
-
def transformer: () -> (nil | ^(untyped) -> untyped)
|
146
|
-
def offset: () -> Integer
|
147
|
-
def configured_query: () -> ActiveRecord::Relation
|
148
|
-
def sanitised_page_size: () -> Integer
|
149
|
-
def query_with_logging: () -> ActiveRecord::Relation
|
150
|
-
def underlying_query: () -> ActiveRecord::Relation
|
151
|
-
def unwrap_relation: (composable query) -> ActiveRecord::Relation
|
152
|
-
def test_eager: (composable rel) -> bool
|
153
|
-
def test_relation: (composable rel) -> bool
|
154
|
-
end
|
155
|
-
|
156
|
-
class MergedQuery < Quo::Query
|
157
|
-
def initialize: (untyped options, ?untyped source_queries) -> void
|
158
|
-
|
159
|
-
def query: () -> composable
|
160
|
-
|
161
|
-
def to_s: () -> ::String
|
12
|
+
type query = Quo::Query
|
13
|
+
type queryOrRel = query | ActiveRecord::Relation
|
14
|
+
type enumerable = Object & Enumerable[untyped]
|
15
|
+
type relOrEnumerable = ActiveRecord::Relation | enumerable
|
16
|
+
type composable = query | relOrEnumerable
|
162
17
|
|
163
|
-
|
18
|
+
# TODO: how can we do the known options, eg `page` and then allow anything else?
|
19
|
+
# Maybe we should separate out the known options from the unknown options
|
20
|
+
type queryOptions = Hash[Symbol, untyped]
|
164
21
|
|
165
|
-
|
166
|
-
def
|
167
|
-
|
168
|
-
def
|
22
|
+
interface _Logger
|
23
|
+
def info: (String) -> void
|
24
|
+
def error: (String) -> void
|
25
|
+
def debug: (String) -> void
|
169
26
|
end
|
170
27
|
|
171
|
-
class
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
private
|
28
|
+
class Configuration
|
29
|
+
attr_accessor formatted_query_log: bool?
|
30
|
+
attr_accessor query_show_callstack_size: Integer?
|
31
|
+
attr_accessor logger: _Logger?
|
32
|
+
attr_accessor max_page_size: Integer?
|
33
|
+
attr_accessor default_page_size: Integer?
|
179
34
|
|
180
|
-
def
|
35
|
+
def initialize: () -> void
|
181
36
|
end
|
37
|
+
attr_reader self.configuration: Configuration
|
182
38
|
|
183
|
-
|
184
|
-
def initialize: (composable left, composable right, ?untyped? joins) -> void
|
185
|
-
def compose: () -> Quo::MergedQuery
|
186
|
-
|
187
|
-
private
|
188
|
-
|
189
|
-
attr_reader left: composable
|
190
|
-
attr_reader right: composable
|
191
|
-
attr_reader joins: untyped
|
192
|
-
|
193
|
-
def merge: () -> (ActiveRecord::Relation | Array[untyped] | void)
|
194
|
-
def merged_options: () -> ::Hash[untyped, untyped]
|
195
|
-
def unwrap_relation: (composable) -> query_like
|
196
|
-
def relation_type?: (composable) -> bool
|
197
|
-
def apply_joins: (ActiveRecord::Relation left_rel, untyped joins) -> ActiveRecord::Relation
|
198
|
-
def both_relations?: (bool left_rel_type, bool right_rel_type) -> bool
|
199
|
-
def left_relation_right_eager?: (bool left_rel_type, bool right_rel_type) -> bool
|
200
|
-
def left_eager_right_relation?: (bool left_rel_type, bool right_rel_type) -> bool
|
201
|
-
def both_eager_loaded?: (bool left_rel_type, bool right_rel_type) -> bool
|
202
|
-
|
203
|
-
def raise_error: () -> void
|
204
|
-
end
|
39
|
+
def self.configure: () { (Configuration config) -> void } -> void
|
205
40
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: quo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stephen Ierodiaconou
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-12-
|
11
|
+
date: 2022-12-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -66,20 +66,31 @@ files:
|
|
66
66
|
- Steepfile
|
67
67
|
- lib/quo.rb
|
68
68
|
- lib/quo/eager_query.rb
|
69
|
-
- lib/quo/enumerator.rb
|
70
69
|
- lib/quo/merged_query.rb
|
71
70
|
- lib/quo/query.rb
|
72
71
|
- lib/quo/query_composer.rb
|
73
72
|
- lib/quo/railtie.rb
|
73
|
+
- lib/quo/results.rb
|
74
74
|
- lib/quo/rspec/helpers.rb
|
75
75
|
- lib/quo/utilities/callstack.rb
|
76
76
|
- lib/quo/utilities/compose.rb
|
77
77
|
- lib/quo/utilities/sanitize.rb
|
78
78
|
- lib/quo/utilities/wrap.rb
|
79
79
|
- lib/quo/version.rb
|
80
|
+
- lib/quo/wrapped_query.rb
|
80
81
|
- lib/tasks/quo.rake
|
81
82
|
- rbs_collection.yaml
|
82
83
|
- sig/quo.rbs
|
84
|
+
- sig/quo/eager_query.rbs
|
85
|
+
- sig/quo/merged_query.rbs
|
86
|
+
- sig/quo/query.rbs
|
87
|
+
- sig/quo/query_composer.rbs
|
88
|
+
- sig/quo/results.rbs
|
89
|
+
- sig/quo/utilities/callstack.rbs
|
90
|
+
- sig/quo/utilities/compose.rbs
|
91
|
+
- sig/quo/utilities/sanitize.rbs
|
92
|
+
- sig/quo/utilities/wrap.rbs
|
93
|
+
- sig/quo/wrapped_query.rbs
|
83
94
|
homepage: https://github.com/stevegeek/quo
|
84
95
|
licenses:
|
85
96
|
- MIT
|