Class: ROM::SQL::Attribute

Inherits:
Attribute
  • Object
show all
Extended by:
Dry::Core::Cache
Defined in:
lib/rom/sql/attribute.rb,
lib/rom/sql/extensions/postgres/types/json.rb,
lib/rom/sql/extensions/postgres/types/array.rb,
lib/rom/sql/extensions/postgres/types/ltree.rb

Overview

Extended schema attributes tailored for SQL databases

Constant Summary collapse

OPERATORS =
%i[>= <= > <].freeze
NONSTANDARD_EQUALITY_VALUES =
[true, false, nil].freeze
META_KEYS =
%i[index foreign_key target sql_expr qualified].freeze
QualifyError =

Error raised when an attribute cannot be qualified

Class.new(StandardError)

Instance Method Summary collapse

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(meth, *args, &block) ⇒ Object (private)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Delegate to sql expression if it responds to a given method



374
375
376
377
378
379
380
381
382
383
384
# File 'lib/rom/sql/attribute.rb', line 374

def method_missing(meth, *args, &block)
  if OPERATORS.include?(meth)
    __cmp__(meth, args[0])
  elsif extensions.key?(meth)
    extensions[meth].(type, sql_expr, *args, &block)
  elsif sql_expr.respond_to?(meth)
    meta(sql_expr: sql_expr.__send__(meth, *args, &block))
  else
    super
  end
end

Instance Method Details

#!Attribute

Negate the attribute's sql expression

Examples:

users.where(!users[:id].is(1))

Returns:



209
210
211
# File 'lib/rom/sql/attribute.rb', line 209

def !
  ~self
end

#+(value) ⇒ SQL::Attribute<Types::LTree>

Concatenate two LTree values Translates to ||

Examples:

people.select { (ltree_tags + ROM::Types::Values::TreePath.new('Moscu')).as(:ltree_tags) }.where { name.is('Jade Doe') }
people.select { (ltree_tags + 'Moscu').as(:ltree_tags) }.where { name.is('Jade Doe') }

Parameters:

  • keys (LTree, String)

Returns:



# File 'lib/rom/sql/extensions/postgres/types/json.rb', line 113

#=~(other) ⇒ Attribute

Return a new attribute with an equality expression

Examples:

users.where { email =~ 1 }

Returns:



183
184
185
# File 'lib/rom/sql/attribute.rb', line 183

def =~(other)
  meta(sql_expr: sql_expr =~ binary_operation_arg(other))
end

#any(value) ⇒ SQL::Attribute<Types::Bool>

Check whether the array includes a value Translates to the ANY operator

Parameters:

  • value (Object)

Returns:



# File 'lib/rom/sql/extensions/postgres/types/array.rb', line 21

#ascendant(value) ⇒ SQL::Attribute<Types::Bool>

Check whether the LTree is a ascendant of the LTree value Translates to the @> operator

Examples:

people.select(:name).where { ltree_tags.ascendant('Bottom.Cities') }

Parameters:

  • value (String)

Returns:



# File 'lib/rom/sql/extensions/postgres/types/ltree.rb', line 83

#canonicalObject

Return a new attribute in its canonical form



41
42
43
44
45
46
47
# File 'lib/rom/sql/attribute.rb', line 41

def canonical
  if aliased?
    with(alias: nil).meta(sql_expr: nil)
  else
    self
  end
end

#case(mapping) ⇒ SQL::Attribute

Build a case expression based on attribute. See SQL::Function#case when you don't have a specific expression after the CASE keyword. Pass the :else keyword to provide the catch-all case, it's mandatory because of the Sequel's API used underneath.

Examples:

users.select_append { id.case(1 => `'first'`, else: `'other'`).as(:first_or_not) }

Parameters:

  • mapping (Hash)

    mapping between SQL expressions

Returns:



351
352
353
354
355
356
357
358
359
360
# File 'lib/rom/sql/attribute.rb', line 351

def case(mapping)
  mapping = mapping.dup
  otherwise = mapping.delete(:else) do
    raise ArgumentError, 'provide the default case using the :else keyword'
  end

  type = mapping.values[0].type

  Attribute[type].meta(sql_expr: ::Sequel.case(mapping, otherwise, self))
end

#concat(other, sep = ' ') ⇒ SQL::Function

Create a CONCAT function from the attribute

Examples:

with default separator (' ')

users[:id].concat(users[:name])

with custom separator

users[:id].concat(users[:name], '-')

Parameters:

Returns:



268
269
270
# File 'lib/rom/sql/attribute.rb', line 268

def concat(other, sep = ' ')
  Function.new(type).concat(self, sep, other)
end

#contain(other) ⇒ SQL::Attribute<Types::Bool>

Check whether the array includes another array Translates to the @> operator

Parameters:

  • other (Array)

Returns:



# File 'lib/rom/sql/extensions/postgres/types/json.rb', line 2

#contain_ancestor(value) ⇒ SQL::Attribute<Types::PG::Bool>

Does LTree array contain an ancestor of ltree Translates to @>

Examples:

people.select(:name).where { parents_tags.contain_ancestor('Top.Building.EmpireState.381')}

Parameters:

  • value (String)

Returns:



# File 'lib/rom/sql/extensions/postgres/types/ltree.rb', line 123

#contain_any_ltextquery(value) ⇒ SQL::Attribute<Types::Bool>

Does LTree array contain any path matching ltxtquery Translates to @

Examples:

people.select(:name).where { parents_tags.contain_any_ltextquery('Parks')}

Parameters:

  • value (String)

Returns:



# File 'lib/rom/sql/extensions/postgres/types/ltree.rb', line 110

#contain_ascendant(value) ⇒ SQL::Attribute<Types::Bool>

Check whether the LTree is a ascendant of the LTree values Translates to the @> operator

Examples:

people.select(:name).where { ltree_tags.contain_ascendant(['Bottom.Cities']) }
people.select(:name).where { ltree_tags.contain_ascendant('Bottom.Cities, Bottom.Parks') }

Parameters:

  • value (Array<String>, String)

Returns:



# File 'lib/rom/sql/extensions/postgres/types/ltree.rb', line 69

#contain_descendant(value) ⇒ SQL::Attribute<Types::PG::Bool>

Does LTree array contain an descendant of ltree Translates to <@

Examples:

people.select(:name).where { parents_tags.contain_descendant('Top.Building.EmpireState.381')}

Parameters:

  • value (String)

Returns:



# File 'lib/rom/sql/extensions/postgres/types/ltree.rb', line 42

#contained_by(other) ⇒ SQL::Attribute<Types::Bool>

Check whether the array is contained by another array Translates to the <@ operator

Parameters:

  • other (Array)

Returns:



# File 'lib/rom/sql/extensions/postgres/types/json.rb', line 17

#delete(*path) ⇒ SQL::Attribute<Types::PG::JSONB>

Deletes the specified value by key, index, or path Translates to - or #- depending on the number of arguments

Examples:

people.select { data.delete('age').as(:data_without_age) }
people.select { fields.delete(0).as(:fields_without_first) }
people.select { fields.delete(-1).as(:fields_without_last) }
people.select { data.delete('deeply', 'nested', 'value').as(:data) }
people.select { fields.delete('0', 'name').as(:data) }

Parameters:

  • path (Array<String>)

Returns:



# File 'lib/rom/sql/extensions/postgres/types/json.rb', line 118

#descendant(value) ⇒ SQL::Attribute<Types::Bool>

Check whether the LTree is a descendant of the LTree value Translates to the <@ operator

Examples:

people.select(:name).where { ltree_tags.descendant('Bottom.Cities') }

Parameters:

  • value (String)

Returns:



# File 'lib/rom/sql/extensions/postgres/types/ltree.rb', line 56

#find_ancestor(value) ⇒ SQL::Attribute<Types::PG::LTree>

Return first LTree array entry that is an ancestor of ltree, NULL if none Translates to ?@>

Examples:

people.select(:name).where { parents_tags.find_ancestor('Left.Parks').not(nil)}

Parameters:

  • value (String)

Returns:



# File 'lib/rom/sql/extensions/postgres/types/ltree.rb', line 149

#find_descendant(value) ⇒ SQL::Attribute<Types::PG::LTree>

Return first LTree array entry that is an descendant of ltree, NULL if none Translates to ?<@

Examples:

people.select(:name).where { parents_tags.find_descendant('Left.Parks').not(nil)}

Parameters:

  • value (String)

Returns:



# File 'lib/rom/sql/extensions/postgres/types/ltree.rb', line 162

#foreign_keySQL::Attribute

Return a new attribute marked as a FK

Returns:



126
127
128
# File 'lib/rom/sql/attribute.rb', line 126

def foreign_key
  meta(foreign_key: true)
end

#func(&block) ⇒ SQL::Function

Create a function DSL from the attribute

Examples:

users[:id].func { integer::count(id).as(:count) }

Returns:



251
252
253
# File 'lib/rom/sql/attribute.rb', line 251

def func(&block)
  ProjectionDSL.new(name => self).call(&block).first
end

#get(idx) ⇒ SQL::Attribute

Get element by index (PG uses 1-based indexing)

Parameters:

  • idx (Integer)

Returns:



# File 'lib/rom/sql/extensions/postgres/types/json.rb', line 30

#get_text(*path) ⇒ SQL::Attribute<Types::String>

Extract the JSON value as text using at the specified path Translates to ->> or #>> depending on the number of arguments

Examples:

people.select { data.get('age').as(:person_age) }
people.select { fields.get(0).as(:first_field) }
people.select { fields.get('0', 'value').as(:first_field_value) }

Parameters:

  • path (Array<Integer>, Array<String>)

    Path to extract

Returns:



# File 'lib/rom/sql/extensions/postgres/types/json.rb', line 45

#has_all_keys(*keys) ⇒ SQL::Attribute<Types::Bool>

Does the JSON value have all the specified top-level keys Translates to ?&

Examples:

people.where { data.has_all_keys('age', 'height') }

Parameters:

  • keys (Array<String>)

Returns:



# File 'lib/rom/sql/extensions/postgres/types/json.rb', line 86

#has_any_key(*keys) ⇒ SQL::Attribute<Types::Bool>

Does the JSON value have any of the specified top-level keys Translates to ?|

Examples:

people.where { data.has_any_key('age', 'height') }

Parameters:

  • keys (Array<String>)

Returns:



# File 'lib/rom/sql/extensions/postgres/types/json.rb', line 73

#has_key(key) ⇒ SQL::Attribute<Types::Bool>

Does the JSON value have the specified top-level key Translates to ?

Examples:

people.where { data.has_key('age') }

Parameters:

  • key (String)

Returns:



# File 'lib/rom/sql/extensions/postgres/types/json.rb', line 60

#in(*args) ⇒ Object

Return a boolean expression with an inclusion test

If the single argument passed to the method is a Range object then the resulting expression will restrict the attribute value with range's bounds. Upper bound condition will be inclusive/non-inclusive depending on the range type.

If more than one argument is passed to the method or the first argument is not Range then the result will be a simple IN check.

Examples:

users.where { id.in(1..100) | created_at(((Time.now - 86400)..Time.now)) }
users.where { id.in(1, 2, 3) }
users.where(users[:id].in(1, 2, 3))

Parameters:

  • args (Array<Object>)

    A range or a list of values for an inclusion check



231
232
233
234
235
236
237
238
239
240
241
# File 'lib/rom/sql/attribute.rb', line 231

def in(*args)
  if args.first.is_a?(Range)
    range = args.first
    lower_cond = __cmp__(:>=, range.begin)
    upper_cond = __cmp__(range.exclude_end? ? :< : :<=, range.end)

    Sequel::SQL::BooleanExpression.new(:AND, lower_cond, upper_cond)
  else
    __cmp__(:IN, args)
  end
end

#indexedObject

Returns a new attribute marked as indexed



307
308
309
# File 'lib/rom/sql/attribute.rb', line 307

def indexed
  meta(index: true)
end

#indexed?Boolean

Returns:

  • (Boolean)


300
301
302
# File 'lib/rom/sql/attribute.rb', line 300

def indexed?
  meta[:index].equal?(true)
end

#is(other) ⇒ Object

Return a boolean expression with an equality operator

Examples:

users.where { id.is(1) }

users.where(users[:id].is(1))

Parameters:

  • other (Object)

    Any SQL-compatible object type



171
172
173
# File 'lib/rom/sql/attribute.rb', line 171

def is(other)
  self =~ other
end

#join(delimiter, null_repr) ⇒ SQL::Attribute<Types::String>

Convert the array to a string by joining values with a delimiter (empty stirng by default) and optional filler for NULL values Translates to an array_to_string call

Parameters:

  • delimiter (Object)
  • null_repr (Object)

Returns:



# File 'lib/rom/sql/extensions/postgres/types/array.rb', line 67

#joinedSQL::Attribute

Return a new attribute marked as joined

Whenever you join two schemas, the right schema's attribute will be marked as joined using this method

Returns:



78
79
80
# File 'lib/rom/sql/attribute.rb', line 78

def joined
  meta(joined: true)
end

#joined?Boolean

Return if an attribute was used in a join

Examples:

schema = users.schema.join(tasks.schema)

schema[:id, :tasks].joined?
# => true

Returns:

  • (Boolean)


93
94
95
# File 'lib/rom/sql/attribute.rb', line 93

def joined?
  meta[:joined].equal?(true)
end

#lengthSQL::Attribute<Types::Integer>

Return array size

Returns:



# File 'lib/rom/sql/extensions/postgres/types/array.rb', line 41

#match(value) ⇒ SQL::Attribute<Types::Bool>

Check whether the LTree match a lquery value Translates to the ~ operator

Examples:

people.select(:name).where { ltree_tags.match('Bottom.Cities') }

Parameters:

  • value (String)

Returns:



# File 'lib/rom/sql/extensions/postgres/types/ltree.rb', line 2

#match_any(value) ⇒ SQL::Attribute<Types::Bool>

Check whether the LTree match any of the lquery values Translates to the ? operator

Examples:

people.select(:name).where { ltree_tags.match_any(['Bottom', 'Bottom.Cities.*']) }
people.select(:name).where { ltree_tags.match_any('Bottom,Bottom.Cities.*') }

Parameters:

  • value (Array, String)

Returns:



# File 'lib/rom/sql/extensions/postgres/types/ltree.rb', line 15

#match_any_lquery(value) ⇒ SQL::Attribute<Types::PG::LTree>

Return first LTree array entry that matches lquery, NULL if none Translates to ?~

Examples:

people.select(:name).where { parents_tags.match_any_lquery('Right.*').not(nil)}

Parameters:

  • value (String)

Returns:



# File 'lib/rom/sql/extensions/postgres/types/ltree.rb', line 175

#match_any_ltextquery(value) ⇒ SQL::Attribute<Types::PG::LTree>

Return first LTree array entry that matches ltextquery, NULL if none Translates to ?@

Examples:

people.select(:name).where { parents_tags.match_any_ltextquery('EmpireState').not(nil)}

Parameters:

  • value (String)

Returns:



# File 'lib/rom/sql/extensions/postgres/types/ltree.rb', line 188

#match_ltextquery(value) ⇒ SQL::Attribute<Types::Bool>

Check whether the LTree match a ltextquery Translates to the @ operator

Examples:

people.select(:name).where { ltree_tags.match_ltextquery('Countries & Brasil') }

Parameters:

  • value (String)

Returns:



# File 'lib/rom/sql/extensions/postgres/types/ltree.rb', line 29

#merge(value) ⇒ SQL::Attribute<Types::PG::JSONB>

Concatenate two JSON values Translates to ||

Examples:

people.select { data.merge(fetched_at: Time.now).as(:data) }
people.select { (fields + [name: 'height', value: 165]).as(:fields) }

Parameters:

  • value (Hash, Array)

Returns:



# File 'lib/rom/sql/extensions/postgres/types/json.rb', line 99

#not(other) ⇒ Object

Return a boolean expression with a negated equality operator

Examples:

users.where { id.not(1) }

users.where(users[:id].not(1))

Parameters:

  • other (Object)

    Any SQL-compatible object type



197
198
199
# File 'lib/rom/sql/attribute.rb', line 197

def not(other)
  !is(other)
end

#overlaps(other) ⇒ SQL::Attribute<Types::Bool>

Check whether the arrays have common values Translates to &&

Parameters:

  • other (Array)

Returns:



# File 'lib/rom/sql/extensions/postgres/types/array.rb', line 48

#qualifiable?Boolean

Return if an attribute is qualifiable

Returns:

  • (Boolean)


117
118
119
# File 'lib/rom/sql/attribute.rb', line 117

def qualifiable?
  !source.nil?
end

#qualified(table_alias = nil) ⇒ SQL::Attribute

Return a new attribute marked as qualified

Examples:

users[:id].aliased(:user_id)

Returns:



57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/rom/sql/attribute.rb', line 57

def qualified(table_alias = nil)
  return self if qualified? && table_alias.nil?
  return meta(qualified: false) unless qualifiable?

  case sql_expr
  when Sequel::SQL::AliasedExpression, Sequel::SQL::Identifier, Sequel::SQL::QualifiedIdentifier
    attr = meta(qualified: table_alias || true)
    attr.meta(sql_expr: attr.to_sql_name)
  else
    raise QualifyError, "can't qualify #{name.inspect} (#{sql_expr.inspect})"
  end
end

#qualified?Boolean

Return if an attribute type is qualified

Examples:

id = users[:id].qualify

id.qualified?
# => true

Returns:

  • (Boolean)


108
109
110
# File 'lib/rom/sql/attribute.rb', line 108

def qualified?
  meta[:qualified].equal?(true) || meta[:qualified].is_a?(Symbol)
end

#remove_value(value) ⇒ SQL::Attribute<Types::PG::Array>

Remove elements by value

Parameters:

  • value (Object)

Returns:



# File 'lib/rom/sql/extensions/postgres/types/array.rb', line 58

#to_symSymbol

Return symbol representation of an attribute

This uses convention from sequel where double underscore in the name is used for qualifying, and triple underscore means aliasing

Examples:

users[:id].qualified.to_sym
# => :users__id

users[:id].as(:user_id).to_sym
# => :id___user_id

users[:id].qualified.as(:user_id).to_sym
# => :users__id___user_id

Returns:

  • (Symbol)


148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/rom/sql/attribute.rb', line 148

def to_sym
  @_to_sym ||=
    if qualified? && aliased?
      :"#{table_name}__#{name}___#{meta[:alias]}"
    elsif qualified?
      :"#{table_name}__#{name}"
    elsif aliased?
      :"#{name}___#{meta[:alias]}"
    else
      name
    end
end

#value(value) ⇒ SQL::Attribute

Wrap a value with the type, it allows using attribute and type specific methods on literals and things like this

Parameters:

  • value (Object)

    any SQL-serializable value

Returns:



335
336
337
# File 'lib/rom/sql/attribute.rb', line 335

def value(value)
  meta(sql_expr: Sequel[value])
end