class ROM::Schema

Relation schema

Schemas hold detailed information about relation tuples, including their primitive types (String, Integer, Hash, etc. or custom classes), as well as various meta information like primary/foreign key and literally any other information that a given database adapter may need.

Adapters can extend this class and it can be used in adapter-specific relations. In example rom-sql extends schema with Association DSL and many additional SQL-specific APIs in schema types.

Schemas are used for projecting canonical relations into other relations and every relation object maintains its schema. This means that we always have all information about relation tuples, even when a relation was projected and diverged from its canonical form.

Furthermore schema attributes know their source relations, which makes it possible to merge schemas from multiple relations and maintain information about the source relations. In example when two relations are joined, their schemas are merged, and we know which attributes belong to which relation.

@api public

Constants

DEFAULT_INFERRER
EMPTY_ASSOCIATION_SET
HASH_SCHEMA

Public Class Methods

attributes(attributes, attr_class) click to toggle source

@api private

# File lib/rom/schema.rb, line 158
def self.attributes(attributes, attr_class)
  attributes.map do |attr|
    attr_class.new(attr[:type], **attr.fetch(:options))
  end
end
build_attribute_info(type, **options) click to toggle source

Builds a representation of the information needed to create an attribute.

This representation is consumed by `Schema.define` in order to create the actual attributes.

@return [Hash] A hash with `:type` and `:options` keys.

@api private

# File lib/rom/schema.rb, line 150
def self.build_attribute_info(type, **options)
  {
    type: type,
    options: options
  }
end
define(name, attributes: EMPTY_ARRAY, attr_class: Attribute, **options) { |schema| ... } click to toggle source

Define a relation schema from plain rom types and optional options

Resulting schema will decorate plain rom types with adapter-specific types By default `Attribute` will be used

@param [Relation::Name, Symbol] name The schema name, typically ROM::Relation::Name

@return [Schema]

@api public

# File lib/rom/schema.rb, line 132
def self.define(name, attributes: EMPTY_ARRAY, attr_class: Attribute, **options)
  new(
    name,
    attr_class: attr_class,
    attributes: attributes(attributes, attr_class),
    **options
  ) { |schema| yield(schema) if block_given? }
end
new(*) { |self| ... } click to toggle source

@api private

Calls superclass method
# File lib/rom/schema.rb, line 165
def initialize(*)
  super

  yield(self) if block_given?
end

Public Instance Methods

+(other)
Alias for: merge
[](key, src = name.to_sym) click to toggle source

Return attribute

@param [Symbol] key The attribute name @param [Symbol, Relation::Name] src The source relation (for merged schemas)

@raise KeyError

@api public

# File lib/rom/schema.rb, line 224
def [](key, src = name.to_sym)
  attr =
    if count_index[key].equal?(1)
      name_index[key]
    else
      source_index[src][key]
    end

  raise(KeyError, "#{key.inspect} attribute doesn't exist in #{src} schema") unless attr

  attr
end
append(*new_attributes) click to toggle source

Append more attributes to the schema

This returns a new schema instance

@param [Array<Attribute>] new_attributes

@return [Schema]

@api public

# File lib/rom/schema.rb, line 339
def append(*new_attributes)
  new(attributes + new_attributes)
end
call(relation) click to toggle source

Abstract method for creating a new relation based on schema definition

This can be used by views to generate a new relation automatically. In example a schema can project a relation, join any additional relations if it includes attributes from other relations etc.

Default implementation is a no-op and it simply returns back untouched relation

@param [Relation] relation

@return [Relation]

@api public

# File lib/rom/schema.rb, line 185
def call(relation)
  relation
end
canonical?() click to toggle source

Return if a schema is canonical

@return [Boolean]

@api public

# File lib/rom/schema.rb, line 372
def canonical?
  equal?(canonical)
end
each(&block) click to toggle source

Iterate over schema's attributes

@yield [Attribute]

@api public

# File lib/rom/schema.rb, line 194
def each(&block)
  attributes.each(&block)
end
empty?() click to toggle source

Check if schema has any attributes

@return [TrueClass, FalseClass]

@api public

# File lib/rom/schema.rb, line 203
def empty?
  attributes.empty?
end
exclude(*names) click to toggle source

Exclude provided attributes from a schema

@param [*Array] names Attribute names

@return [Schema]

@api public

# File lib/rom/schema.rb, line 255
def exclude(*names)
  project(*(map(&:name) - names))
end
finalize!(**_opts) click to toggle source

Finalize a schema

@return [self]

@api private

# File lib/rom/schema.rb, line 381
def finalize!(**_opts)
  return self if frozen?

  freeze
end
finalize_associations!(relations:) { || ... } click to toggle source

Finalize associations defined in a schema

@param [RelationRegistry] relations

@return [self]

@api private

# File lib/rom/schema.rb, line 412
def finalize_associations!(relations:)
  set!(:associations, yield) if associations.any?
  self
end
finalize_attributes!(gateway: nil, relations: nil) { || ... } click to toggle source

This hook is called when relation is being build during container finalization

When block is provided it'll be called just before freezing the instance so that additional ivars can be set

@return [self]

@api private

# File lib/rom/schema.rb, line 395
def finalize_attributes!(gateway: nil, relations: nil)
  inferrer.(self, gateway).each { |key, value| set!(key, value) }

  yield if block_given?

  initialize_primary_key_names

  self
end
foreign_key(relation) click to toggle source

Return FK attribute for a given relation name

@return [Attribute]

@api public

# File lib/rom/schema.rb, line 305
def foreign_key(relation)
  detect { |attr| attr.foreign_key? && attr.target == relation }
end
key?(name) click to toggle source

Return if a schema includes an attribute with the given name

@param [Symbol] name The name of the attribute

@return [Boolean]

@api public

# File lib/rom/schema.rb, line 363
def key?(name)
  !attributes.detect { |attr| attr.name == name }.nil?
end
merge(other) click to toggle source

Merge with another schema

@param [Schema] other Other schema

@return [Schema]

@api public

# File lib/rom/schema.rb, line 325
def merge(other)
  append(*other)
end
Also aliased as: +
prefix(prefix) click to toggle source

Project a schema with renamed attributes using provided prefix

@param [Symbol] prefix The name of the prefix

@return [Schema]

@api public

# File lib/rom/schema.rb, line 282
def prefix(prefix)
  new(map { |attr| attr.prefixed(prefix) })
end
primary_key() click to toggle source

Return primary key attributes

@return [Array<Attribute>]

@api public

# File lib/rom/schema.rb, line 314
def primary_key
  select(&:primary_key?)
end
project(*names) click to toggle source

Project a schema to include only specified attributes

@param [*Array<Symbol, Attribute>] names Attribute names

@return [Schema]

@api public

# File lib/rom/schema.rb, line 244
def project(*names)
  new(names.map { |name| name.is_a?(Symbol) ? self[name] : name })
end
rename(mapping) click to toggle source

Project a schema with renamed attributes

@param [Hash] mapping The attribute mappings

@return [Schema]

@api public

# File lib/rom/schema.rb, line 266
def rename(mapping)
  new_attributes = map do |attr|
    alias_name = mapping[attr.name]
    alias_name ? attr.aliased(alias_name) : attr
  end

  new(new_attributes)
end
set!(key, value) click to toggle source

@api private

# File lib/rom/schema.rb, line 454
def set!(key, value)
  instance_variable_set("@#{key}", value)
  options[key] = value
end
to_ast() click to toggle source

Return AST for the schema

@return [Array]

@api public

# File lib/rom/schema.rb, line 449
def to_ast
  [:schema, [name, attributes.map(&:to_ast)]]
end
to_h() click to toggle source

Coerce schema into a <AttributeName=>Attribute> Hash

@return [Hash]

@api public

# File lib/rom/schema.rb, line 212
def to_h
  each_with_object({}) { |attr, h| h[attr.name] = attr }
end
to_input_hash() click to toggle source

Return coercion function using attribute types

This is used for `input_schema` in relations, typically commands use it for processing input

@return [Dry::Types::Hash]

@api private

# File lib/rom/schema.rb, line 438
def to_input_hash
  HASH_SCHEMA.schema(
    map { |attr| [attr.name, attr.to_write_type] }.to_h
  )
end
to_output_hash() click to toggle source

Return coercion function using attribute read types

This is used for `output_schema` in relations

@return [Dry::Types::Hash]

@api private

# File lib/rom/schema.rb, line 424
def to_output_hash
  HASH_SCHEMA.schema(
    map { |attr| [attr.key, attr.to_read_type] }.to_h
  )
end
uniq(&block) click to toggle source

Return a new schema with uniq attributes

@return [Schema]

@api public

# File lib/rom/schema.rb, line 348
def uniq(&block)
  if block
    new(attributes.uniq(&block))
  else
    new(attributes.uniq(&:name))
  end
end
wrap(prefix = name.dataset) click to toggle source

Return new schema with all attributes marked as prefixed and wrapped

This is useful when relations are joined and the right side should be marked as wrapped

@param [Symbol] prefix The prefix used for aliasing wrapped attributes

@return [Schema]

@api public

# File lib/rom/schema.rb, line 296
def wrap(prefix = name.dataset)
  new(map { |attr| attr.wrapped? ? attr : attr.wrapped(prefix) })
end

Private Instance Methods

count_index() click to toggle source

@api private

# File lib/rom/schema.rb, line 462
def count_index
  map(&:name).map { |name| [name, count { |attr| attr.name == name }] }.to_h
end
initialize_primary_key_names() click to toggle source

@api private

# File lib/rom/schema.rb, line 485
def initialize_primary_key_names
  return if primary_key.empty?

  set!(:primary_key_name, primary_key[0].name)
  set!(:primary_key_names, primary_key.map(&:name))
end
name_index() click to toggle source

@api private

# File lib/rom/schema.rb, line 467
def name_index
  map { |attr| [attr.name, attr] }.to_h
end
new(attributes) click to toggle source

@api private

# File lib/rom/schema.rb, line 480
def new(attributes)
  self.class.new(name, **options, attributes: attributes)
end
source_index() click to toggle source

@api private

# File lib/rom/schema.rb, line 472
def source_index
  select(&:source)
    .group_by(&:source)
    .map { |src, grp| [src.to_sym, grp.map { |attr| [attr.name, attr] }.to_h] }
    .to_h
end