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 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