Module: ActiveRecord::Refined::AST::Predications

Overview

The conditions a column or an expression can be put in. Every one gives back a condition that combines with &, | and !, and the comparisons quote a Ruby value on the right the way Active Record does, or take a column, an expression or a subquery there.

Predicate builders shared by symbols, qualified columns and expressions. Imported into the Symbol refinement with Refinement#import_methods, so every method must be defined with def.

Examples:

Author.where { :age.between?(20, 40) & :name.like?("A%") }
Author.where { :id.in?(Post.select(:author_id)) }

Instance Method Summary collapse

Instance Method Details

#!=(other) ⇒ AST::Predicate

!=; nil is refused, as with ==.

Returns:



76
77
78
79
80
81
# File 'lib/active_record/refined/ast.rb', line 76

def !=(other)
  if other.nil?
    raise ArgumentError, "!= does not take nil; use !null? instead"
  end
  Comparison.new(self, :!=, other)
end

#!~(pattern) ⇒ AST::Predicate

The negated regular expression match.

Returns:



118
119
120
# File 'lib/active_record/refined/ast.rb', line 118

def !~(pattern)
  Match.new(self, pattern, negated: true)
end

#<(other) ⇒ AST::Predicate

<.

Returns:



97
98
99
# File 'lib/active_record/refined/ast.rb', line 97

def <(other)
  Comparison.new(self, :<, other)
end

#<=(other) ⇒ AST::Predicate

<=.

Returns:



103
104
105
# File 'lib/active_record/refined/ast.rb', line 103

def <=(other)
  Comparison.new(self, :<=, other)
end

#==(other) ⇒ AST::Predicate

=. A value, a column, an expression or a scalar subquery on the right; nil is refused, since = NULL is never true -- #null? is the spelling.

and != mean SQL = and <>, and = NULL is never true there, so nil

is rejected rather than silently rewritten to IS NULL. null? builds its node directly and stays clear of this check.

Examples:

Author.where { :name == "alice" }
Author.where { :age == Author.select { max(:age) } }

Returns:



67
68
69
70
71
72
# File 'lib/active_record/refined/ast.rb', line 67

def ==(other)
  if other.nil?
    raise ArgumentError, "== does not take nil; use null? instead"
  end
  Comparison.new(self, :==, other)
end

#=~(pattern) ⇒ AST::Predicate

A regular expression match: ~ on PostgreSQL, REGEXP on MySQL, and what the adapter has elsewhere. A Ruby Regexp's source is the pattern.

Examples:

Author.where { :name =~ /^A/ }

Parameters:

  • pattern (Regexp, String)

Returns:



112
113
114
# File 'lib/active_record/refined/ast.rb', line 112

def =~(pattern)
  Match.new(self, pattern)
end

#>(other) ⇒ AST::Predicate

>.

Returns:



85
86
87
# File 'lib/active_record/refined/ast.rb', line 85

def >(other)
  Comparison.new(self, :>, other)
end

#>=(other) ⇒ AST::Predicate

>=.

Returns:



91
92
93
# File 'lib/active_record/refined/ast.rb', line 91

def >=(other)
  Comparison.new(self, :>=, other)
end

#between?(min, max) ⇒ AST::Predicate

BETWEEN min AND max, with either end a value, a column or an expression. Not min..max: an endpoint may be an expression, which Range would refuse to hold, since expressions do not compare among themselves.

Examples:

Author.where { :age.between?(20, 40) }

Returns:



196
197
198
# File 'lib/active_record/refined/ast.rb', line 196

def between?(min, max)
  In.new(self, In::QuotedRange.new(min, max, false))
end

#bury(*path, value) ⇒ AST::JsonSet

The document with a value set at a path, as #dig reads one; an expression, for update_all to write back. What dig reads, bury sets: the last argument is the value and the rest are the path to it. The document comes back changed rather than being written anywhere, which update_all is for.

Examples:

Doc.update_all { { meta: :meta.bury(:author, :name, "alice") } }

Returns:



386
387
388
# File 'lib/active_record/refined/ast.rb', line 386

def bury(*path, value)
  JsonSet.new(self, path, value)
end

#casecmp?(value) ⇒ AST::Predicate

Case-insensitive equality: LOWER(column) = LOWER(value). Case-insensitive equality, folded on both sides rather than left to the collation, so it means the same thing on every adapter.

Examples:

Author.where { :name.casecmp?("Alice") }

Returns:



252
253
254
255
256
257
258
# File 'lib/active_record/refined/ast.rb', line 252

def casecmp?(value)
  if value.nil?
    raise ArgumentError, "casecmp? does not take nil; use null? instead"
  end
  Comparison.new(Function.new("LOWER", [self]), :==,
                 Function.new("LOWER", [value]))
end

#contains?(value) ⇒ AST::Predicate

Whether the document contains the Ruby document given, which SQL calls containment: @> on PostgreSQL, JSON_CONTAINS on MySQL. SQLite and MariaDB have none. Whether the document holds what is given, which SQL calls containment. SQLite has no equivalent.

Examples:

Doc.where { :meta.contains?(author: { name: "alice" }) }

Returns:



397
398
399
# File 'lib/active_record/refined/ast.rb', line 397

def contains?(value)
  JsonContains.new(self, value)
end

#dig(*path) ⇒ AST::JsonPath

The JSON at a path into a JSON column, still JSON -- to be dug further, compared with a Ruby value, or asked #key? and the rest. A string or a symbol steps into an object, an integer into an array. #> on PostgreSQL, JSON_EXTRACT on MySQL, -> on SQLite. Reading inside a JSON document, by the name of what Hash does. A string or symbol steps into an object, an integer into an array, and what comes back is still JSON, the way Hash#dig hands back the structure itself -- for a document to be dug into further or asked the JSON questions. dig_text gives the value as text instead, which is what a comparison wants.

Examples:

Doc.select { :meta.dig(:author).as(:author) }
Doc.where { :meta.dig(:author).key?(:name) }

Returns:



354
355
356
# File 'lib/active_record/refined/ast.rb', line 354

def dig(*path)
  JsonPath.new(self, path)
end

#dig_text(*path) ⇒ AST::JsonPath

The value at a path as text, which is what a comparison against a string wants where the JSON type would not do: #>> on PostgreSQL, JSON_UNQUOTE(JSON_EXTRACT(...)) on MySQL, ->> on SQLite.

Examples:

Doc.where { :meta.dig_text(:author, :name) == "alice" }

Returns:



362
363
364
# File 'lib/active_record/refined/ast.rb', line 362

def dig_text(*path)
  JsonPath.new(self, path, json_value: false)
end

#distinct_from?(value) ⇒ AST::Predicate

IS DISTINCT FROM: != that treats NULL as a value. IS NOT on SQLite, NOT <=> on MySQL. Null-safe comparison: unlike = and <>, these treat NULL as a value, so not_distinct_from? is the one equality that may take nil.

Returns:



265
266
267
# File 'lib/active_record/refined/ast.rb', line 265

def distinct_from?(value)
  DistinctFrom.new(self, value, negated: true)
end

#end_with?(*suffixes) ⇒ AST::Predicate

LIKE '%suffix', escaped as #start_with? escapes.

Returns:



290
291
292
293
294
295
# File 'lib/active_record/refined/ast.rb', line 290

def end_with?(*suffixes)
  if suffixes.empty?
    raise ArgumentError, "end_with? needs at least one suffix"
  end
  Like.any(self, suffixes.map { |suffix| "%#{Like.escape(suffix)}" })
end

#except(*keys) ⇒ AST::JsonExcept

The document without the keys given, as Hash#except gives it; an expression, for update_all to write back. Keys taken out of a JSON document, by the name of what Hash does, and taking keys as Hash#except takes them. Like bury it gives back the document changed rather than writing it anywhere.

Examples:

Doc.update_all { { meta: :meta.except(:draft) } }

Returns:



374
375
376
# File 'lib/active_record/refined/ast.rb', line 374

def except(*keys)
  JsonExcept.new(self, keys)
end

#false?AST::Predicate

IS FALSE.

Returns:



163
164
165
# File 'lib/active_record/refined/ast.rb', line 163

def false?
  TruthValue.new(self, false)
end

#ilike?(pattern) ⇒ AST::Predicate

A case-insensitive LIKE: ILIKE on PostgreSQL, and LIKE over both sides lower-cased elsewhere.

Returns:



235
236
237
# File 'lib/active_record/refined/ast.rb', line 235

def ilike?(pattern)
  Like.new(self, pattern, nil, case_sensitive: false)
end

#in?(values) ⇒ AST::Predicate

IN (...): an array of values, a range, or a relation as a subquery.

Examples:

Author.where { :country.in?(%w[JP US]) }
Author.where { :id.in?(Post.select(:author_id)) }

Parameters:

  • values (Array, Range, ActiveRecord::Relation)

Returns:



179
180
181
# File 'lib/active_record/refined/ast.rb', line 179

def in?(values)
  In.new(self, values)
end

#include?(substring) ⇒ AST::Predicate

LIKE '%substring%', escaped as #start_with? escapes.

Examples:

Post.where { :title.include?("ruby") }

Returns:



301
302
303
# File 'lib/active_record/refined/ast.rb', line 301

def include?(substring)
  Like.new(self, "%#{Like.escape(substring)}%", Like::ESCAPE)
end

#intersect?(elements) ⇒ AST::Predicate

Whether an array column and the elements given share any: &&.

Examples:

Post.where { :tags.intersect?(%w[ruby sql]) }

Returns:



338
339
340
# File 'lib/active_record/refined/ast.rb', line 338

def intersect?(elements)
  ArrayPredicate.new(self, :"&&", ArrayPredicate.elements(elements, "intersect?"))
end

#key?(key) ⇒ AST::Predicate

Whether the object has the key, as Hash#key? asks. Whether the key is there at all, as Hash#key? asks. Hash has has_key? too; one name is enough, and this is the one Ruby's own style prefers.

Examples:

Doc.where { :meta.key?(:author) }

Returns:



409
410
411
# File 'lib/active_record/refined/ast.rb', line 409

def key?(key)
  JsonHasKey.new(self, key)
end

#keysAST::JsonKeys

The keys of the object as a JSON array, as Hash#keys gives them. Oracle has none. The keys of the document, as Hash#keys gives them: a JSON array.

Returns:



417
418
419
# File 'lib/active_record/refined/ast.rb', line 417

def keys
  JsonKeys.new(self)
end

#like?(pattern) ⇒ AST::Predicate

LIKE pattern, the pattern as written: % and _ are its wildcards.

Examples:

Author.where { :name.like?("A%") }

Parameters:

  • pattern (String)

Returns:



223
224
225
# File 'lib/active_record/refined/ast.rb', line 223

def like?(pattern)
  Like.new(self, pattern)
end

#member?(element) ⇒ AST::Predicate

Whether a PostgreSQL array column holds the element: @> ARRAY[element]. The array comparisons carry the meaning of their Ruby namesakes. member? is Enumerable's element test, so an Array argument is rejected rather than quietly meaning something Array#member? does not; whole-array comparisons go by the Set and Array names.

Examples:

Post.where { :tags.member?("ruby") }

Returns:



314
315
316
317
318
319
320
# File 'lib/active_record/refined/ast.rb', line 314

def member?(element)
  if element.is_a?(::Array) || element.is_a?(::Set)
    raise ArgumentError,
      "member? takes a single element; use superset? to require every element"
  end
  ArrayPredicate.new(self, :"@>", [element])
end

#not_between?(min, max) ⇒ AST::Predicate

NOT BETWEEN min AND max.

Returns:



202
203
204
# File 'lib/active_record/refined/ast.rb', line 202

def not_between?(min, max)
  In.new(self, In::QuotedRange.new(min, max, false), negated: true)
end

#not_distinct_from?(value) ⇒ AST::Predicate

IS NOT DISTINCT FROM: = that treats NULL as a value, so this is the one equality that takes nil.

Examples:

Author.where { :country.not_distinct_from?(nil) }

Returns:



273
274
275
# File 'lib/active_record/refined/ast.rb', line 273

def not_distinct_from?(value)
  DistinctFrom.new(self, value)
end

#not_false?AST::Predicate

IS NOT FALSE.

Returns:



169
170
171
# File 'lib/active_record/refined/ast.rb', line 169

def not_false?
  TruthValue.new(self, false, negated: true)
end

#not_ilike?(pattern) ⇒ AST::Predicate

The negated case-insensitive LIKE.

Returns:



241
242
243
# File 'lib/active_record/refined/ast.rb', line 241

def not_ilike?(pattern)
  Like.new(self, pattern, nil, case_sensitive: false, negated: true)
end

#not_in?(values) ⇒ AST::Predicate

NOT IN (...).

Returns:



185
186
187
# File 'lib/active_record/refined/ast.rb', line 185

def not_in?(values)
  In.new(self, values, negated: true)
end

#not_like?(pattern) ⇒ AST::Predicate

NOT LIKE pattern.

Returns:



229
230
231
# File 'lib/active_record/refined/ast.rb', line 229

def not_like?(pattern)
  Like.new(self, pattern, negated: true)
end

#not_null?AST::Predicate

IS NOT NULL.

Returns:



137
138
139
# File 'lib/active_record/refined/ast.rb', line 137

def not_null?
  Comparison.new(self, :!=, nil)
end

#not_true?AST::Predicate

IS NOT TRUE: keeps the NULL rows that !(:flag == true) drops.

Returns:



157
158
159
# File 'lib/active_record/refined/ast.rb', line 157

def not_true?
  TruthValue.new(self, true, negated: true)
end

#null?AST::Predicate

IS NULL. ! negates any predicate, so these are here for the four that SQL spells for itself: IS NOT NULL rather than NOT (... IS NULL), and likewise NOT IN and NOT LIKE. They mean the same thing either way, including when the column is NULL; what they save is the reading.

Examples:

Author.where { :country.null? }

Returns:



131
132
133
# File 'lib/active_record/refined/ast.rb', line 131

def null?
  Comparison.new(self, :==, nil)
end

#start_with?(*prefixes) ⇒ AST::Predicate

LIKE 'prefix%', the prefix escaped so that a % or _ in it is itself; several prefixes are ORed.

Examples:

Author.where { :name.start_with?("A", "B") }

Returns:



281
282
283
284
285
286
# File 'lib/active_record/refined/ast.rb', line 281

def start_with?(*prefixes)
  if prefixes.empty?
    raise ArgumentError, "start_with? needs at least one prefix"
  end
  Like.any(self, prefixes.map { |prefix| "#{Like.escape(prefix)}%" })
end

#subset?(elements) ⇒ AST::Predicate

Whether every element of an array column is among those given: <@.

Returns:



330
331
332
# File 'lib/active_record/refined/ast.rb', line 330

def subset?(elements)
  ArrayPredicate.new(self, :"<@", ArrayPredicate.elements(elements, "subset?"))
end

#superset?(elements) ⇒ AST::Predicate

Whether an array column holds every element given: @>.

Returns:



324
325
326
# File 'lib/active_record/refined/ast.rb', line 324

def superset?(elements)
  ArrayPredicate.new(self, :"@>", ArrayPredicate.elements(elements, "superset?"))
end

#true?AST::Predicate

IS TRUE: true of the rows where the boolean is true, false where it is false or NULL -- where == true would be NULL. IS TRUE and IS FALSE differ from a comparison against the literal in what they make of NULL: flag = TRUE is itself NULL there, and a NULL predicate selects nothing, while these two answer false. So the difference shows in the negations: not_true? keeps the NULL rows that !(:flag == true) drops.

Examples:

Post.where { :published.true? }

Returns:



151
152
153
# File 'lib/active_record/refined/ast.rb', line 151

def true?
  TruthValue.new(self, true)
end

#when(value = nil, &block) ⇒ AST::Case::When

CASE column WHEN value THEN ...: a CASE with this as the operand, each when a value it is compared against, followed by then and finally else. CASE with this as the operand, compared against each when: :age.when(10).then(1).else(0). The other shape, where each when carries its own condition, starts at case_when.

Examples:

Author.select { :country.when("JP").then("Japan").else("elsewhere").as(:where) }

Returns:

  • (AST::Case::When)


214
215
216
# File 'lib/active_record/refined/ast.rb', line 214

def when(value = nil, &block)
  Case.new(self).when(value, &block)
end