quo 0.5.3 → 1.0.0.alpha1
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/.standard.yml +4 -1
- data/Appraisals +11 -0
- data/CHANGELOG.md +78 -0
- data/Gemfile +6 -4
- data/LICENSE.txt +1 -1
- data/README.md +37 -69
- data/Steepfile +0 -2
- data/gemfiles/rails_7.0.gemfile +15 -0
- data/gemfiles/rails_7.1.gemfile +15 -0
- data/gemfiles/rails_7.2.gemfile +15 -0
- data/lib/quo/collection_backed_query.rb +87 -0
- data/lib/quo/collection_results.rb +44 -0
- data/lib/quo/composed_query.rb +168 -0
- data/lib/quo/engine.rb +11 -0
- data/lib/quo/minitest/helpers.rb +41 -0
- data/lib/quo/preloadable.rb +46 -0
- data/lib/quo/query.rb +101 -213
- data/lib/quo/relation_backed_query.rb +177 -0
- data/lib/quo/relation_results.rb +58 -0
- data/lib/quo/results.rb +48 -44
- data/lib/quo/rspec/helpers.rb +31 -9
- data/lib/quo/testing/collection_backed_fake.rb +29 -0
- data/lib/quo/testing/relation_backed_fake.rb +52 -0
- data/lib/quo/version.rb +3 -1
- data/lib/quo.rb +22 -30
- data/rbs_collection.yaml +0 -2
- data/sig/generated/quo/collection_backed_query.rbs +39 -0
- data/sig/generated/quo/collection_results.rbs +30 -0
- data/sig/generated/quo/composed_query.rbs +83 -0
- data/sig/generated/quo/engine.rbs +6 -0
- data/sig/generated/quo/preloadable.rbs +29 -0
- data/sig/generated/quo/query.rbs +98 -0
- data/sig/generated/quo/relation_backed_query.rbs +90 -0
- data/sig/generated/quo/relation_results.rbs +38 -0
- data/sig/generated/quo/results.rbs +39 -0
- data/sig/generated/quo/version.rbs +5 -0
- data/sig/generated/quo.rbs +9 -0
- metadata +67 -30
- data/lib/quo/eager_query.rb +0 -51
- data/lib/quo/loaded_query.rb +0 -18
- data/lib/quo/merged_query.rb +0 -36
- data/lib/quo/query_composer.rb +0 -78
- data/lib/quo/railtie.rb +0 -7
- data/lib/quo/utilities/callstack.rb +0 -20
- data/lib/quo/utilities/compose.rb +0 -18
- data/lib/quo/utilities/sanitize.rb +0 -19
- data/lib/quo/utilities/wrap.rb +0 -23
- data/lib/quo/wrapped_query.rb +0 -18
- data/sig/quo/eager_query.rbs +0 -15
- data/sig/quo/loaded_query.rbs +0 -7
- data/sig/quo/merged_query.rbs +0 -19
- data/sig/quo/query.rbs +0 -83
- data/sig/quo/query_composer.rbs +0 -32
- data/sig/quo/results.rbs +0 -22
- data/sig/quo/utilities/callstack.rbs +0 -7
- data/sig/quo/utilities/compose.rbs +0 -8
- data/sig/quo/utilities/sanitize.rbs +0 -9
- data/sig/quo/utilities/wrap.rbs +0 -11
- data/sig/quo/wrapped_query.rbs +0 -11
- data/sig/quo.rbs +0 -41
data/lib/quo/query_composer.rb
DELETED
@@ -1,78 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Quo
|
4
|
-
class QueryComposer
|
5
|
-
def initialize(left, right, joins = nil)
|
6
|
-
@left = left
|
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)
|
12
|
-
@joins = joins
|
13
|
-
end
|
14
|
-
|
15
|
-
def compose
|
16
|
-
Quo::MergedQuery.new(
|
17
|
-
merge_left_and_right,
|
18
|
-
left,
|
19
|
-
right,
|
20
|
-
**merged_options
|
21
|
-
)
|
22
|
-
end
|
23
|
-
|
24
|
-
private
|
25
|
-
|
26
|
-
attr_reader :left, :right, :joins, :unwrapped_left, :unwrapped_right
|
27
|
-
|
28
|
-
def left_relation?
|
29
|
-
@left_relation
|
30
|
-
end
|
31
|
-
|
32
|
-
def right_relation?
|
33
|
-
@right_relation
|
34
|
-
end
|
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? && unwrapped_left.respond_to?(:+)
|
43
|
-
unwrapped_left + unwrapped_right.to_a
|
44
|
-
elsif unwrapped_left.respond_to?(:+)
|
45
|
-
unwrapped_left + unwrapped_right
|
46
|
-
else
|
47
|
-
raise ArgumentError, "Cannot merge #{left.class} with #{right.class}"
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
def merged_options
|
52
|
-
return left.options.merge(right.options) if left.is_a?(Quo::Query) && right.is_a?(Quo::Query)
|
53
|
-
return left.options if left.is_a?(Quo::Query)
|
54
|
-
return right.options if right.is_a?(Quo::Query)
|
55
|
-
{}
|
56
|
-
end
|
57
|
-
|
58
|
-
def unwrap_relation(query)
|
59
|
-
query.is_a?(Quo::Query) ? query.unwrap : query
|
60
|
-
end
|
61
|
-
|
62
|
-
def apply_joins(left_rel, joins)
|
63
|
-
joins.present? ? left_rel.joins(joins) : left_rel
|
64
|
-
end
|
65
|
-
|
66
|
-
def both_relations?
|
67
|
-
left_relation? && right_relation?
|
68
|
-
end
|
69
|
-
|
70
|
-
def left_relation_right_enumerable?
|
71
|
-
left_relation? && !right_relation?
|
72
|
-
end
|
73
|
-
|
74
|
-
def left_enumerable_right_relation?
|
75
|
-
!left_relation? && right_relation?
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
data/lib/quo/railtie.rb
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Quo
|
4
|
-
module Utilities
|
5
|
-
module Callstack
|
6
|
-
def debug_callstack
|
7
|
-
return unless Rails.env.development?
|
8
|
-
callstack_size = Quo.configuration.query_show_callstack_size
|
9
|
-
return unless callstack_size&.positive?
|
10
|
-
working_dir = Dir.pwd
|
11
|
-
exclude = %r{/(gems/|rubies/|query\.rb)}
|
12
|
-
stack = Kernel.caller.grep_v(exclude).map { |l| l.gsub(working_dir + "/", "") }
|
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
|
16
|
-
Quo.configuration.logger&.info(message)
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
@@ -1,18 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Quo
|
4
|
-
module Utilities
|
5
|
-
# Combine two query-like or composeable entities:
|
6
|
-
# These can be Quo::Query, Quo::MergedQuery, Quo::EagerQuery and ActiveRecord::Relations.
|
7
|
-
# See the `README.md` docs for more details.
|
8
|
-
module Compose
|
9
|
-
def compose(query1, query2, joins: nil)
|
10
|
-
Quo::QueryComposer.new(query1, query2, joins).compose
|
11
|
-
end
|
12
|
-
|
13
|
-
def composable_with?(query)
|
14
|
-
query.is_a?(Quo::Query) || query.is_a?(ActiveRecord::Relation)
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
@@ -1,19 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Quo
|
4
|
-
module Utilities
|
5
|
-
module Sanitize
|
6
|
-
def sanitize_sql_for_conditions(conditions)
|
7
|
-
ActiveRecord::Base.sanitize_sql_for_conditions(conditions)
|
8
|
-
end
|
9
|
-
|
10
|
-
def sanitize_sql_string(string)
|
11
|
-
sanitize_sql_for_conditions(["'%s'", string])
|
12
|
-
end
|
13
|
-
|
14
|
-
def sanitize_sql_parameter(value)
|
15
|
-
sanitize_sql_for_conditions(["?", value])
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
data/lib/quo/utilities/wrap.rb
DELETED
@@ -1,23 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Quo
|
4
|
-
module Utilities
|
5
|
-
# Wrap a ActiveRecord::Relation or data collection in a Query.
|
6
|
-
#
|
7
|
-
# If the passed in object is already a Query object then just return it or copy it if new options are passed in.
|
8
|
-
# Otherwise if a Relation wrap it in a new Query object or else in an EagerQuery .
|
9
|
-
module Wrap
|
10
|
-
def wrap(query_rel_or_data, **options)
|
11
|
-
if query_rel_or_data.is_a? Quo::Query
|
12
|
-
return options.present? ? query_rel_or_data.copy(**options) : query_rel_or_data
|
13
|
-
end
|
14
|
-
|
15
|
-
if query_rel_or_data.is_a? ActiveRecord::Relation
|
16
|
-
Quo::WrappedQuery.new(query_rel_or_data, **options)
|
17
|
-
else
|
18
|
-
Quo::LoadedQuery.new(query_rel_or_data, **options)
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
data/lib/quo/wrapped_query.rb
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Quo
|
4
|
-
class WrappedQuery < Quo::Query
|
5
|
-
def initialize(wrapped_query, **options)
|
6
|
-
@wrapped_query = wrapped_query
|
7
|
-
super(**options)
|
8
|
-
end
|
9
|
-
|
10
|
-
def copy(**options)
|
11
|
-
self.class.new(query, **@options.merge(options))
|
12
|
-
end
|
13
|
-
|
14
|
-
def query
|
15
|
-
@wrapped_query
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
data/sig/quo/eager_query.rbs
DELETED
@@ -1,15 +0,0 @@
|
|
1
|
-
module Quo
|
2
|
-
class EagerQuery < Quo::Query
|
3
|
-
def collection: () -> loadedQueryOrEnumerable
|
4
|
-
def query: () -> loadedQueryOrEnumerable
|
5
|
-
|
6
|
-
def relation?: () -> false
|
7
|
-
def eager?: () -> true
|
8
|
-
|
9
|
-
private
|
10
|
-
|
11
|
-
def preload_includes: (untyped records, ?untyped? preload) -> untyped
|
12
|
-
def underlying_query: () -> enumerable
|
13
|
-
def unwrap_relation: (loadedQueryOrEnumerable collection) -> enumerable
|
14
|
-
end
|
15
|
-
end
|
data/sig/quo/loaded_query.rbs
DELETED
data/sig/quo/merged_query.rbs
DELETED
@@ -1,19 +0,0 @@
|
|
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 inspect: () -> ::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
DELETED
@@ -1,83 +0,0 @@
|
|
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
|
data/sig/quo/query_composer.rbs
DELETED
@@ -1,32 +0,0 @@
|
|
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
DELETED
@@ -1,22 +0,0 @@
|
|
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
|
data/sig/quo/utilities/wrap.rbs
DELETED
data/sig/quo/wrapped_query.rbs
DELETED
@@ -1,11 +0,0 @@
|
|
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
DELETED
@@ -1,41 +0,0 @@
|
|
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
|
5
|
-
end
|
6
|
-
end
|
7
|
-
end
|
8
|
-
|
9
|
-
module Quo
|
10
|
-
VERSION: String
|
11
|
-
|
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 loadedQueryOrEnumerable = LoadedQuery | EagerQuery | enumerable
|
17
|
-
type composable = query | relOrEnumerable
|
18
|
-
|
19
|
-
# TODO: how can we do the known options, eg `page` and then allow anything else?
|
20
|
-
# Maybe we should separate out the known options from the unknown options
|
21
|
-
type queryOptions = Hash[Symbol, untyped]
|
22
|
-
|
23
|
-
interface _Logger
|
24
|
-
def info: (String) -> void
|
25
|
-
def error: (String) -> void
|
26
|
-
def debug: (String) -> void
|
27
|
-
end
|
28
|
-
|
29
|
-
class Configuration
|
30
|
-
attr_accessor formatted_query_log: bool?
|
31
|
-
attr_accessor query_show_callstack_size: Integer?
|
32
|
-
attr_accessor logger: _Logger?
|
33
|
-
attr_accessor max_page_size: Integer?
|
34
|
-
attr_accessor default_page_size: Integer?
|
35
|
-
|
36
|
-
def initialize: () -> void
|
37
|
-
end
|
38
|
-
attr_reader self.configuration: Configuration
|
39
|
-
|
40
|
-
def self.configure: () { (Configuration config) -> void } -> void
|
41
|
-
end
|