Class: ROM::Schema

Inherits:
Object
  • Object
show all
Extended by:
Initializer
Includes:
Enumerable, Memoizable
Defined in:
lib/rom/schema.rb,
lib/rom/schema/inferrer.rb,
lib/rom/compat/schema/dsl.rb,
lib/rom/schema/associations_dsl.rb

Overview

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.

Direct Known Subclasses

Memory::Schema

Defined Under Namespace

Classes: AssociationsDSL, DSL, Inferrer

Constant Summary collapse

EMPTY_ASSOCIATION_SET =
EMPTY_HASH.freeze
DEFAULT_INFERRER =
Inferrer.new(enabled: false).freeze
HASH_SCHEMA =
Types::Coercible::Hash
.schema(EMPTY_HASH)
.with_type_transform(type_transformation)

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize {|_self| ... } ⇒ Schema

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.

Returns a new instance of Schema.

Yields:

  • (_self)

Yield Parameters:

  • _self (ROM::Schema)

    the object that the method was called on



153
154
155
156
157
# File 'lib/rom/schema.rb', line 153

def initialize(*)
  super

  yield(self) if block_given?
end

Instance Attribute Details

#__memoized__Object (readonly) Originally defined in module Memoizable

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.

#associationsSetup::Registry::Root (readonly)

Returns Optional association set (this is adapter-specific).

Returns:

  • (Setup::Registry::Root)

    Optional association set (this is adapter-specific)



86
# File 'lib/rom/schema.rb', line 86

option :associations, default: -> { EMPTY_ASSOCIATION_SET }

#attributesArray (readonly)

Returns Array with schema attributes.

Returns:

  • (Array)

    Array with schema attributes



82
# File 'lib/rom/schema.rb', line 82

option :attributes, default: -> { EMPTY_ARRAY }

#canonicalSymbol (readonly)

Returns The canonical schema which is carried in all schema instances.

Returns:

  • (Symbol)

    The canonical schema which is carried in all schema instances



94
# File 'lib/rom/schema.rb', line 94

option :canonical, default: -> { self }

#inferrer#call (readonly)

Returns An optional inferrer object used in finalize!.

Returns:

  • (#call)

    An optional inferrer object used in finalize!



90
# File 'lib/rom/schema.rb', line 90

option :inferrer, default: -> { DEFAULT_INFERRER }

#nameSymbol (readonly)

Returns The name of this schema.

Returns:

  • (Symbol)

    The name of this schema



70
# File 'lib/rom/schema.rb', line 70

param :name

#primary_key_nameSymbol (readonly)

Returns The name of the primary key. This is set because in most of the cases relations don't have composite pks.

Returns:

  • (Symbol)

    The name of the primary key. This is set because in most of the cases relations don't have composite pks



102
# File 'lib/rom/schema.rb', line 102

option :primary_key_name, optional: true

#primary_key_namesArray<Symbol> (readonly)

Returns A list of all pk names.

Returns:

  • (Array<Symbol>)

    A list of all pk names



106
# File 'lib/rom/schema.rb', line 106

option :primary_key_names, optional: true

#registryregistry (readonly)

Returns Registry::Root with runtime dependency resolving.

Returns:

  • (registry)

    Registry::Root with runtime dependency resolving



74
# File 'lib/rom/schema.rb', line 74

option :registry, default: -> { self.class.registry }

#relationsregistry (readonly)

Returns Setup relation registry.

Returns:

  • (registry)

    Setup relation registry



78
# File 'lib/rom/schema.rb', line 78

option :relations, default: -> { registry.relations }

Class Method Details

.attributes(attributes, attr_class) ⇒ Object

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.



146
147
148
149
150
# File 'lib/rom/schema.rb', line 146

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) ⇒ Hash

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.

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.

Returns:

  • (Hash)

    A hash with :type and :options keys.



138
139
140
141
142
143
# File 'lib/rom/schema.rb', line 138

def self.build_attribute_info(type, **options)
  {
    type: type,
    options: options
  }
end

.define(name, attributes: EMPTY_ARRAY, attr_class: Attribute, **options) ⇒ Schema

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

Parameters:

  • name (Relation::Name, Symbol)

    The schema name, typically ROM::Relation::Name

Returns:



120
121
122
123
124
125
126
127
# File 'lib/rom/schema.rb', line 120

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

Instance Method Details

#[](key, src = nil) ⇒ Object

Return attribute

Parameters:

  • key (Symbol)

    The attribute name

  • src (Symbol, Relation::Name) (defaults to: nil)

    The source relation (for merged schemas)

Raises:

  • KeyError



212
213
214
215
216
217
218
219
220
221
222
# File 'lib/rom/schema.rb', line 212

def [](key, src = nil)
  if count_index[key].zero?
    raise(KeyError, "#{key.inspect} attribute doesn't exist in #{src} schema")
  elsif count_index[key] > 1 && src.nil?
    raise(KeyError, "#{key.inspect} attribute is not unique") if count_index[key] > 1
  elsif src
    source_index[src][key]
  else
    name_index[key]
  end
end

#append(*new_attributes) ⇒ Schema

Append more attributes to the schema

This returns a new schema instance

Parameters:

Returns:



326
327
328
# File 'lib/rom/schema.rb', line 326

def append(*new_attributes)
  new(attributes + new_attributes)
end

#call(relation) ⇒ Relation

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

Parameters:

Returns:



173
174
175
# File 'lib/rom/schema.rb', line 173

def call(relation)
  relation
end

#canonical?Boolean

Return if a schema is canonical

Returns:

  • (Boolean)


359
360
361
# File 'lib/rom/schema.rb', line 359

def canonical?
  equal?(canonical)
end

#each {|Attribute| ... } ⇒ Object

Iterate over schema's attributes

Yields:



182
183
184
# File 'lib/rom/schema.rb', line 182

def each(&block)
  attributes.each(&block)
end

#empty?TrueClass, FalseClass

Check if schema has any attributes

Returns:

  • (TrueClass, FalseClass)


191
192
193
# File 'lib/rom/schema.rb', line 191

def empty?
  attributes.empty?
end

#exclude(*names) ⇒ Schema

Exclude provided attributes from a schema

Parameters:

  • names (*Array)

    Attribute names

Returns:



242
243
244
# File 'lib/rom/schema.rb', line 242

def exclude(*names)
  project(*(map(&:name) - names))
end

#finalize!self

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.

Finalize a schema

Returns:

  • (self)


368
369
370
371
372
# File 'lib/rom/schema.rb', line 368

def finalize!(*)
  return self if frozen?

  freeze
end

#finalize_attributes!(gateway: nil) ⇒ self

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.

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

Returns:

  • (self)


382
383
384
385
386
387
388
389
390
# File 'lib/rom/schema.rb', line 382

def finalize_attributes!(gateway: nil)
  inferrer.(self, gateway).each { |key, value| set!(key, value) }

  yield if block_given?

  initialize_primary_key_names

  self
end

#foreign_key(relation) ⇒ Attribute

Return FK attribute for a given relation name

Returns:



292
293
294
# File 'lib/rom/schema.rb', line 292

def foreign_key(relation)
  detect { |attr| attr.foreign_key? && attr.target == relation }
end

#key?(name) ⇒ Boolean

Return if a schema includes an attribute with the given name

Parameters:

  • name (Symbol)

    The name of the attribute

Returns:

  • (Boolean)


350
351
352
# File 'lib/rom/schema.rb', line 350

def key?(name)
  !attributes.detect { |attr| attr.name == name }.nil?
end

#merge(other) ⇒ Schema Also known as: +

Merge with another schema

Parameters:

  • other (Schema)

    Other schema

Returns:



312
313
314
# File 'lib/rom/schema.rb', line 312

def merge(other)
  append(*other)
end

#prefix(prefix) ⇒ Schema

Project a schema with renamed attributes using provided prefix

Parameters:

  • prefix (Symbol)

    The name of the prefix

Returns:



269
270
271
# File 'lib/rom/schema.rb', line 269

def prefix(prefix)
  new(map { |attr| attr.prefixed(prefix) })
end

#primary_keyArray<Attribute>

Return primary key attributes

Returns:



301
302
303
# File 'lib/rom/schema.rb', line 301

def primary_key
  select(&:primary_key?)
end

#project(*names) ⇒ Schema

Project a schema to include only specified attributes

Parameters:

  • names (*Array<Symbol, Attribute>)

    Attribute names

Returns:



231
232
233
# File 'lib/rom/schema.rb', line 231

def project(*names)
  new(names.map { |name| name.is_a?(Symbol) ? self[name] : name })
end

#rename(mapping) ⇒ Schema

Project a schema with renamed attributes

Parameters:

  • mapping (Hash)

    The attribute mappings

Returns:



253
254
255
256
257
258
259
260
# File 'lib/rom/schema.rb', line 253

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) ⇒ Object

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.



429
430
431
432
# File 'lib/rom/schema.rb', line 429

def set!(key, value)
  instance_variable_set("@#{key}", value)
  options[key] = value
end

#to_aryArray

Returns Array with schema attributes.

Returns:

  • (Array)

    Array with schema attributes



108
# File 'lib/rom/schema.rb', line 108

option :attributes, default: -> { EMPTY_ARRAY }

#to_astArray

Return AST for the schema

Returns:

  • (Array)


424
425
426
# File 'lib/rom/schema.rb', line 424

def to_ast
  [:schema, [name, attributes.map(&:to_ast)]]
end

#to_hHash

Coerce schema into a Attribute> Hash

Returns:

  • (Hash)


200
201
202
# File 'lib/rom/schema.rb', line 200

def to_h
  each_with_object({}) { |attr, h| h[attr.name] = attr }
end

#to_input_hashDry::Types::Hash

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.

Return coercion function using attribute types

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

Returns:

  • (Dry::Types::Hash)


413
414
415
416
417
# File 'lib/rom/schema.rb', line 413

def to_input_hash
  HASH_SCHEMA.schema(
    map { |attr| [attr.name, attr.to_write_type] }.to_h
  )
end

#to_output_hashDry::Types::Hash

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.

Return coercion function using attribute read types

This is used for output_schema in relations

Returns:

  • (Dry::Types::Hash)


399
400
401
402
403
# File 'lib/rom/schema.rb', line 399

def to_output_hash
  HASH_SCHEMA.schema(
    map { |attr| [attr.key, attr.to_read_type] }.to_h
  )
end

#uniq(&block) ⇒ Schema

Return a new schema with uniq attributes

Returns:



335
336
337
338
339
340
341
# File 'lib/rom/schema.rb', line 335

def uniq(&block)
  if block
    new(attributes.uniq(&block))
  else
    new(attributes.uniq(&:name))
  end
end

#wrap(prefix = name.dataset) ⇒ Schema

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

Parameters:

  • prefix (Symbol) (defaults to: name.dataset)

    The prefix used for aliasing wrapped attributes

Returns:



283
284
285
# File 'lib/rom/schema.rb', line 283

def wrap(prefix = name.dataset)
  new(map { |attr| attr.wrapped? ? attr : attr.wrapped(prefix) })
end