activerecord-refined 0.3.3 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/sandbox.yml +158 -0
- data/.github/workflows/test.yml +56 -2
- data/LICENSE.txt +2 -1
- data/README.md +263 -17
- data/activerecord-refined.gemspec +3 -1
- data/benchmark/query_building.rb +129 -0
- data/examples/complex_joins.rb +14 -1
- data/examples/ctes.rb +82 -0
- data/examples/expressions.rb +87 -0
- data/examples/postgresql.rb +105 -0
- data/examples/predicates.rb +93 -0
- data/examples/subqueries.rb +67 -0
- data/lib/active_record/refined/ast.rb +320 -33
- data/lib/active_record/refined.rb +180 -20
- data/lib/activerecord-refined/version.rb +1 -1
- data/test/test_block_syntax.rb +597 -20
- data/test/test_helper.rb +12 -0
- metadata +8 -1
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
module ActiveRecord
|
|
2
2
|
module Refined
|
|
3
3
|
module AST
|
|
4
|
+
# Column aliases and function names are written into the SQL as given,
|
|
5
|
+
# where a value would have been quoted, so anything that is not a plain
|
|
6
|
+
# name is refused. Quoting them instead would need the model's adapter,
|
|
7
|
+
# which does not reach this far, and quoting with the wrong one would be
|
|
8
|
+
# a bug of its own -- MySQL does not read "x" as an identifier.
|
|
9
|
+
NAME = /[[:alpha:]_][[:alnum:]_$]*/
|
|
10
|
+
ALIAS_NAME = /\A#{NAME}\z/
|
|
11
|
+
FUNCTION_NAME = /\A#{NAME}(\.#{NAME})?\z/
|
|
12
|
+
# A SQL type as cast writes it: words, at most parenthesized with
|
|
13
|
+
# lengths -- double precision, decimal(10,2).
|
|
14
|
+
TYPE_NAME = /\A[[:alpha:]_][[:alnum:]_ ]*(\(\d+(, ?\d+)?\))?\z/
|
|
15
|
+
|
|
16
|
+
def self.check_name(name, pattern, what)
|
|
17
|
+
return name if pattern.match?(name.to_s)
|
|
18
|
+
raise ArgumentError, "#{name.inspect} is not a plain #{what}"
|
|
19
|
+
end
|
|
20
|
+
|
|
4
21
|
# Predicate builders shared by symbols, qualified columns and
|
|
5
22
|
# expressions. Imported into the Symbol refinement with
|
|
6
23
|
# Refinement#import_methods, so every method must be defined with def.
|
|
@@ -62,6 +79,30 @@ module ActiveRecord
|
|
|
62
79
|
Like.new(self, pattern)
|
|
63
80
|
end
|
|
64
81
|
|
|
82
|
+
def ilike?(pattern)
|
|
83
|
+
Like.new(self, pattern, nil, case_sensitive: false)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Case-insensitive equality, folded on both sides rather than left to
|
|
87
|
+
# the collation, so it means the same thing on every adapter.
|
|
88
|
+
def casecmp?(value)
|
|
89
|
+
if value.nil?
|
|
90
|
+
raise ArgumentError, "casecmp? does not take nil; use null? instead"
|
|
91
|
+
end
|
|
92
|
+
Comparison.new(Function.new("LOWER", [self]), :==,
|
|
93
|
+
Function.new("LOWER", [value]))
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Null-safe comparison: unlike = and <>, these treat NULL as a value,
|
|
97
|
+
# so not_distinct_from? is the one equality that may take nil.
|
|
98
|
+
def distinct_from?(value)
|
|
99
|
+
DistinctFrom.new(self, value, negated: true)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def not_distinct_from?(value)
|
|
103
|
+
DistinctFrom.new(self, value)
|
|
104
|
+
end
|
|
105
|
+
|
|
65
106
|
def start_with?(*prefixes)
|
|
66
107
|
if prefixes.empty?
|
|
67
108
|
raise ArgumentError, "start_with? needs at least one prefix"
|
|
@@ -80,8 +121,75 @@ module ActiveRecord
|
|
|
80
121
|
Like.new(self, "%#{Like.escape(substring)}%", Like::ESCAPE)
|
|
81
122
|
end
|
|
82
123
|
|
|
124
|
+
# The array comparisons carry the meaning of their Ruby namesakes.
|
|
125
|
+
# member? is Enumerable's element test, so an Array argument is
|
|
126
|
+
# rejected rather than quietly meaning something Array#member? does
|
|
127
|
+
# not; whole-array comparisons go by the Set and Array names.
|
|
83
128
|
def member?(element)
|
|
84
|
-
|
|
129
|
+
if element.is_a?(::Array) || element.is_a?(::Set)
|
|
130
|
+
raise ArgumentError,
|
|
131
|
+
"member? takes a single element; use superset? to require every element"
|
|
132
|
+
end
|
|
133
|
+
ArrayPredicate.new(self, :"@>", [element])
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def superset?(elements)
|
|
137
|
+
ArrayPredicate.new(self, :"@>", ArrayPredicate.elements(elements, "superset?"))
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def subset?(elements)
|
|
141
|
+
ArrayPredicate.new(self, :"<@", ArrayPredicate.elements(elements, "subset?"))
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def intersect?(elements)
|
|
145
|
+
ArrayPredicate.new(self, :"&&", ArrayPredicate.elements(elements, "intersect?"))
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Arithmetic builders shared by symbols, qualified columns and
|
|
150
|
+
# expressions. Imported into the Symbol refinement like Predications,
|
|
151
|
+
# so every method must be defined with def.
|
|
152
|
+
module Arithmetics
|
|
153
|
+
def +(other)
|
|
154
|
+
Arithmetic.new(self, :+, other)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def -(other)
|
|
158
|
+
Arithmetic.new(self, :-, other)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def *(other)
|
|
162
|
+
Arithmetic.new(self, :*, other)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def /(other)
|
|
166
|
+
Arithmetic.new(self, :/, other)
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Aggregate builders shared by symbols, qualified columns and
|
|
171
|
+
# expressions. Imported into the Symbol refinement like Predications,
|
|
172
|
+
# so every method must be defined with def.
|
|
173
|
+
module Aggregations
|
|
174
|
+
# DISTINCT is Arel's only aggregate modifier, and only for count.
|
|
175
|
+
def count(distinct: false)
|
|
176
|
+
Aggregate.new(self, :count, distinct: distinct)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def sum
|
|
180
|
+
Aggregate.new(self, :sum)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def average
|
|
184
|
+
Aggregate.new(self, :average)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def maximum
|
|
188
|
+
Aggregate.new(self, :maximum)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def minimum
|
|
192
|
+
Aggregate.new(self, :minimum)
|
|
85
193
|
end
|
|
86
194
|
end
|
|
87
195
|
|
|
@@ -113,6 +221,15 @@ module ActiveRecord
|
|
|
113
221
|
else operand
|
|
114
222
|
end
|
|
115
223
|
end
|
|
224
|
+
|
|
225
|
+
# Resolves a function argument: a column or an expression as above,
|
|
226
|
+
# anything else a value to be quoted.
|
|
227
|
+
def to_arel_argument(arg, table)
|
|
228
|
+
case arg
|
|
229
|
+
when Node, Symbol then to_arel_operand(arg, table)
|
|
230
|
+
else Arel::Nodes.build_quoted(arg)
|
|
231
|
+
end
|
|
232
|
+
end
|
|
116
233
|
end
|
|
117
234
|
|
|
118
235
|
class Predicate < Node
|
|
@@ -131,6 +248,8 @@ module ActiveRecord
|
|
|
131
248
|
|
|
132
249
|
class Column < Node
|
|
133
250
|
include Predications
|
|
251
|
+
include Arithmetics
|
|
252
|
+
include Aggregations
|
|
134
253
|
|
|
135
254
|
attr_reader :table_name, :column_name
|
|
136
255
|
|
|
@@ -142,24 +261,55 @@ module ActiveRecord
|
|
|
142
261
|
def to_arel(_table)
|
|
143
262
|
Arel::Table.new(table_name)[column_name]
|
|
144
263
|
end
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
# Arithmetic on columns and expressions. Ruby's precedence puts these
|
|
267
|
+
# above the comparison operators, so :price * :quantity > 100 groups the
|
|
268
|
+
# way it reads.
|
|
269
|
+
class Arithmetic < Node
|
|
270
|
+
include Predications
|
|
271
|
+
include Arithmetics
|
|
272
|
+
include Aggregations
|
|
273
|
+
|
|
274
|
+
attr_reader :left, :operator, :right
|
|
145
275
|
|
|
146
|
-
|
|
147
|
-
|
|
276
|
+
def initialize(left, operator, right)
|
|
277
|
+
@left = left
|
|
278
|
+
@operator = operator
|
|
279
|
+
@right = right
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def to_arel(table)
|
|
283
|
+
to_arel_operand(left, table).
|
|
284
|
+
public_send(operator, to_arel_operand(right, table))
|
|
148
285
|
end
|
|
149
286
|
end
|
|
150
287
|
|
|
151
288
|
class Aggregate < Node
|
|
152
289
|
include Predications
|
|
290
|
+
include Arithmetics
|
|
153
291
|
|
|
154
|
-
attr_reader :operand, :function
|
|
292
|
+
attr_reader :operand, :function, :distinct
|
|
155
293
|
|
|
156
|
-
def initialize(operand, function)
|
|
294
|
+
def initialize(operand, function, distinct: false)
|
|
295
|
+
if distinct && function != :count
|
|
296
|
+
raise ArgumentError, "#{function} does not take distinct"
|
|
297
|
+
end
|
|
298
|
+
if distinct && operand == :*
|
|
299
|
+
raise ArgumentError, "count(:*) does not take distinct; name a column"
|
|
300
|
+
end
|
|
157
301
|
@operand = operand
|
|
158
302
|
@function = function
|
|
303
|
+
@distinct = distinct
|
|
159
304
|
end
|
|
160
305
|
|
|
161
306
|
def to_arel(table)
|
|
162
|
-
to_arel_operand(operand, table)
|
|
307
|
+
arel_operand = to_arel_operand(operand, table)
|
|
308
|
+
if function == :count
|
|
309
|
+
arel_operand.count(distinct)
|
|
310
|
+
else
|
|
311
|
+
arel_operand.public_send(function)
|
|
312
|
+
end
|
|
163
313
|
end
|
|
164
314
|
end
|
|
165
315
|
|
|
@@ -168,7 +318,7 @@ module ActiveRecord
|
|
|
168
318
|
|
|
169
319
|
def initialize(operand, alias_name)
|
|
170
320
|
@operand = operand
|
|
171
|
-
@alias_name = alias_name
|
|
321
|
+
@alias_name = AST.check_name(alias_name, ALIAS_NAME, "column alias")
|
|
172
322
|
end
|
|
173
323
|
|
|
174
324
|
def to_arel(table)
|
|
@@ -177,20 +327,33 @@ module ActiveRecord
|
|
|
177
327
|
end
|
|
178
328
|
|
|
179
329
|
class Ordering < Node
|
|
180
|
-
attr_reader :operand, :direction
|
|
330
|
+
attr_reader :operand, :direction, :nulls
|
|
181
331
|
|
|
182
|
-
def initialize(operand, direction)
|
|
332
|
+
def initialize(operand, direction, nulls = nil)
|
|
183
333
|
@operand = operand
|
|
184
334
|
@direction = direction
|
|
335
|
+
@nulls = nulls
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
# MySQL has no NULLS FIRST/LAST, but Arel emulates it there with a
|
|
339
|
+
# leading IS NULL ordering, so these are portable.
|
|
340
|
+
def nulls_first
|
|
341
|
+
Ordering.new(operand, direction, :nulls_first)
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
def nulls_last
|
|
345
|
+
Ordering.new(operand, direction, :nulls_last)
|
|
185
346
|
end
|
|
186
347
|
|
|
187
348
|
def to_arel(table)
|
|
188
|
-
to_arel_operand(operand, table).public_send(direction)
|
|
349
|
+
ordering = to_arel_operand(operand, table).public_send(direction)
|
|
350
|
+
nulls ? ordering.public_send(nulls) : ordering
|
|
189
351
|
end
|
|
190
352
|
end
|
|
191
353
|
|
|
192
354
|
class Function < Node
|
|
193
355
|
include Predications
|
|
356
|
+
include Arithmetics
|
|
194
357
|
|
|
195
358
|
attr_reader :name, :args
|
|
196
359
|
|
|
@@ -200,16 +363,80 @@ module ActiveRecord
|
|
|
200
363
|
end
|
|
201
364
|
|
|
202
365
|
def to_arel(table)
|
|
203
|
-
arel_args = args.map
|
|
204
|
-
case arg
|
|
205
|
-
when Node, Symbol then to_arel_operand(arg, table)
|
|
206
|
-
else Arel::Nodes.build_quoted(arg)
|
|
207
|
-
end
|
|
208
|
-
end
|
|
366
|
+
arel_args = args.map {|arg| to_arel_argument(arg, table) }
|
|
209
367
|
Arel::Nodes::NamedFunction.new(name, arel_args)
|
|
210
368
|
end
|
|
211
369
|
end
|
|
212
370
|
|
|
371
|
+
# EXTRACT(field FROM expr). The field is grammar rather than a value --
|
|
372
|
+
# a keyword the adapter reads bare -- so it has to be a plain name,
|
|
373
|
+
# which Arel upcases on the way out.
|
|
374
|
+
class Extract < Node
|
|
375
|
+
include Predications
|
|
376
|
+
include Arithmetics
|
|
377
|
+
|
|
378
|
+
attr_reader :field, :operand
|
|
379
|
+
|
|
380
|
+
def initialize(field, operand)
|
|
381
|
+
@field = AST.check_name(field, ALIAS_NAME, "extract field")
|
|
382
|
+
@operand = operand
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
def to_arel(table)
|
|
386
|
+
Arel::Nodes::Extract.new(to_arel_argument(operand, table), field.to_s)
|
|
387
|
+
end
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
# CAST(expr AS type). The type is grammar too, written into the SQL as
|
|
391
|
+
# given -- it is the adapter's own name for the type, and whether it
|
|
392
|
+
# exists is the database's to say -- so it has to look like one:
|
|
393
|
+
# a plain name, at most parenthesized with lengths.
|
|
394
|
+
class Cast < Node
|
|
395
|
+
include Predications
|
|
396
|
+
include Arithmetics
|
|
397
|
+
|
|
398
|
+
attr_reader :operand, :sql_type
|
|
399
|
+
|
|
400
|
+
def initialize(operand, sql_type)
|
|
401
|
+
@operand = operand
|
|
402
|
+
@sql_type = AST.check_name(sql_type, TYPE_NAME, "SQL type")
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
def to_arel(table)
|
|
406
|
+
Arel::Nodes::NamedFunction.new(
|
|
407
|
+
"CAST",
|
|
408
|
+
[Arel::Nodes::As.new(to_arel_argument(operand, table),
|
|
409
|
+
Arel::Nodes::SqlLiteral.new(sql_type.to_s))])
|
|
410
|
+
end
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
# CURRENT_TIMESTAMP and its relatives, what the SQL grammar calls a
|
|
414
|
+
# datetime value function. The grammar has them bare, and PostgreSQL
|
|
415
|
+
# and SQLite reject them written as calls, so unlike Function the name
|
|
416
|
+
# is emitted without parentheses. A precision is the one thing that
|
|
417
|
+
# does go into parentheses, and it is written into the SQL as given, so
|
|
418
|
+
# only an Integer is accepted.
|
|
419
|
+
class DatetimeValueFunction < Node
|
|
420
|
+
include Predications
|
|
421
|
+
include Arithmetics
|
|
422
|
+
|
|
423
|
+
attr_reader :name, :precision
|
|
424
|
+
|
|
425
|
+
def initialize(name, precision = nil)
|
|
426
|
+
unless precision.nil? || precision.is_a?(Integer)
|
|
427
|
+
raise ArgumentError,
|
|
428
|
+
"#{precision.inspect} is not an Integer precision"
|
|
429
|
+
end
|
|
430
|
+
@name = name
|
|
431
|
+
@precision = precision
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
def to_arel(_table)
|
|
435
|
+
Arel::Nodes::SqlLiteral.new(
|
|
436
|
+
precision ? "#{name}(#{precision})" : name)
|
|
437
|
+
end
|
|
438
|
+
end
|
|
439
|
+
|
|
213
440
|
# A plain SQL comparison. The value is passed through as it is, so a Range
|
|
214
441
|
# or an Array compares against a PostgreSQL range or array column, the way
|
|
215
442
|
# ActiveRecord's own force_equality? types do.
|
|
@@ -229,9 +456,27 @@ module ActiveRecord
|
|
|
229
456
|
|
|
230
457
|
def to_arel(table)
|
|
231
458
|
arel_column = to_arel_operand(column, table)
|
|
232
|
-
arel_value =
|
|
459
|
+
arel_value =
|
|
460
|
+
case value
|
|
461
|
+
when Node then value.to_arel(table)
|
|
462
|
+
when ActiveRecord::Relation then scalar_subquery(value)
|
|
463
|
+
else value
|
|
464
|
+
end
|
|
233
465
|
arel_column.public_send(OPERATOR_MAP.fetch(operator), arel_value)
|
|
234
466
|
end
|
|
467
|
+
|
|
468
|
+
private
|
|
469
|
+
|
|
470
|
+
# A relation compared against a column has to yield a single value, so
|
|
471
|
+
# unlike In there is no sensible default select list to fall back on.
|
|
472
|
+
def scalar_subquery(relation)
|
|
473
|
+
if relation.select_values.empty?
|
|
474
|
+
raise ArgumentError,
|
|
475
|
+
"#{operator} needs a subquery selecting one value; add a select"
|
|
476
|
+
end
|
|
477
|
+
relation = relation.send(:apply_join_dependency) if relation.eager_loading?
|
|
478
|
+
relation.arel
|
|
479
|
+
end
|
|
235
480
|
end
|
|
236
481
|
|
|
237
482
|
# IN for a list of values, BETWEEN for a range, IN (SELECT ...) for a
|
|
@@ -310,38 +555,80 @@ module ActiveRecord
|
|
|
310
555
|
inject {|left, right| Or.new(left, right) }
|
|
311
556
|
end
|
|
312
557
|
|
|
313
|
-
attr_reader :operand, :pattern, :escape
|
|
558
|
+
attr_reader :operand, :pattern, :escape, :case_sensitive
|
|
314
559
|
|
|
315
|
-
def initialize(operand, pattern, escape = nil)
|
|
560
|
+
def initialize(operand, pattern, escape = nil, case_sensitive: true)
|
|
316
561
|
@operand = operand
|
|
317
562
|
@pattern = pattern
|
|
318
563
|
@escape = escape
|
|
564
|
+
@case_sensitive = case_sensitive
|
|
319
565
|
end
|
|
320
566
|
|
|
321
567
|
def to_arel(table)
|
|
322
|
-
# Arel matches case-insensitively unless told otherwise, which
|
|
323
|
-
#
|
|
324
|
-
to_arel_operand(operand, table).matches(pattern, escape,
|
|
568
|
+
# Arel matches case-insensitively unless told otherwise, which is
|
|
569
|
+
# what picks ILIKE over LIKE on PostgreSQL.
|
|
570
|
+
to_arel_operand(operand, table).matches(pattern, escape, case_sensitive)
|
|
325
571
|
end
|
|
326
572
|
end
|
|
327
573
|
|
|
328
|
-
#
|
|
329
|
-
#
|
|
330
|
-
#
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
# expression works as the operand and no schema lookup is needed.
|
|
334
|
-
class Member < Predicate
|
|
335
|
-
attr_reader :operand, :elements
|
|
574
|
+
# IS [NOT] DISTINCT FROM, spelled IS / IS NOT on SQLite and <=> on
|
|
575
|
+
# MySQL. NULL compares as a value here, which is what separates these
|
|
576
|
+
# from = and <>.
|
|
577
|
+
class DistinctFrom < Predicate
|
|
578
|
+
attr_reader :operand, :value, :negated
|
|
336
579
|
|
|
337
|
-
def initialize(operand,
|
|
580
|
+
def initialize(operand, value, negated: false)
|
|
338
581
|
@operand = operand
|
|
339
|
-
@
|
|
582
|
+
@value = value
|
|
583
|
+
@negated = negated
|
|
340
584
|
end
|
|
341
585
|
|
|
342
586
|
def to_arel(table)
|
|
343
587
|
arel_operand = to_arel_operand(operand, table)
|
|
344
|
-
|
|
588
|
+
arel_value = value.is_a?(Node) ? value.to_arel(table) : value
|
|
589
|
+
if negated
|
|
590
|
+
arel_operand.is_distinct_from(arel_value)
|
|
591
|
+
else
|
|
592
|
+
arel_operand.is_not_distinct_from(arel_value)
|
|
593
|
+
end
|
|
594
|
+
end
|
|
595
|
+
end
|
|
596
|
+
|
|
597
|
+
# Comparisons against a PostgreSQL array column, named after the Ruby
|
|
598
|
+
# methods that mean the same thing: member? is Enumerable's element
|
|
599
|
+
# test, superset? and subset? are Set's whole-array containment, and
|
|
600
|
+
# intersect? is Array's "any element in common". Each name maps to one
|
|
601
|
+
# operator; the elements are rendered as an array literal, which
|
|
602
|
+
# PostgreSQL coerces to the column's element type, so any expression
|
|
603
|
+
# works as the operand and no schema lookup is needed.
|
|
604
|
+
class ArrayPredicate < Predicate
|
|
605
|
+
# The whole-array comparisons take the collection kinds their
|
|
606
|
+
# namesakes compare against: an Array, or a Set for the Set methods.
|
|
607
|
+
def self.elements(arg, method_name)
|
|
608
|
+
case arg
|
|
609
|
+
when ::Array then arg
|
|
610
|
+
when ::Set then arg.to_a
|
|
611
|
+
else
|
|
612
|
+
raise ArgumentError, "#{method_name} takes an Array or Set of elements"
|
|
613
|
+
end
|
|
614
|
+
end
|
|
615
|
+
|
|
616
|
+
attr_reader :operand, :operator, :elements
|
|
617
|
+
|
|
618
|
+
def initialize(operand, operator, elements)
|
|
619
|
+
@operand = operand
|
|
620
|
+
@operator = operator
|
|
621
|
+
@elements = elements
|
|
622
|
+
end
|
|
623
|
+
|
|
624
|
+
def to_arel(table)
|
|
625
|
+
arel_operand = to_arel_operand(operand, table)
|
|
626
|
+
quoted = Arel::Nodes.build_quoted(array_literal)
|
|
627
|
+
case operator
|
|
628
|
+
when :"@>" then Arel::Nodes::Contains.new(arel_operand, quoted)
|
|
629
|
+
when :"&&" then Arel::Nodes::Overlaps.new(arel_operand, quoted)
|
|
630
|
+
else Arel::Nodes::InfixOperation.new(operator, arel_operand, quoted)
|
|
631
|
+
end
|
|
345
632
|
end
|
|
346
633
|
|
|
347
634
|
private
|