active_record_extended_telescope 2.0.1
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 +7 -0
- data/README.md +870 -0
- data/lib/active_record_extended.rb +10 -0
- data/lib/active_record_extended/active_record.rb +25 -0
- data/lib/active_record_extended/active_record/relation_patch.rb +50 -0
- data/lib/active_record_extended/arel.rb +7 -0
- data/lib/active_record_extended/arel/aggregate_function_name.rb +40 -0
- data/lib/active_record_extended/arel/nodes.rb +49 -0
- data/lib/active_record_extended/arel/predications.rb +50 -0
- data/lib/active_record_extended/arel/sql_literal.rb +16 -0
- data/lib/active_record_extended/arel/visitors/postgresql_decorator.rb +122 -0
- data/lib/active_record_extended/patch/5_1/where_clause.rb +11 -0
- data/lib/active_record_extended/patch/5_2/where_clause.rb +11 -0
- data/lib/active_record_extended/predicate_builder/array_handler_decorator.rb +20 -0
- data/lib/active_record_extended/query_methods/any_of.rb +93 -0
- data/lib/active_record_extended/query_methods/either.rb +62 -0
- data/lib/active_record_extended/query_methods/inet.rb +88 -0
- data/lib/active_record_extended/query_methods/json.rb +329 -0
- data/lib/active_record_extended/query_methods/select.rb +118 -0
- data/lib/active_record_extended/query_methods/unionize.rb +249 -0
- data/lib/active_record_extended/query_methods/where_chain.rb +132 -0
- data/lib/active_record_extended/query_methods/window.rb +93 -0
- data/lib/active_record_extended/query_methods/with_cte.rb +150 -0
- data/lib/active_record_extended/utilities/order_by.rb +77 -0
- data/lib/active_record_extended/utilities/support.rb +178 -0
- data/lib/active_record_extended/version.rb +5 -0
- data/lib/active_record_extended_telescope.rb +4 -0
- data/spec/active_record_extended_spec.rb +7 -0
- data/spec/query_methods/any_of_spec.rb +131 -0
- data/spec/query_methods/array_query_spec.rb +64 -0
- data/spec/query_methods/either_spec.rb +59 -0
- data/spec/query_methods/hash_query_spec.rb +45 -0
- data/spec/query_methods/inet_query_spec.rb +112 -0
- data/spec/query_methods/json_spec.rb +157 -0
- data/spec/query_methods/select_spec.rb +115 -0
- data/spec/query_methods/unionize_spec.rb +165 -0
- data/spec/query_methods/window_spec.rb +51 -0
- data/spec/query_methods/with_cte_spec.rb +50 -0
- data/spec/spec_helper.rb +28 -0
- data/spec/sql_inspections/any_of_sql_spec.rb +41 -0
- data/spec/sql_inspections/arel/aggregate_function_name_spec.rb +41 -0
- data/spec/sql_inspections/arel/array_spec.rb +63 -0
- data/spec/sql_inspections/arel/inet_spec.rb +66 -0
- data/spec/sql_inspections/contains_sql_queries_spec.rb +47 -0
- data/spec/sql_inspections/either_sql_spec.rb +55 -0
- data/spec/sql_inspections/json_sql_spec.rb +82 -0
- data/spec/sql_inspections/unionize_sql_spec.rb +124 -0
- data/spec/sql_inspections/window_sql_spec.rb +98 -0
- data/spec/sql_inspections/with_cte_sql_spec.rb +95 -0
- data/spec/support/database_cleaner.rb +15 -0
- data/spec/support/models.rb +68 -0
- metadata +245 -0
@@ -0,0 +1,118 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecordExtended
|
4
|
+
module QueryMethods
|
5
|
+
module Select
|
6
|
+
class SelectHelper
|
7
|
+
include ::ActiveRecordExtended::Utilities::Support
|
8
|
+
include ::ActiveRecordExtended::Utilities::OrderBy
|
9
|
+
|
10
|
+
AGGREGATE_ONE_LINERS = /^(exists|sum|max|min|avg|count|jsonb?_agg|(bit|bool)_(and|or)|xmlagg|array_agg)$/.freeze
|
11
|
+
|
12
|
+
def initialize(scope)
|
13
|
+
@scope = scope
|
14
|
+
end
|
15
|
+
|
16
|
+
def build_foster_select(*args)
|
17
|
+
flatten_safely(args).each do |select_arg|
|
18
|
+
case select_arg
|
19
|
+
when String, Symbol
|
20
|
+
select!(select_arg)
|
21
|
+
when Hash
|
22
|
+
select_arg.each_pair do |alias_name, options_or_column|
|
23
|
+
case options_or_column
|
24
|
+
when Array
|
25
|
+
process_array!(options_or_column, alias_name)
|
26
|
+
when Hash
|
27
|
+
process_hash!(options_or_column, alias_name)
|
28
|
+
else
|
29
|
+
select!(options_or_column, alias_name)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
# Assumes that the first element in the array is the source/target column.
|
39
|
+
# Example
|
40
|
+
# process_array_options!([:col_name], :my_alias_name)
|
41
|
+
# #=> SELECT ([:col_name:]) AS "my_alias_name", ...
|
42
|
+
def process_array!(array_of_options, alias_name)
|
43
|
+
options = array_of_options.detect { |opts| opts.is_a?(Hash) }
|
44
|
+
query = { __select_statement: array_of_options.first }
|
45
|
+
query.merge!(options) unless options.nil?
|
46
|
+
process_hash!(query, alias_name)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Processes options that come in as Hash elements
|
50
|
+
# Examples:
|
51
|
+
# process_hash_options!({ memberships: :price, cast_with: :agg_array_distinct }, :past_purchases)
|
52
|
+
# #=> SELECT (ARRAY_AGG(DISTINCT members.price)) AS past_purchases, ...
|
53
|
+
def process_hash!(hash_of_options, alias_name)
|
54
|
+
enforced_options = {
|
55
|
+
cast_with: hash_of_options[:cast_with],
|
56
|
+
order_by: hash_of_options[:order_by],
|
57
|
+
distinct: !(!hash_of_options[:distinct])
|
58
|
+
}
|
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
|
+
end
|
62
|
+
|
63
|
+
# Turn a hash chain into a query statement:
|
64
|
+
# Example: hash_to_dot_notation(table_name: :col_name) #=> "table_name.col_name"
|
65
|
+
def hash_to_dot_notation(column)
|
66
|
+
case column
|
67
|
+
when Hash, Array
|
68
|
+
column.to_a.flat_map { |col| hash_to_dot_notation(col) }.join(".")
|
69
|
+
when String, Symbol
|
70
|
+
/^([[:alpha:]]+)$/.match?(column.to_s) ? double_quote(column) : column
|
71
|
+
else
|
72
|
+
column
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Add's select statement values to the current relation, select statement lists
|
77
|
+
def select!(query, alias_name = nil, **options)
|
78
|
+
pipe_cte_with!(query)
|
79
|
+
@scope._select!(to_casted_query(query, alias_name, **options))
|
80
|
+
end
|
81
|
+
|
82
|
+
# Wraps the query with the requested query method
|
83
|
+
# Example:
|
84
|
+
# to_casted_query("memberships.cost", :total_revenue, :sum)
|
85
|
+
# #=> SELECT (SUM(memberships.cost)) AS total_revenue
|
86
|
+
def to_casted_query(query, alias_name, **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
|
+
|
91
|
+
case cast_with
|
92
|
+
when "array", "true"
|
93
|
+
wrap_with_array(query, alias_name)
|
94
|
+
when AGGREGATE_ONE_LINERS
|
95
|
+
expr = to_sql_array(query) { |value| group_when_needed(value) }
|
96
|
+
casted_query = ::Arel::Nodes::AggregateFunctionName.new(cast_with, expr, distinct).order_by(order_expr)
|
97
|
+
nested_alias_escape(casted_query, alias_name)
|
98
|
+
else
|
99
|
+
alias_name.presence ? nested_alias_escape(query, alias_name) : query
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def foster_select(*args)
|
105
|
+
raise ArgumentError.new("Call `.forster_select' with at least one field") if args.empty?
|
106
|
+
|
107
|
+
spawn._foster_select!(*args)
|
108
|
+
end
|
109
|
+
|
110
|
+
def _foster_select!(*args)
|
111
|
+
SelectHelper.new(self).build_foster_select(*args)
|
112
|
+
self
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
ActiveRecord::Relation.prepend(ActiveRecordExtended::QueryMethods::Select)
|
@@ -0,0 +1,249 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecordExtended
|
4
|
+
module QueryMethods
|
5
|
+
module Unionize
|
6
|
+
UNION_RELATION_METHODS = [:order_union, :reorder_union, :union_as].freeze
|
7
|
+
UNIONIZE_METHODS = [:union, :union_all, :union_except, :union_intersect].freeze
|
8
|
+
|
9
|
+
class UnionChain
|
10
|
+
include ::ActiveRecordExtended::Utilities::Support
|
11
|
+
include ::ActiveRecordExtended::Utilities::OrderBy
|
12
|
+
|
13
|
+
def initialize(scope)
|
14
|
+
@scope = scope
|
15
|
+
end
|
16
|
+
|
17
|
+
def as(from_clause_name)
|
18
|
+
@scope.unionized_name = from_clause_name.to_s
|
19
|
+
@scope
|
20
|
+
end
|
21
|
+
alias union_as as
|
22
|
+
|
23
|
+
def order(*ordering_args)
|
24
|
+
process_ordering_arguments!(ordering_args)
|
25
|
+
@scope.union_ordering_values += ordering_args
|
26
|
+
@scope
|
27
|
+
end
|
28
|
+
alias order_union order
|
29
|
+
|
30
|
+
def reorder(*ordering_args)
|
31
|
+
@scope.union_ordering_values.clear
|
32
|
+
order(*ordering_args)
|
33
|
+
end
|
34
|
+
alias reorder_union reorder
|
35
|
+
|
36
|
+
def union(*args)
|
37
|
+
append_union_order!(:union, args)
|
38
|
+
@scope
|
39
|
+
end
|
40
|
+
|
41
|
+
def all(*args)
|
42
|
+
append_union_order!(:union_all, args)
|
43
|
+
@scope
|
44
|
+
end
|
45
|
+
alias union_all all
|
46
|
+
|
47
|
+
def except(*args)
|
48
|
+
append_union_order!(:except, args)
|
49
|
+
@scope
|
50
|
+
end
|
51
|
+
alias union_except except
|
52
|
+
|
53
|
+
def intersect(*args)
|
54
|
+
append_union_order!(:intersect, args)
|
55
|
+
@scope
|
56
|
+
end
|
57
|
+
alias union_intersect intersect
|
58
|
+
|
59
|
+
protected
|
60
|
+
|
61
|
+
def append_union_order!(union_type, args)
|
62
|
+
args.each { |arg| pipe_cte_with!(arg) }
|
63
|
+
flatten_scopes = flatten_to_sql(args)
|
64
|
+
@scope.union_values += flatten_scopes
|
65
|
+
calculate_union_operation!(union_type, flatten_scopes.size)
|
66
|
+
end
|
67
|
+
|
68
|
+
def calculate_union_operation!(union_type, scope_count)
|
69
|
+
scope_count -= 1 unless @scope.union_operations?
|
70
|
+
scope_count = 1 if scope_count <= 0 && @scope.union_values.size <= 1
|
71
|
+
@scope.union_operations += [union_type] * scope_count
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def unionize_storage
|
76
|
+
@values.fetch(:unionize, {})
|
77
|
+
end
|
78
|
+
|
79
|
+
def unionize_storage!
|
80
|
+
@values[:unionize] ||= {
|
81
|
+
union_values: [],
|
82
|
+
union_operations: [],
|
83
|
+
union_ordering_values: [],
|
84
|
+
unionized_name: nil
|
85
|
+
}
|
86
|
+
end
|
87
|
+
|
88
|
+
{
|
89
|
+
union_values: Array,
|
90
|
+
union_operations: Array,
|
91
|
+
union_ordering_values: Array,
|
92
|
+
unionized_name: lambda { |klass| klass.arel_table.name }
|
93
|
+
}.each_pair do |method_name, default|
|
94
|
+
define_method(method_name) do
|
95
|
+
return unionize_storage[method_name] if send("#{method_name}?")
|
96
|
+
|
97
|
+
(default.is_a?(Proc) ? default.call(@klass) : default.new)
|
98
|
+
end
|
99
|
+
|
100
|
+
define_method("#{method_name}?") do
|
101
|
+
unionize_storage.key?(method_name) && !unionize_storage[method_name].presence.nil?
|
102
|
+
end
|
103
|
+
|
104
|
+
define_method("#{method_name}=") do |value|
|
105
|
+
unionize_storage![method_name] = value
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def union(opts = :chain, *args)
|
110
|
+
return UnionChain.new(spawn) if opts == :chain
|
111
|
+
|
112
|
+
opts.nil? ? self : spawn.union!(opts, *args, chain_method: __callee__)
|
113
|
+
end
|
114
|
+
|
115
|
+
(UNIONIZE_METHODS + UNION_RELATION_METHODS).each do |union_method|
|
116
|
+
next if union_method == :union
|
117
|
+
|
118
|
+
alias_method union_method, :union
|
119
|
+
end
|
120
|
+
|
121
|
+
def union!(opts = :chain, *args, chain_method: :union)
|
122
|
+
union_chain = UnionChain.new(self)
|
123
|
+
chain_method ||= :union
|
124
|
+
return union_chain if opts == :chain
|
125
|
+
|
126
|
+
union_chain.public_send(chain_method, *([opts] + args))
|
127
|
+
end
|
128
|
+
|
129
|
+
# Will construct *Just* the union SQL statement that was been built thus far
|
130
|
+
def to_union_sql
|
131
|
+
return unless union_values?
|
132
|
+
|
133
|
+
apply_union_ordering(build_union_nodes!(false)).to_sql
|
134
|
+
end
|
135
|
+
|
136
|
+
def to_nice_union_sql(color = true)
|
137
|
+
return to_union_sql unless defined?(::Niceql)
|
138
|
+
|
139
|
+
::Niceql::Prettifier.prettify_sql(to_union_sql, color)
|
140
|
+
end
|
141
|
+
|
142
|
+
protected
|
143
|
+
|
144
|
+
def build_unions(arel = @klass.arel_table)
|
145
|
+
return unless union_values?
|
146
|
+
|
147
|
+
union_nodes = apply_union_ordering(build_union_nodes!)
|
148
|
+
table_name = Arel.sql(unionized_name)
|
149
|
+
table_alias = arel.create_table_alias(arel.grouping(union_nodes), table_name)
|
150
|
+
arel.from(table_alias)
|
151
|
+
end
|
152
|
+
|
153
|
+
# Builds a set of nested nodes that union each other's results
|
154
|
+
#
|
155
|
+
# Note: Order of chained unions *DOES* matter
|
156
|
+
#
|
157
|
+
# Example:
|
158
|
+
#
|
159
|
+
# User.union(User.select(:id).where(id: 8))
|
160
|
+
# .union(User.select(:id).where(id: 50))
|
161
|
+
# .union.except(User.select(:id).where(id: 8))
|
162
|
+
#
|
163
|
+
# #=> [<#User id: 50]]
|
164
|
+
#
|
165
|
+
# ```sql
|
166
|
+
# SELECT users.*
|
167
|
+
# FROM(
|
168
|
+
# (
|
169
|
+
# (SELECT users.id FROM users WHERE id = 8)
|
170
|
+
# UNION
|
171
|
+
# (SELECT users.id FROM users WHERE id = 50)
|
172
|
+
# )
|
173
|
+
# EXCEPT
|
174
|
+
# (SELECT users.id FROM users WHERE id = 8)
|
175
|
+
# ) users;
|
176
|
+
# ```
|
177
|
+
|
178
|
+
def build_union_nodes!(raise_error = true)
|
179
|
+
unionize_error_or_warn!(raise_error)
|
180
|
+
union_values.each_with_index.reduce(nil) do |union_node, (relation_node, index)|
|
181
|
+
next resolve_relation_node(relation_node) if union_node.nil?
|
182
|
+
|
183
|
+
operation = union_operations.fetch(index - 1, :union)
|
184
|
+
left = union_node
|
185
|
+
right = resolve_relation_node(relation_node)
|
186
|
+
|
187
|
+
case operation
|
188
|
+
when :union_all
|
189
|
+
Arel::Nodes::UnionAll.new(left, right)
|
190
|
+
when :except
|
191
|
+
Arel::Nodes::Except.new(left, right)
|
192
|
+
when :intersect
|
193
|
+
Arel::Nodes::Intersect.new(left, right)
|
194
|
+
else
|
195
|
+
Arel::Nodes::Union.new(left, right)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
# Apply's the allowed ORDER BY to the end of the final union statement
|
201
|
+
#
|
202
|
+
# Note: This will only apply at the very end of the union statements. Not nested ones.
|
203
|
+
# (I guess you could double nest a union and apply it, but that would be dumb)
|
204
|
+
#
|
205
|
+
# Example:
|
206
|
+
# User.union(User.select(:id).where(id: 8))
|
207
|
+
# .union(User.select(:id).where(id: 50))
|
208
|
+
# .union.order(id: :desc)
|
209
|
+
# #=> [<#User id: 50>, <#User id: 8>]
|
210
|
+
#
|
211
|
+
# ```sql
|
212
|
+
# SELECT users.*
|
213
|
+
# FROM(
|
214
|
+
# (SELECT users.id FROM users WHERE id = 8)
|
215
|
+
# UNION
|
216
|
+
# (SELECT users.id FROM users WHERE id = 50)
|
217
|
+
# ORDER BY id DESC
|
218
|
+
# ) users;
|
219
|
+
# ```
|
220
|
+
#
|
221
|
+
def apply_union_ordering(union_nodes)
|
222
|
+
return union_nodes unless union_ordering_values?
|
223
|
+
|
224
|
+
UnionChain.new(self).inline_order_by(union_nodes, union_ordering_values)
|
225
|
+
end
|
226
|
+
|
227
|
+
private
|
228
|
+
|
229
|
+
def unionize_error_or_warn!(raise_error = true)
|
230
|
+
if raise_error && union_values.size <= 1
|
231
|
+
raise ArgumentError.new("You are required to provide 2 or more unions to join!")
|
232
|
+
elsif !raise_error && union_values.size <= 1
|
233
|
+
warn("Warning: You are required to provide 2 or more unions to join.")
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
def resolve_relation_node(relation_node)
|
238
|
+
case relation_node
|
239
|
+
when String
|
240
|
+
Arel::Nodes::Grouping.new(Arel.sql(relation_node))
|
241
|
+
else
|
242
|
+
relation_node.arel
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
ActiveRecord::Relation.prepend(ActiveRecordExtended::QueryMethods::Unionize)
|
@@ -0,0 +1,132 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecordExtended
|
4
|
+
module WhereChain
|
5
|
+
# Finds Records that have an array column that contain any a set of values
|
6
|
+
# User.where.overlap(tags: [1,2])
|
7
|
+
# # SELECT * FROM users WHERE tags && {1,2}
|
8
|
+
def overlaps(opts, *rest)
|
9
|
+
substitute_comparisons(opts, rest, Arel::Nodes::Overlaps, "overlap")
|
10
|
+
end
|
11
|
+
alias overlap overlaps
|
12
|
+
|
13
|
+
# Finds Records that contain an element in an array column
|
14
|
+
# User.where.any(tags: 3)
|
15
|
+
# # SELECT user.* FROM user WHERE 3 = ANY(user.tags)
|
16
|
+
def any(opts, *rest)
|
17
|
+
equality_to_function("ANY", opts, rest)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Finds Records that contain a single matchable array element
|
21
|
+
# User.where.all(tags: 3)
|
22
|
+
# # SELECT user.* FROM user WHERE 3 = ALL(user.tags)
|
23
|
+
def all(opts, *rest)
|
24
|
+
equality_to_function("ALL", opts, rest)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Finds Records that contains a nested set elements
|
28
|
+
#
|
29
|
+
# Array Column Type:
|
30
|
+
# User.where.contains(tags: [1, 3])
|
31
|
+
# # SELECT user.* FROM user WHERE user.tags @> {1,3}
|
32
|
+
#
|
33
|
+
# HStore Column Type:
|
34
|
+
# User.where.contains(data: { nickname: 'chainer' })
|
35
|
+
# # SELECT user.* FROM user WHERE user.data @> 'nickname' => 'chainer'
|
36
|
+
#
|
37
|
+
# JSONB Column Type:
|
38
|
+
# User.where.contains(data: { nickname: 'chainer' })
|
39
|
+
# # SELECT user.* FROM user WHERE user.data @> {'nickname': 'chainer'}
|
40
|
+
#
|
41
|
+
# This can also be used along side joined tables
|
42
|
+
#
|
43
|
+
# JSONB Column Type Example:
|
44
|
+
# Tag.joins(:user).where.contains(user: { data: { nickname: 'chainer' } })
|
45
|
+
# # SELECT tags.* FROM tags INNER JOIN user on user.id = tags.user_id WHERE user.data @> { nickname: 'chainer' }
|
46
|
+
#
|
47
|
+
def contains(opts, *rest)
|
48
|
+
build_where_chain(opts, rest) do |arel|
|
49
|
+
case arel
|
50
|
+
when Arel::Nodes::In, Arel::Nodes::Equality
|
51
|
+
column = left_column(arel) || column_from_association(arel)
|
52
|
+
|
53
|
+
if [:hstore, :jsonb].include?(column.type)
|
54
|
+
Arel::Nodes::ContainsHStore.new(arel.left, arel.right)
|
55
|
+
elsif column.try(:array)
|
56
|
+
Arel::Nodes::ContainsArray.new(arel.left, arel.right)
|
57
|
+
else
|
58
|
+
raise ArgumentError.new("Invalid argument for .where.contains(), got #{arel.class}")
|
59
|
+
end
|
60
|
+
else
|
61
|
+
raise ArgumentError.new("Invalid argument for .where.contains(), got #{arel.class}")
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def matchable_column?(col, arel)
|
69
|
+
col.name == arel.left.name.to_s || col.name == arel.left.relation.name.to_s
|
70
|
+
end
|
71
|
+
|
72
|
+
def column_from_association(arel)
|
73
|
+
assoc = assoc_from_related_table(arel)
|
74
|
+
assoc.klass.columns.detect { |col| matchable_column?(col, arel) } if assoc
|
75
|
+
end
|
76
|
+
|
77
|
+
def assoc_from_related_table(arel)
|
78
|
+
@scope.klass.reflect_on_association(arel.left.relation.name.to_sym) ||
|
79
|
+
@scope.klass.reflect_on_association(arel.left.relation.name.singularize.to_sym)
|
80
|
+
end
|
81
|
+
|
82
|
+
def left_column(arel)
|
83
|
+
@scope.klass.columns_hash[arel.left.name] || @scope.klass.columns_hash[arel.left.relation.name]
|
84
|
+
end
|
85
|
+
|
86
|
+
def equality_to_function(function_name, opts, rest)
|
87
|
+
build_where_chain(opts, rest) do |arel|
|
88
|
+
case arel
|
89
|
+
when Arel::Nodes::Equality
|
90
|
+
Arel::Nodes::Equality.new(arel.right, Arel::Nodes::NamedFunction.new(function_name, [arel.left]))
|
91
|
+
else
|
92
|
+
raise ArgumentError.new("Invalid argument for .where.#{function_name.downcase}(), got #{arel.class}")
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def substitute_comparisons(opts, rest, arel_node_class, method)
|
98
|
+
build_where_chain(opts, rest) do |arel|
|
99
|
+
case arel
|
100
|
+
when Arel::Nodes::In, Arel::Nodes::Equality
|
101
|
+
arel_node_class.new(arel.left, arel.right)
|
102
|
+
else
|
103
|
+
raise ArgumentError.new("Invalid argument for .where.#{method}(), got #{arel.class}")
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def build_where_clause_for(scope, opts, rest)
|
109
|
+
if ActiveRecord::VERSION::MAJOR == 6 && ActiveRecord::VERSION::MINOR == 1
|
110
|
+
scope.send(:build_where_clause, opts, rest)
|
111
|
+
else
|
112
|
+
scope.send(:where_clause_factory).build(opts, rest)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
module ActiveRecord
|
119
|
+
module QueryMethods
|
120
|
+
class WhereChain
|
121
|
+
prepend ActiveRecordExtended::WhereChain
|
122
|
+
|
123
|
+
def build_where_chain(opts, rest, &block)
|
124
|
+
where_clause = build_where_clause_for(@scope, opts, rest)
|
125
|
+
@scope.tap do |scope|
|
126
|
+
scope.references!(PredicateBuilder.references(opts.stringify_keys)) if opts.is_a?(Hash)
|
127
|
+
scope.where_clause += where_clause.modified_predicates(&block)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|