quo 0.3.0 → 0.4.0
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/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
|