class ROM::SQL::Attribute
Extended schema attributes tailored for SQL
databases
@api public
Constants
- META_KEYS
- NONSTANDARD_EQUALITY_VALUES
- OPERATORS
- QualifyError
Error
raised when an attribute cannot be qualified
Public Class Methods
@api private
# File lib/rom/sql/attribute.rb, line 33 def [](type, options = EMPTY_HASH) fetch_or_store([type, options]) { new(type, **options) } end
Public Instance Methods
Negate the attribute’s sql expression
@example
users.where(!users[:id].is(1))
@return [Attribute]
@api public
# File lib/rom/sql/attribute.rb, line 224 def ! ~self end
Return a new attribute with an equality expression
@example
users.where { email =~ 1 }
@return [Attribute]
@api public
# File lib/rom/sql/attribute.rb, line 198 def =~(other) meta(sql_expr: sql_expr =~ binary_operation_arg(other)) end
Return a new attribute in its canonical form
@api public
# File lib/rom/sql/attribute.rb, line 41 def canonical if aliased? with(alias: nil).meta(sql_expr: nil) else self end end
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.
@example
users.select_append { id.case(1 => `'first'`, else: `'other'`).as(:first_or_not) }
@param [Hash] mapping mapping between SQL
expressions @return [SQL::Attribute]
@api public
# File lib/rom/sql/attribute.rb, line 366 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
Create a CONCAT function from the attribute
@example with default separator (‘ ’)
users[:id].concat(users[:name])
@example with custom separator
users[:id].concat(users[:name], '-')
@param [SQL::Attribute] other
@return [SQL::Function]
@api public
# File lib/rom/sql/attribute.rb, line 283 def concat(other, sep = ' ') Function.new(type).concat(self, sep, other) end
Return a new attribute marked as a FK
@return [SQL::Attribute]
@api public
# File lib/rom/sql/attribute.rb, line 141 def foreign_key meta(foreign_key: true) end
Create a function DSL
from the attribute
@example
users[:id].func { integer::count(id).as(:count) }
@return [SQL::Function]
@api public
# File lib/rom/sql/attribute.rb, line 266 def func(&block) ProjectionDSL.new(name => self).call(&block).first end
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.
@example
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))
@param [Array<Object>] args A range or a list of values for an inclusion check
@api public
# File lib/rom/sql/attribute.rb, line 246 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
Returns a new attribute marked as indexed
@api public
# File lib/rom/sql/attribute.rb, line 322 def indexed meta(index: true) end
@api public
# File lib/rom/sql/attribute.rb, line 315 def indexed? meta[:index].equal?(true) end
Return a boolean expression with an equality operator
@example
users.where { id.is(1) } users.where(users[:id].is(1))
@param [Object] other Any SQL-compatible object type
@api public
# File lib/rom/sql/attribute.rb, line 186 def is(other) self =~ other end
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
@return [SQL::Attribute] Original attribute marked as joined
@api public
# File lib/rom/sql/attribute.rb, line 93 def joined meta(joined: true) end
Return if an attribute was used in a join
@example
schema = users.schema.join(tasks.schema) schema[:id, :tasks].joined? # => true
@return [Boolean]
@api public
# File lib/rom/sql/attribute.rb, line 108 def joined? meta[:joined].equal?(true) end
@api private
# File lib/rom/sql/attribute.rb, line 327 def meta_options_ast meta = super meta[:index] = true if indexed? meta end
Return a boolean expression with a negated equality operator
@example
users.where { id.not(1) } users.where(users[:id].not(1))
@param [Object] other Any SQL-compatible object type
@api public
# File lib/rom/sql/attribute.rb, line 212 def not(other) !is(other) end
Return if an attribute is qualifiable
@return [Boolean]
@api public
# File lib/rom/sql/attribute.rb, line 132 def qualifiable? !source.nil? end
Return a new attribute marked as qualified
@example
users[:id].aliased(:user_id)
@return [SQL::Attribute]
@api public
# 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
Return if an attribute type is qualified
@example
id = users[:id].qualify id.qualified? # => true
@return [Boolean]
@api public
# File lib/rom/sql/attribute.rb, line 123 def qualified? meta[:qualified].equal?(true) || meta[:qualified].is_a?(Symbol) end
Return a new attribute that is aliased and marked as qualified
Intended to be used when passing attributes to ‘dataset#select`
@return [SQL::Attribute]
@api public
# File lib/rom/sql/attribute.rb, line 77 def qualified_projection(table_alias = nil) if aliased? qualified(table_alias).aliased(self.alias) else qualified(table_alias) end end
Sequel calls this method to coerce an attribute into SQL
string
@param [Sequel::Dataset] ds
@api private
# File lib/rom/sql/attribute.rb, line 292 def sql_literal(ds) ds.literal(sql_expr) end
Sequel column representation
@return [Sequel::SQL::AliasedExpression,Sequel::SQL::Identifier]
@api private
# File lib/rom/sql/attribute.rb, line 301 def to_sql_name @_to_sql_name ||= if qualified? && aliased_projection? Sequel.qualify(table_name, name).as(self.alias) elsif qualified? Sequel.qualify(table_name, name) elsif aliased_projection? Sequel.as(name, self.alias) else Sequel[name] end end
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
@example
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
@return [Symbol]
@api public
# File lib/rom/sql/attribute.rb, line 163 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
Removes metadata from the type
@api private
# File lib/rom/sql/attribute.rb, line 336 def unwrap cleaned_meta = meta.reject { |k, _| META_KEYS.include?(k) } type = optional? ? right : self.type self.class.new(type.with(meta: cleaned_meta), **options) end
Wrap
a value with the type, it allows using attribute and type specific methods on literals and things like this
@param [Object] value any SQL-serializable value @return [SQL::Attribute]
@api public
# File lib/rom/sql/attribute.rb, line 350 def value(value) meta(sql_expr: Sequel[value]) end
Private Instance Methods
A simple wrapper for the boolean expression constructor where the left part is the attribute value
@api private
# File lib/rom/sql/attribute.rb, line 405 def __cmp__(op, other) Sequel::SQL::BooleanExpression.new(op, self, binary_operation_arg(other)) end
Preprocess input value for binary operations
@api private
# File lib/rom/sql/attribute.rb, line 412 def binary_operation_arg(value) case value when Sequel::SQL::Expression value else type[value] end end
@api private
# File lib/rom/sql/attribute.rb, line 433 def extensions TypeExtensions[type] end
Delegate to sql expression if it responds to a given method
@api private
# File lib/rom/sql/attribute.rb, line 389 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
Return Sequel Expression object for an attribute
@api private
# File lib/rom/sql/attribute.rb, line 382 def sql_expr @sql_expr ||= (meta[:sql_expr] || to_sql_name) end
Return source table name or its alias
@api private
# File lib/rom/sql/attribute.rb, line 424 def table_name if qualified? && meta[:qualified].is_a?(Symbol) meta[:qualified] else source.dataset end end