Class: ROM::Schema

Inherits:
Object
  • Object
show all
Extended by:
Initializer, Notifications::Listener
Includes:
Enumerable, Memoizable
Defined in:
lib/rom/schema.rb,
lib/rom/schema/dsl.rb,
lib/rom/schema/inferrer.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 =
AssociationSet.new(EMPTY_HASH).freeze
DEFAULT_INFERRER =
Inferrer.new(enabled: false).freeze

Constants included from Memoizable

Memoizable::MEMOIZED_HASH

Instance Attribute Summary collapse

Attributes included from Memoizable

#__memoized__

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Notifications::Listener

subscribe

Methods included from Initializer

extended

Methods included from Memoizable

included

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



132
133
134
135
136
# File 'lib/rom/schema.rb', line 132

def initialize(*)
  super

  yield(self) if block_given?
end

Instance Attribute Details

#associationsAssociationSet (readonly)



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

option :associations, default: -> { EMPTY_ASSOCIATION_SET }

#attributesArray (readonly)



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

option :attributes, default: -> { EMPTY_ARRAY }

#canonicalSymbol (readonly)



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

option :canonical, default: -> { self }

#inferrer#call (readonly)



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

option :inferrer, default: -> { DEFAULT_INFERRER }

#nameSymbol (readonly)



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

param :name

#primary_key_nameSymbol (readonly)



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

option :primary_key_name, optional: true

#primary_key_namesArray<Symbol> (readonly)



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

option :primary_key_names, optional: true

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.



127
128
129
# File 'lib/rom/schema.rb', line 127

def self.attributes(attributes, attr_class)
  attributes.map { |type| attr_class.new(type) }
end

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

Define a relation schema from plain rom types

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



117
118
119
120
121
122
123
124
# File 'lib/rom/schema.rb', line 117

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 = name.to_sym) ⇒ Object

Return attribute

Raises:

  • KeyError



190
191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'lib/rom/schema.rb', line 190

def [](key, src = name.to_sym)
  attr =
    if count_index[key].equal?(1)
      name_index[key]
    else
      source_index[src][key]
    end

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

  attr
end

#append(*new_attributes) ⇒ Schema

Append more attributes to the schema

This returns a new schema instance



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

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 uncludes attributes from other relations etc.

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



151
152
153
# File 'lib/rom/schema.rb', line 151

def call(relation)
  relation
end

#canonical?Boolean

Return if a schema is canonical



340
341
342
# File 'lib/rom/schema.rb', line 340

def canonical?
  self.equal?(canonical)
end

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

Iterate over schema’s attributes

Yields:



160
161
162
# File 'lib/rom/schema.rb', line 160

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

#empty?TrueClass, FalseClass

Check if schema has any attributes



169
170
171
# File 'lib/rom/schema.rb', line 169

def empty?
  attributes.size == 0
end

#exclude(*names) ⇒ Schema

Exclude provided attributes from a schema



223
224
225
# File 'lib/rom/schema.rb', line 223

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

#finalize!(**opts) ⇒ 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



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

def finalize!(**opts)
  return self if frozen?
  freeze
end

#finalize_associations!(relations:) ⇒ 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 associations defined in a schema



379
380
381
382
# File 'lib/rom/schema.rb', line 379

def finalize_associations!(relations:)
  set!(:associations, yield) if associations.any?
  self
end

#finalize_attributes!(gateway: nil, relations: 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



362
363
364
365
366
367
368
369
370
# File 'lib/rom/schema.rb', line 362

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

Return FK attribute for a given relation name



273
274
275
# File 'lib/rom/schema.rb', line 273

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



331
332
333
# File 'lib/rom/schema.rb', line 331

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

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

Merge with another schema



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

def merge(other)
  append(*other)
end

#prefix(prefix) ⇒ Schema

Project a schema with renamed attributes using provided prefix



250
251
252
# File 'lib/rom/schema.rb', line 250

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

#primary_keyArray<Attribute>

Return primary key attributes



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

def primary_key
  select(&:primary_key?)
end

#project(*names) ⇒ Schema

Project a schema to include only specified attributes



212
213
214
# File 'lib/rom/schema.rb', line 212

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

#rename(mapping) ⇒ Schema

Project a schema with renamed attributes



234
235
236
237
238
239
240
241
# File 'lib/rom/schema.rb', line 234

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.



421
422
423
424
# File 'lib/rom/schema.rb', line 421

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

#to_aryArray



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

option :attributes, default: -> { EMPTY_ARRAY }

#to_astArray

Return AST for the schema



416
417
418
# File 'lib/rom/schema.rb', line 416

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

#to_hHash

Coerce schema into a <AttributeName=>Attribute> Hash



178
179
180
# File 'lib/rom/schema.rb', line 178

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



405
406
407
408
409
# File 'lib/rom/schema.rb', line 405

def to_input_hash
  Types::Coercible::Hash.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



391
392
393
394
395
# File 'lib/rom/schema.rb', line 391

def to_output_hash
  Types::Coercible::Hash.schema(
    map { |attr| [attr.key, attr.to_read_type] }.to_h
  )
end

#uniq(&block) ⇒ Schema

Return a new schema with uniq attributes



316
317
318
319
320
321
322
# File 'lib/rom/schema.rb', line 316

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



264
265
266
# File 'lib/rom/schema.rb', line 264

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