active_record_extended 1.3.1 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +11 -10
- data/lib/active_record_extended/active_record/relation_patch.rb +16 -1
- data/lib/active_record_extended/active_record.rb +2 -11
- data/lib/active_record_extended/arel/nodes.rb +24 -21
- data/lib/active_record_extended/arel/predications.rb +3 -2
- data/lib/active_record_extended/arel/sql_literal.rb +16 -0
- data/lib/active_record_extended/arel/visitors/postgresql_decorator.rb +1 -1
- data/lib/active_record_extended/arel.rb +1 -0
- data/lib/active_record_extended/query_methods/any_of.rb +5 -4
- data/lib/active_record_extended/query_methods/either.rb +2 -1
- data/lib/active_record_extended/query_methods/inet.rb +6 -2
- data/lib/active_record_extended/query_methods/json.rb +14 -17
- data/lib/active_record_extended/query_methods/select.rb +13 -12
- data/lib/active_record_extended/query_methods/unionize.rb +13 -7
- data/lib/active_record_extended/query_methods/where_chain.rb +19 -8
- data/lib/active_record_extended/query_methods/window.rb +4 -3
- data/lib/active_record_extended/query_methods/with_cte.rb +104 -37
- data/lib/active_record_extended/utilities/order_by.rb +11 -30
- data/lib/active_record_extended/utilities/support.rb +9 -16
- data/lib/active_record_extended/version.rb +1 -1
- data/spec/query_methods/any_of_spec.rb +2 -2
- data/spec/query_methods/either_spec.rb +11 -0
- data/spec/query_methods/json_spec.rb +5 -5
- data/spec/query_methods/select_spec.rb +13 -13
- data/spec/query_methods/unionize_spec.rb +6 -6
- data/spec/query_methods/with_cte_spec.rb +14 -4
- data/spec/spec_helper.rb +1 -1
- data/spec/sql_inspections/any_of_sql_spec.rb +2 -2
- data/spec/sql_inspections/contains_sql_queries_spec.rb +8 -8
- data/spec/sql_inspections/either_sql_spec.rb +19 -3
- data/spec/sql_inspections/json_sql_spec.rb +0 -1
- data/spec/sql_inspections/unionize_sql_spec.rb +2 -2
- data/spec/sql_inspections/window_sql_spec.rb +12 -0
- data/spec/sql_inspections/with_cte_sql_spec.rb +30 -1
- data/spec/support/database_cleaner.rb +1 -1
- data/spec/support/models.rb +12 -0
- metadata +18 -20
- data/lib/active_record_extended/patch/5_0/predicate_builder_decorator.rb +0 -87
- data/lib/active_record_extended/patch/5_0/regex_match.rb +0 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7f3f7041cc4869183bc20b38f8387bd8f3f216225990a62ec0c4e0fba5f847dd
|
4
|
+
data.tar.gz: c61e5ad1c2fae32a5dbfe8660bb888d85f255f285a2df8b1f02206f51cbd71b7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c27cf6541f3e11a3beb00263be7c1478e8f4ddb64b0121ed141347ca995252453864bd3cf0992038b7ea2aa1c409f50792800fdf909d8e544edd470af35231fa
|
7
|
+
data.tar.gz: d0426b00cca79e3d0d145fb87fdd05c25611ff5bfde95c8c1845a281786bd4c7e6bfbd866df46cb8ce1ccd45ab5b4bd91aa83e6c21db879721436574f90087cf
|
data/README.md
CHANGED
@@ -52,11 +52,12 @@ Active Record Extended is essentially providing users with the other half of Pos
|
|
52
52
|
## Compatibility
|
53
53
|
|
54
54
|
This package is designed align and work with any officially supported Ruby and Rails versions.
|
55
|
-
- Minimum Ruby Version: 2.
|
56
|
-
- Minimum Rails Version: 5.
|
57
|
-
-
|
58
|
-
- Latest
|
59
|
-
-
|
55
|
+
- Minimum Ruby Version: 2.4.x **(EOL warning!)**
|
56
|
+
- Minimum Rails Version: 5.1.x **(EOL warning!)**
|
57
|
+
- Minimum Postgres Version: 9.6.x **(EOL warning!)**
|
58
|
+
- Latest Ruby supported: 3.0.x
|
59
|
+
- Latest Rails supported: 7.0.x
|
60
|
+
- Postgres: 9.6-current(13) (probably works with most older versions to a certain point)
|
60
61
|
|
61
62
|
## Installation
|
62
63
|
|
@@ -430,7 +431,7 @@ While quite the mouthful of an explanation. The implementation of combining unre
|
|
430
431
|
product_query =
|
431
432
|
Product.select(:id)
|
432
433
|
.joins(:items)
|
433
|
-
.select_row_to_json(item_query, key: :outer_items, as: :items,
|
434
|
+
.select_row_to_json(item_query, key: :outer_items, as: :items, cast_with: :array) do |item_scope|
|
434
435
|
item_scope.where("outer_items.product_id = products.id")
|
435
436
|
# Results to:
|
436
437
|
# SELECT ..., ARRAY(SELECT ROW_TO_JSON("outer_items")
|
@@ -440,7 +441,7 @@ While quite the mouthful of an explanation. The implementation of combining unre
|
|
440
441
|
end
|
441
442
|
|
442
443
|
# Not defining a key will automatically generate a random key between a-z
|
443
|
-
category_query = Category.select(:name, :id).select_row_to_json(product_query, as: :products,
|
444
|
+
category_query = Category.select(:name, :id).select_row_to_json(product_query, as: :products, cast_with: :array)
|
444
445
|
Category.json_build_object(:physical_category, category_query.where(id: physical_cat.id)).results
|
445
446
|
#=> {
|
446
447
|
# "physical_category" => {
|
@@ -648,12 +649,12 @@ SELECT "people".*
|
|
648
649
|
|
649
650
|
```ruby
|
650
651
|
users = Person.where(id: 1..5)
|
651
|
-
|
652
|
+
except_these_users = Person.where(id: 2..4)
|
652
653
|
|
653
|
-
Person.union_except(users,
|
654
|
+
Person.union_except(users, except_these_users) #=> [#<Person id: 1, ..>, #<Person id: 5,..>]
|
654
655
|
|
655
656
|
# You can also chain union's
|
656
|
-
Person.union.except(users,
|
657
|
+
Person.union.except(users, except_these_users).union(Person.where(id: 20))
|
657
658
|
```
|
658
659
|
|
659
660
|
Query Output
|
@@ -14,7 +14,22 @@ module ActiveRecordExtended
|
|
14
14
|
|
15
15
|
module Merger
|
16
16
|
def normal_values
|
17
|
-
super + [:
|
17
|
+
super + [:union, :define_window]
|
18
|
+
end
|
19
|
+
|
20
|
+
def merge
|
21
|
+
merge_ctes!
|
22
|
+
super
|
23
|
+
end
|
24
|
+
|
25
|
+
def merge_ctes!
|
26
|
+
return unless other.with_values?
|
27
|
+
|
28
|
+
if other.recursive_value? && !relation.recursive_value?
|
29
|
+
relation.with!(:chain).recursive(other.cte)
|
30
|
+
else
|
31
|
+
relation.with!(other.cte)
|
32
|
+
end
|
18
33
|
end
|
19
34
|
end
|
20
35
|
|
@@ -1,10 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# TODO: Remove this when ruby 2.3 support is dropped
|
4
|
-
unless Hash.instance_methods(false).include?(:compact!)
|
5
|
-
require "active_support/all"
|
6
|
-
end
|
7
|
-
|
8
3
|
require "active_record"
|
9
4
|
require "active_record/relation"
|
10
5
|
require "active_record/relation/merger"
|
@@ -23,12 +18,8 @@ require "active_record_extended/query_methods/inet"
|
|
23
18
|
require "active_record_extended/query_methods/json"
|
24
19
|
require "active_record_extended/query_methods/select"
|
25
20
|
|
26
|
-
if
|
27
|
-
if ActiveRecord::VERSION::MINOR.zero?
|
28
|
-
require "active_record_extended/patch/5_0/regex_match"
|
29
|
-
require "active_record_extended/patch/5_0/predicate_builder_decorator"
|
30
|
-
end
|
21
|
+
if Gem::Requirement.new("~> 5.1.0").satisfied_by?(ActiveRecord.gem_version)
|
31
22
|
require "active_record_extended/patch/5_1/where_clause"
|
32
|
-
|
23
|
+
else
|
33
24
|
require "active_record_extended/patch/5_2/where_clause"
|
34
25
|
end
|
@@ -5,28 +5,31 @@ require "arel/nodes/function"
|
|
5
5
|
|
6
6
|
module Arel
|
7
7
|
module Nodes
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
8
|
+
if Gem::Requirement.new("< 6.1").satisfied_by?(ActiveRecord.gem_version)
|
9
|
+
["Contains", "Overlaps"].each { |binary_node_name| const_set(binary_node_name, Class.new(::Arel::Nodes::Binary)) }
|
10
|
+
end
|
11
|
+
|
12
|
+
[
|
13
|
+
"ContainsHStore",
|
14
|
+
"ContainsArray",
|
15
|
+
"ContainedInArray"
|
14
16
|
].each { |binary_node_name| const_set(binary_node_name, Class.new(::Arel::Nodes::Binary)) }
|
15
17
|
|
16
|
-
|
17
|
-
RowToJson
|
18
|
-
JsonBuildObject
|
19
|
-
JsonbBuildObject
|
20
|
-
ToJson
|
21
|
-
ToJsonb
|
22
|
-
Array
|
23
|
-
ArrayAgg
|
18
|
+
[
|
19
|
+
"RowToJson",
|
20
|
+
"JsonBuildObject",
|
21
|
+
"JsonbBuildObject",
|
22
|
+
"ToJson",
|
23
|
+
"ToJsonb",
|
24
|
+
"Array",
|
25
|
+
"ArrayAgg"
|
24
26
|
].each do |function_node_name|
|
25
27
|
func_klass = Class.new(::Arel::Nodes::Function) do
|
26
28
|
def initialize(*args)
|
27
29
|
super
|
28
30
|
return if @expressions.is_a?(::Array)
|
29
|
-
|
31
|
+
|
32
|
+
@expressions = @expressions.is_a?(::Arel::Nodes::Node) ? [@expressions] : [::Arel.sql(@expressions)]
|
30
33
|
end
|
31
34
|
end
|
32
35
|
|
@@ -34,12 +37,12 @@ module Arel
|
|
34
37
|
end
|
35
38
|
|
36
39
|
module Inet
|
37
|
-
|
38
|
-
Contains
|
39
|
-
ContainsEquals
|
40
|
-
ContainedWithin
|
41
|
-
ContainedWithinEquals
|
42
|
-
ContainsOrContainedWithin
|
40
|
+
[
|
41
|
+
"Contains",
|
42
|
+
"ContainsEquals",
|
43
|
+
"ContainedWithin",
|
44
|
+
"ContainedWithinEquals",
|
45
|
+
"ContainsOrContainedWithin"
|
43
46
|
].each { |binary_node_name| const_set(binary_node_name, Class.new(::Arel::Nodes::Binary)) }
|
44
47
|
end
|
45
48
|
end
|
@@ -14,9 +14,10 @@ module Arel
|
|
14
14
|
Arel::Nodes::Equality.new(Nodes.build_quoted(other, self), all_tags_function)
|
15
15
|
end
|
16
16
|
|
17
|
-
def
|
18
|
-
Nodes::
|
17
|
+
def overlaps(other)
|
18
|
+
Nodes::Overlaps.new(self, Nodes.build_quoted(other, self))
|
19
19
|
end
|
20
|
+
alias overlap overlaps
|
20
21
|
|
21
22
|
def contains(other)
|
22
23
|
Nodes::Contains.new self, Nodes.build_quoted(other, self)
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "arel/nodes/sql_literal"
|
4
|
+
|
5
|
+
# CTE alias fix for Rails 6.1
|
6
|
+
module Arel
|
7
|
+
module Nodes
|
8
|
+
module SqlLiteralDecorator
|
9
|
+
def name
|
10
|
+
self
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
Arel::Nodes::SqlLiteral.prepend(Arel::Nodes::SqlLiteralDecorator)
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "active_record_extended/arel/nodes"
|
4
|
+
require "active_record_extended/arel/sql_literal"
|
4
5
|
require "active_record_extended/arel/aggregate_function_name"
|
5
6
|
require "active_record_extended/arel/predications"
|
6
7
|
require "active_record_extended/arel/visitors/postgresql_decorator"
|
@@ -61,13 +61,14 @@ module ActiveRecordExtended
|
|
61
61
|
# In Rails 5.2 the arel table maintains attribute binds
|
62
62
|
def bind_attributes(query)
|
63
63
|
return [] unless query.respond_to?(:bound_attributes)
|
64
|
+
|
64
65
|
query.bound_attributes.map(&:value)
|
65
66
|
end
|
66
67
|
|
67
68
|
# Rails 5.1 fix
|
68
69
|
def unprepared_query(query)
|
69
|
-
query.gsub(/((?<!\\)'.*?(?<!\\)'|(?<!\\)".*?(?<!\\)")|(
|
70
|
-
Regexp.last_match(2)&.gsub(
|
70
|
+
query.gsub(/((?<!\\)'.*?(?<!\\)'|(?<!\\)".*?(?<!\\)")|(=\ \$\d+)/) do |match|
|
71
|
+
Regexp.last_match(2)&.gsub(/=\ \$\d+/, "= ?") || match
|
71
72
|
end
|
72
73
|
end
|
73
74
|
|
@@ -78,9 +79,9 @@ module ActiveRecordExtended
|
|
78
79
|
def generate_where_clause(query)
|
79
80
|
case query
|
80
81
|
when String, Hash
|
81
|
-
@scope.where(query)
|
82
|
+
@scope.unscoped.where(query)
|
82
83
|
when Array
|
83
|
-
@scope.where(*query)
|
84
|
+
@scope.unscoped.where(*query)
|
84
85
|
else
|
85
86
|
query
|
86
87
|
end
|
@@ -30,7 +30,7 @@ module ActiveRecordExtended
|
|
30
30
|
end
|
31
31
|
|
32
32
|
def sort_order_sql(dir)
|
33
|
-
|
33
|
+
["asc", "desc"].include?(dir.to_s) ? dir.to_s : "asc"
|
34
34
|
end
|
35
35
|
|
36
36
|
def xor_field_options(options)
|
@@ -52,6 +52,7 @@ module ActiveRecordExtended
|
|
52
52
|
def xor_field_options_for_associations(associations)
|
53
53
|
associations.each_with_object({}) do |association_name, options|
|
54
54
|
reflection = reflect_on_association(association_name)
|
55
|
+
reflection = reflection.through_reflection if reflection.through_reflection?
|
55
56
|
options[reflection.table_name] = reflection.foreign_key
|
56
57
|
end
|
57
58
|
end
|
@@ -74,8 +74,12 @@ module ActiveRecordExtended
|
|
74
74
|
# #=> "SELECT \"users\".* FROM \"users\" WHERE \"users\".\"ip\" && '127.0.0.255/32'"
|
75
75
|
#
|
76
76
|
def inet_contains_or_is_contained_within(opts, *rest)
|
77
|
-
substitute_comparisons(
|
78
|
-
|
77
|
+
substitute_comparisons(
|
78
|
+
opts,
|
79
|
+
rest,
|
80
|
+
Arel::Nodes::Inet::ContainsOrContainedWithin,
|
81
|
+
"inet_contains_or_is_contained_within"
|
82
|
+
)
|
79
83
|
end
|
80
84
|
end
|
81
85
|
end
|
@@ -8,7 +8,7 @@ module ActiveRecordExtended
|
|
8
8
|
:json_build_object,
|
9
9
|
:jsonb_build_object,
|
10
10
|
:json_build_literal,
|
11
|
-
:jsonb_build_literal
|
11
|
+
:jsonb_build_literal
|
12
12
|
].freeze
|
13
13
|
|
14
14
|
class JsonChain
|
@@ -51,7 +51,7 @@ module ActiveRecordExtended
|
|
51
51
|
private
|
52
52
|
|
53
53
|
def build_json_literal(arel_klass, values:, col_alias: DEFAULT_ALIAS)
|
54
|
-
json_values = flatten_to_sql(values.to_a
|
54
|
+
json_values = flatten_to_sql(values.to_a) { |value| literal_key(value) }
|
55
55
|
col_alias = double_quote(col_alias)
|
56
56
|
json_build_obj = arel_klass.new(json_values)
|
57
57
|
@scope.select(nested_alias_escape(json_build_obj, col_alias))
|
@@ -77,7 +77,7 @@ module ActiveRecordExtended
|
|
77
77
|
row_to_json = ::Arel::Nodes::ToJsonb.new(row_to_json) if options.dig(:cast_with, :to_jsonb)
|
78
78
|
|
79
79
|
dummy_table = from_clause_constructor(from, key).select(row_to_json)
|
80
|
-
dummy_table = dummy_table.instance_eval(&block) if
|
80
|
+
dummy_table = dummy_table.instance_eval(&block) if block
|
81
81
|
return dummy_table if options[:col_alias].blank?
|
82
82
|
|
83
83
|
query = wrap_row_to_json(dummy_table, options)
|
@@ -98,7 +98,6 @@ module ActiveRecordExtended
|
|
98
98
|
end
|
99
99
|
end
|
100
100
|
|
101
|
-
# TODO: [V2 release] Drop support for option :cast_as_array in favor of a more versatile :cast_with option
|
102
101
|
def json_object_options(args, except: [], only: []) # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity
|
103
102
|
options = {}
|
104
103
|
lean_opts = lambda do |key, &block|
|
@@ -113,12 +112,12 @@ module ActiveRecordExtended
|
|
113
112
|
next if arg.nil?
|
114
113
|
|
115
114
|
if arg.is_a?(Hash)
|
116
|
-
lean_opts.call(:key) { arg.
|
117
|
-
lean_opts.call(:value) { arg
|
118
|
-
lean_opts.call(:col_alias) { arg
|
119
|
-
lean_opts.call(:order_by) { order_by_expression(arg
|
120
|
-
lean_opts.call(:from) { arg
|
121
|
-
lean_opts.call(:cast_with) { casting_options(arg
|
115
|
+
lean_opts.call(:key) { arg.fetch(:key, key_generator) }
|
116
|
+
lean_opts.call(:value) { arg[:value].presence }
|
117
|
+
lean_opts.call(:col_alias) { arg[:as] }
|
118
|
+
lean_opts.call(:order_by) { order_by_expression(arg[:order_by]) }
|
119
|
+
lean_opts.call(:from) { arg[:from].tap { |from_clause| pipe_cte_with!(from_clause) } }
|
120
|
+
lean_opts.call(:cast_with) { casting_options(arg[:cast_with]) }
|
122
121
|
end
|
123
122
|
|
124
123
|
unless except.include?(:values)
|
@@ -155,9 +154,6 @@ module ActiveRecordExtended
|
|
155
154
|
# - key: [Symbol or String] (default=[random letter]) What the row clause will be set as.
|
156
155
|
# - This is useful if you would like to add additional mid-level clauses (see mid-level scope example)
|
157
156
|
#
|
158
|
-
# - cast_as_array [boolean] (default=false): Determines if the query should be nested inside an Array() function
|
159
|
-
# * Will be deprecated in V2.0 in favor of `cast_with` argument
|
160
|
-
#
|
161
157
|
# - cast_with [Symbol or Array of symbols]: Actions to transform your query
|
162
158
|
# * :to_jsonb
|
163
159
|
# * :array
|
@@ -172,13 +168,13 @@ module ActiveRecordExtended
|
|
172
168
|
#
|
173
169
|
# Examples:
|
174
170
|
# subquery = Group.select(:name, :category_id).where("user_id = users.id")
|
175
|
-
# User.select(:name, email).select_row_to_json(subquery, as: :users_groups,
|
171
|
+
# User.select(:name, email).select_row_to_json(subquery, as: :users_groups, cast_with: :array)
|
176
172
|
# #=> [<#User name:.., email:.., users_groups: [{ name: .., category_id: .. }, ..]]
|
177
173
|
#
|
178
174
|
# - Adding mid-level scopes:
|
179
175
|
#
|
180
176
|
# subquery = Group.select(:name, :category_id)
|
181
|
-
# User.select_row_to_json(subquery, key: :group,
|
177
|
+
# User.select_row_to_json(subquery, key: :group, cast_with: :array) do |scope|
|
182
178
|
# scope.where(group: { name: "Nerd Core" })
|
183
179
|
# end
|
184
180
|
# #=> ```sql
|
@@ -252,7 +248,8 @@ module ActiveRecordExtended
|
|
252
248
|
def select_row_to_json(from = nil, **options, &block)
|
253
249
|
from.is_a?(Hash) ? options.merge!(from) : options.reverse_merge!(from: from)
|
254
250
|
options.compact!
|
255
|
-
raise ArgumentError
|
251
|
+
raise ArgumentError.new("Required to provide a non-nilled from clause") unless options.key?(:from)
|
252
|
+
|
256
253
|
JsonChain.new(spawn).row_to_json!(**options, &block)
|
257
254
|
end
|
258
255
|
|
@@ -273,7 +270,7 @@ module ActiveRecordExtended
|
|
273
270
|
# - Generic example:
|
274
271
|
#
|
275
272
|
# subquery = Group.select(:name, :category_id).where("user_id = users.id")
|
276
|
-
# User.select(:name, email).select_row_to_json(subquery, as: :users_groups,
|
273
|
+
# User.select(:name, email).select_row_to_json(subquery, as: :users_groups, cast_with: :array)
|
277
274
|
# #=> [<#User name:.., email:.., users_groups: [{ name: .., category_id: .. }, ..]]
|
278
275
|
#
|
279
276
|
# - Setting a custom value:
|
@@ -52,12 +52,12 @@ module ActiveRecordExtended
|
|
52
52
|
# #=> SELECT (ARRAY_AGG(DISTINCT members.price)) AS past_purchases, ...
|
53
53
|
def process_hash!(hash_of_options, alias_name)
|
54
54
|
enforced_options = {
|
55
|
-
cast_with: hash_of_options
|
56
|
-
order_by: hash_of_options
|
57
|
-
distinct: !(!hash_of_options
|
55
|
+
cast_with: hash_of_options[:cast_with],
|
56
|
+
order_by: hash_of_options[:order_by],
|
57
|
+
distinct: !(!hash_of_options[:distinct])
|
58
58
|
}
|
59
|
-
query_statement = hash_to_dot_notation(hash_of_options
|
60
|
-
select!(query_statement, alias_name, enforced_options)
|
59
|
+
query_statement = hash_to_dot_notation(hash_of_options[:__select_statement] || hash_of_options.first)
|
60
|
+
select!(query_statement, alias_name, **enforced_options)
|
61
61
|
end
|
62
62
|
|
63
63
|
# Turn a hash chain into a query statement:
|
@@ -65,7 +65,7 @@ module ActiveRecordExtended
|
|
65
65
|
def hash_to_dot_notation(column)
|
66
66
|
case column
|
67
67
|
when Hash, Array
|
68
|
-
column.to_a.flat_map(
|
68
|
+
column.to_a.flat_map { |col| hash_to_dot_notation(col) }.join(".")
|
69
69
|
when String, Symbol
|
70
70
|
/^([[:alpha:]]+)$/.match?(column.to_s) ? double_quote(column) : column
|
71
71
|
else
|
@@ -76,7 +76,7 @@ module ActiveRecordExtended
|
|
76
76
|
# Add's select statement values to the current relation, select statement lists
|
77
77
|
def select!(query, alias_name = nil, **options)
|
78
78
|
pipe_cte_with!(query)
|
79
|
-
@scope._select!(to_casted_query(query, alias_name, options))
|
79
|
+
@scope._select!(to_casted_query(query, alias_name, **options))
|
80
80
|
end
|
81
81
|
|
82
82
|
# Wraps the query with the requested query method
|
@@ -84,15 +84,15 @@ module ActiveRecordExtended
|
|
84
84
|
# to_casted_query("memberships.cost", :total_revenue, :sum)
|
85
85
|
# #=> SELECT (SUM(memberships.cost)) AS total_revenue
|
86
86
|
def to_casted_query(query, alias_name, **options)
|
87
|
-
cast_with = options
|
88
|
-
order_expr = order_by_expression(options
|
89
|
-
distinct = cast_with.chomp!("_distinct") || options
|
87
|
+
cast_with = options[:cast_with].to_s.downcase
|
88
|
+
order_expr = order_by_expression(options[:order_by])
|
89
|
+
distinct = cast_with.chomp!("_distinct") || options[:distinct] # account for [:agg_name:]_distinct
|
90
90
|
|
91
91
|
case cast_with
|
92
92
|
when "array", "true"
|
93
93
|
wrap_with_array(query, alias_name)
|
94
94
|
when AGGREGATE_ONE_LINERS
|
95
|
-
expr = to_sql_array(query
|
95
|
+
expr = to_sql_array(query) { |value| group_when_needed(value) }
|
96
96
|
casted_query = ::Arel::Nodes::AggregateFunctionName.new(cast_with, expr, distinct).order_by(order_expr)
|
97
97
|
nested_alias_escape(casted_query, alias_name)
|
98
98
|
else
|
@@ -102,7 +102,8 @@ module ActiveRecordExtended
|
|
102
102
|
end
|
103
103
|
|
104
104
|
def foster_select(*args)
|
105
|
-
raise ArgumentError
|
105
|
+
raise ArgumentError.new("Call `.forster_select' with at least one field") if args.empty?
|
106
|
+
|
106
107
|
spawn._foster_select!(*args)
|
107
108
|
end
|
108
109
|
|
@@ -59,7 +59,7 @@ module ActiveRecordExtended
|
|
59
59
|
protected
|
60
60
|
|
61
61
|
def append_union_order!(union_type, args)
|
62
|
-
args.each
|
62
|
+
args.each { |arg| pipe_cte_with!(arg) }
|
63
63
|
flatten_scopes = flatten_to_sql(args)
|
64
64
|
@scope.union_values += flatten_scopes
|
65
65
|
calculate_union_operation!(union_type, flatten_scopes.size)
|
@@ -81,7 +81,7 @@ module ActiveRecordExtended
|
|
81
81
|
union_values: [],
|
82
82
|
union_operations: [],
|
83
83
|
union_ordering_values: [],
|
84
|
-
unionized_name: nil
|
84
|
+
unionized_name: nil
|
85
85
|
}
|
86
86
|
end
|
87
87
|
|
@@ -89,10 +89,11 @@ module ActiveRecordExtended
|
|
89
89
|
union_values: Array,
|
90
90
|
union_operations: Array,
|
91
91
|
union_ordering_values: Array,
|
92
|
-
unionized_name: lambda { |klass| klass.arel_table.name }
|
92
|
+
unionized_name: lambda { |klass| klass.arel_table.name }
|
93
93
|
}.each_pair do |method_name, default|
|
94
94
|
define_method(method_name) do
|
95
95
|
return unionize_storage[method_name] if send("#{method_name}?")
|
96
|
+
|
96
97
|
(default.is_a?(Proc) ? default.call(@klass) : default.new)
|
97
98
|
end
|
98
99
|
|
@@ -106,19 +107,21 @@ module ActiveRecordExtended
|
|
106
107
|
end
|
107
108
|
|
108
109
|
def union(opts = :chain, *args)
|
109
|
-
return UnionChain.new(spawn) if
|
110
|
+
return UnionChain.new(spawn) if :chain == opts
|
111
|
+
|
110
112
|
opts.nil? ? self : spawn.union!(opts, *args, chain_method: __callee__)
|
111
113
|
end
|
112
114
|
|
113
115
|
(UNIONIZE_METHODS + UNION_RELATION_METHODS).each do |union_method|
|
114
116
|
next if union_method == :union
|
117
|
+
|
115
118
|
alias_method union_method, :union
|
116
119
|
end
|
117
120
|
|
118
121
|
def union!(opts = :chain, *args, chain_method: :union)
|
119
122
|
union_chain = UnionChain.new(self)
|
120
123
|
chain_method ||= :union
|
121
|
-
return union_chain if
|
124
|
+
return union_chain if :chain == opts
|
122
125
|
|
123
126
|
union_chain.public_send(chain_method, *([opts] + args))
|
124
127
|
end
|
@@ -126,11 +129,13 @@ module ActiveRecordExtended
|
|
126
129
|
# Will construct *Just* the union SQL statement that was been built thus far
|
127
130
|
def to_union_sql
|
128
131
|
return unless union_values?
|
132
|
+
|
129
133
|
apply_union_ordering(build_union_nodes!(false)).to_sql
|
130
134
|
end
|
131
135
|
|
132
136
|
def to_nice_union_sql(color = true)
|
133
137
|
return to_union_sql unless defined?(::Niceql)
|
138
|
+
|
134
139
|
::Niceql::Prettifier.prettify_sql(to_union_sql, color)
|
135
140
|
end
|
136
141
|
|
@@ -172,7 +177,7 @@ module ActiveRecordExtended
|
|
172
177
|
|
173
178
|
def build_union_nodes!(raise_error = true)
|
174
179
|
unionize_error_or_warn!(raise_error)
|
175
|
-
union_values.each_with_index.
|
180
|
+
union_values.each_with_index.reduce(nil) do |union_node, (relation_node, index)|
|
176
181
|
next resolve_relation_node(relation_node) if union_node.nil?
|
177
182
|
|
178
183
|
operation = union_operations.fetch(index - 1, :union)
|
@@ -215,6 +220,7 @@ module ActiveRecordExtended
|
|
215
220
|
#
|
216
221
|
def apply_union_ordering(union_nodes)
|
217
222
|
return union_nodes unless union_ordering_values?
|
223
|
+
|
218
224
|
UnionChain.new(self).inline_order_by(union_nodes, union_ordering_values)
|
219
225
|
end
|
220
226
|
|
@@ -222,7 +228,7 @@ module ActiveRecordExtended
|
|
222
228
|
|
223
229
|
def unionize_error_or_warn!(raise_error = true)
|
224
230
|
if raise_error && union_values.size <= 1
|
225
|
-
raise ArgumentError
|
231
|
+
raise ArgumentError.new("You are required to provide 2 or more unions to join!")
|
226
232
|
elsif !raise_error && union_values.size <= 1
|
227
233
|
warn("Warning: You are required to provide 2 or more unions to join.")
|
228
234
|
end
|
@@ -2,12 +2,15 @@
|
|
2
2
|
|
3
3
|
module ActiveRecordExtended
|
4
4
|
module WhereChain
|
5
|
+
AR_VERSION_AT_LEAST_6_1 = ActiveRecord.version >= Gem::Version.new('6.1')
|
6
|
+
|
5
7
|
# Finds Records that have an array column that contain any a set of values
|
6
8
|
# User.where.overlap(tags: [1,2])
|
7
9
|
# # SELECT * FROM users WHERE tags && {1,2}
|
8
|
-
def
|
9
|
-
substitute_comparisons(opts, rest, Arel::Nodes::
|
10
|
+
def overlaps(opts, *rest)
|
11
|
+
substitute_comparisons(opts, rest, Arel::Nodes::Overlaps, "overlap")
|
10
12
|
end
|
13
|
+
alias overlap overlaps
|
11
14
|
|
12
15
|
# Finds Records that contain an element in an array column
|
13
16
|
# User.where.any(tags: 3)
|
@@ -54,10 +57,10 @@ module ActiveRecordExtended
|
|
54
57
|
elsif column.try(:array)
|
55
58
|
Arel::Nodes::ContainsArray.new(arel.left, arel.right)
|
56
59
|
else
|
57
|
-
raise ArgumentError
|
60
|
+
raise ArgumentError.new("Invalid argument for .where.contains(), got #{arel.class}")
|
58
61
|
end
|
59
62
|
else
|
60
|
-
raise ArgumentError
|
63
|
+
raise ArgumentError.new("Invalid argument for .where.contains(), got #{arel.class}")
|
61
64
|
end
|
62
65
|
end
|
63
66
|
end
|
@@ -88,7 +91,7 @@ module ActiveRecordExtended
|
|
88
91
|
when Arel::Nodes::Equality
|
89
92
|
Arel::Nodes::Equality.new(arel.right, Arel::Nodes::NamedFunction.new(function_name, [arel.left]))
|
90
93
|
else
|
91
|
-
raise ArgumentError
|
94
|
+
raise ArgumentError.new("Invalid argument for .where.#{function_name.downcase}(), got #{arel.class}")
|
92
95
|
end
|
93
96
|
end
|
94
97
|
end
|
@@ -99,10 +102,18 @@ module ActiveRecordExtended
|
|
99
102
|
when Arel::Nodes::In, Arel::Nodes::Equality
|
100
103
|
arel_node_class.new(arel.left, arel.right)
|
101
104
|
else
|
102
|
-
raise ArgumentError
|
105
|
+
raise ArgumentError.new("Invalid argument for .where.#{method}(), got #{arel.class}")
|
103
106
|
end
|
104
107
|
end
|
105
108
|
end
|
109
|
+
|
110
|
+
def build_where_clause_for(scope, opts, rest)
|
111
|
+
if AR_VERSION_AT_LEAST_6_1
|
112
|
+
scope.send(:build_where_clause, opts, rest)
|
113
|
+
else
|
114
|
+
scope.send(:where_clause_factory).build(opts, rest)
|
115
|
+
end
|
116
|
+
end
|
106
117
|
end
|
107
118
|
end
|
108
119
|
|
@@ -112,9 +123,9 @@ module ActiveRecord
|
|
112
123
|
prepend ActiveRecordExtended::WhereChain
|
113
124
|
|
114
125
|
def build_where_chain(opts, rest, &block)
|
115
|
-
where_clause = @scope
|
126
|
+
where_clause = build_where_clause_for(@scope, opts, rest)
|
116
127
|
@scope.tap do |scope|
|
117
|
-
scope.references!(PredicateBuilder.references(opts)) if opts.is_a?(Hash)
|
128
|
+
scope.references!(PredicateBuilder.references(opts.stringify_keys)) if opts.is_a?(Hash)
|
118
129
|
scope.where_clause += where_clause.modified_predicates(&block)
|
119
130
|
end
|
120
131
|
end
|
@@ -16,7 +16,7 @@ module ActiveRecordExtended
|
|
16
16
|
@scope.window_values! << {
|
17
17
|
window_name: to_arel_sql(@window_name),
|
18
18
|
partition_by: flatten_to_sql(partitions),
|
19
|
-
order_by: order_by_expression(order_by)
|
19
|
+
order_by: order_by_expression(order_by)
|
20
20
|
}
|
21
21
|
|
22
22
|
@scope
|
@@ -81,8 +81,9 @@ module ActiveRecordExtended
|
|
81
81
|
|
82
82
|
def build_windows(arel)
|
83
83
|
window_values.each do |window_value|
|
84
|
-
window = arel.window(window_value[:window_name])
|
85
|
-
window.
|
84
|
+
window = arel.window(window_value[:window_name])
|
85
|
+
window = window.partition(window_value[:partition_by]) if window_value[:partition_by].present?
|
86
|
+
window.order(window_value[:order_by]) if window_value[:order_by]
|
86
87
|
end
|
87
88
|
end
|
88
89
|
end
|