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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3278d72f55569eb93fb569185467cd4b327ffd157e249c8538d4a643c652443c
4
- data.tar.gz: e936c5fd0de429b941e110c273cb12ff302079095ab0f3d7d38023718b80b5e3
3
+ metadata.gz: 61bf7495c0092bf641ff6e85bed8dc0073ea61e15a5292f0825f50eca4348a92
4
+ data.tar.gz: c10cc10dfe8ce323e4395640604a1c6358333bb7ea26b1c109d43eee67ed2d73
5
5
  SHA512:
6
- metadata.gz: 4adcb23af540cf2db462bd21640aef4497c1f2509a1ac6968a5a15b1cd2004d7db9935638ba5496eb57ffc655ad515688fc9468a191d17b32735da1a7c652705
7
- data.tar.gz: f99119cdf52a97e956317a530e9074e12b07816405f6dd5fd80f74665dd34c0294368ed6d6a7d88f8147484486f52606462864663357e430ca2eebc36b7372d7
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(collection: [1, 2, 3])
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(collection: my_data, total_count: 100, page: current_page)
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(collection: [1, 2])).last
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
@@ -27,8 +27,11 @@
27
27
  # end
28
28
 
29
29
  target :lib do
30
- check "lib/quo"
30
+ check "lib"
31
31
  signature "sig"
32
+ ignore "lib/quo/rspec/*.rb"
33
+ ignore "lib/tasks/*"
34
+ ignore "lib/quo/railtie.rb"
32
35
 
33
36
  library "forwardable"
34
37
  end
@@ -2,9 +2,29 @@
2
2
 
3
3
  module Quo
4
4
  class EagerQuery < Quo::Query
5
- def initialize(**options)
6
- @collection = Array.wrap(options[:collection])
7
- super(**options.except(:collection))
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.
@@ -2,39 +2,52 @@
2
2
 
3
3
  module Quo
4
4
  class MergedQuery < Quo::Query
5
- def initialize(options, source_queries = [])
6
- @source_queries = source_queries
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
- @scope
31
+ @merged_query
12
32
  end
13
33
 
14
- def to_s
15
- left = operand_desc(source_queries_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
- private
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
- def source_queries_right
27
- source_queries&.last
28
- end
42
+ private
29
43
 
30
- attr_reader :source_queries
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(*args)
96
+ def first(limit = nil)
100
97
  if transform?
101
- res = query_with_logging.first(*args)
98
+ res = query_with_logging.first(limit)
102
99
  if res.is_a? Array
103
- res.map.with_index { |r, i| transformer.call(r, i) }
100
+ res.map.with_index { |r, i| transformer&.call(r, i) }
104
101
  elsif !res.nil?
105
- transformer.call(query_with_logging.first(*args))
102
+ transformer&.call(query_with_logging.first(limit))
106
103
  end
107
104
  else
108
- query_with_logging.first(*args)
105
+ query_with_logging.first(limit)
109
106
  end
110
107
  end
111
108
 
112
- def first!(*args)
113
- item = first(*args)
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(*args)
116
+ def last(limit = nil)
120
117
  if transform?
121
- res = query_with_logging.last(*args)
118
+ res = query_with_logging.last(limit)
122
119
  if res.is_a? Array
123
- res.map.with_index { |r, i| transformer.call(r, i) }
120
+ res.map.with_index { |r, i| transformer&.call(r, i) }
124
121
  elsif !res.nil?
125
- transformer.call(query_with_logging.last(*args))
122
+ transformer&.call(res)
126
123
  end
127
124
  else
128
- query_with_logging.last(*args)
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.call(r, i) } : arr
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(collection: to_a, **options.merge(more_opts))
137
+ Quo::EagerQuery.new(to_a, **options.merge(more_opts))
141
138
  end
142
139
  alias_method :load, :to_eager
143
140
 
144
- # Return an enumerable
145
- def enumerator
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 iterating over the results
150
- delegate :each, :map, :flat_map, :reduce, :reject, :filter, to: :enumerator
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&.formatted_query_log
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.positive? ? current_page : 1
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
- (page_size.present? && page_size.positive?) ? [page_size.to_i, 200].min : 20
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
@@ -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
- merged_options.merge({scope: combined, source_queries: [left, right]})
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 merge
23
- left_rel = unwrap_relation(left)
24
- right_rel = unwrap_relation(right)
25
- left_type = relation_type?(left)
26
- right_type = relation_type?(right)
28
+ def left_relation?
29
+ @left_relation
30
+ end
31
+
32
+ def right_relation?
33
+ @right_relation
34
+ end
27
35
 
28
- if both_relations?(left_type, right_type)
29
- apply_joins(left_rel, joins).merge(right_rel)
30
- elsif left_relation_right_eager?(left_type, right_type)
31
- left_rel.to_a + right_rel
32
- elsif left_eager_right_relation?(left_type, right_type) && left_rel.respond_to?(:+)
33
- left_rel + right_rel.to_a
34
- elsif both_eager_loaded?(left_type, right_type) && left_rel.respond_to?(:+)
35
- left_rel + right_rel
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
- raise_error
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?(left_rel_type, right_rel_type)
65
- left_rel_type && right_rel_type
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 both_eager_loaded?(left_rel_type, right_rel_type)
77
- !left_rel_type && !right_rel_type
68
+ def left_relation_right_enumerable?
69
+ left_relation? && !right_relation?
78
70
  end
79
71
 
80
- def raise_error
81
- raise ArgumentError, "Unable to composite queries #{left.class.name} and " \
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 Enumerator
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
- :tally,
25
- :count,
26
- :group_by,
27
- :partition,
28
- :slice_before,
29
- :slice_after,
30
- :slice_when,
31
- :chunk,
32
- :chunk_while,
33
- :sum,
34
- :zip
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.present? ? transformer.call(x) : x
44
- block.call(transformed, *block_args[1..])
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
- return raw if raw.is_a?(Quo::Enumerator) || raw.is_a?(::Enumerator)
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.present?
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) }
@@ -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(collection: results) }
12
+ .with(with) { ::Quo::EagerQuery.new(results) }
13
13
  )
14
14
  end
15
- allow(query_class).to receive(:new) { ::Quo::EagerQuery.new(collection: results) }
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 Quo.configuration&.query_show_callstack_size&.positive? && Rails.env.development?
8
- max_stack = Quo.configuration.query_show_callstack_size
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
- trace_message = stack[0..max_stack].join("\n &> ")
13
- message = "\n[Query stack]: -> #{trace_message}\n"
14
- message += " (truncated to #{max_stack} most recent)" if stack.size > max_stack
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
@@ -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.merge(scope: query_rel_or_data))
16
+ Quo::WrappedQuery.new(query_rel_or_data, **options)
17
17
  else
18
- Quo::EagerQuery.new(**options.merge(collection: query_rel_or_data))
18
+ Quo::EagerQuery.new(query_rel_or_data, **options)
19
19
  end
20
20
  end
21
21
  end
data/lib/quo/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Quo
4
- VERSION = "0.3.0"
4
+ VERSION = "0.4.0"
5
5
  end
@@ -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/enumerator"
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, :query_show_callstack_size, :logger
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
@@ -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,7 @@
1
+ module Quo
2
+ module Utilities
3
+ module Callstack
4
+ def debug_callstack: () -> void
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,8 @@
1
+ module Quo
2
+ module Utilities
3
+ module Compose
4
+ def compose: (composable query1, composable query2, ?joins: untyped?) -> Quo::MergedQuery
5
+ def composable_with?: (queryOrRel query) -> bool
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,9 @@
1
+ module Quo
2
+ module Utilities
3
+ module Sanitize
4
+ def sanitize_sql_for_conditions: (untyped conditions) -> untyped?
5
+ def sanitize_sql_string: (untyped string) -> untyped?
6
+ def sanitize_sql_parameter: (untyped value) -> untyped?
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ module Quo
2
+ module Utilities
3
+ interface _Wrapable
4
+ def new: (**untyped options) -> query
5
+ end
6
+
7
+ module Wrap : _Wrapable
8
+ def wrap: (composable query_rel_or_data, **untyped options) -> query
9
+ end
10
+ end
11
+ 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 Quo
2
- VERSION: String
3
-
4
- interface _Enumerable
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
- class Query
75
- include Quo::Utilities::Callstack
76
- extend Quo::Utilities::Compose
77
- extend Quo::Utilities::Sanitize
78
- extend Quo::Utilities::Wrap
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
- private
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
- def source_queries_left: () -> composable
166
- def source_queries_right: () -> composable
167
- attr_reader source_queries: Array[composable]
168
- def operand_desc: (untyped operand) -> (nil | String)
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 EagerQuery < Quo::Query
172
- def initialize: (**untyped options) -> void
173
- def query: () -> enumerable
174
- def relation?: () -> false
175
- def eager?: () -> true
176
- attr_reader collection: enumerable
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 preload_includes: (untyped records, ?untyped? preload) -> untyped
35
+ def initialize: () -> void
181
36
  end
37
+ attr_reader self.configuration: Configuration
182
38
 
183
- class QueryComposer
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.3.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-20 00:00:00.000000000 Z
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