# File lib/rom/schema.rb, line 314 def primary_key select(&:primary_key?) end
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
@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
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 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
@api private
# File lib/rom/schema.rb, line 165 def initialize(*) super yield(self) if block_given? end
Public Instance Methods
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 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
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
Return if a schema is canonical
@return [Boolean]
@api public
# File lib/rom/schema.rb, line 372 def canonical? equal?(canonical) end
Iterate over schema's attributes
@yield [Attribute]
@api public
# File lib/rom/schema.rb, line 194 def each(&block) attributes.each(&block) end
Check if schema has any attributes
@return [TrueClass, FalseClass]
@api public
# File lib/rom/schema.rb, line 203 def empty? attributes.empty? end
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
a schema
@return [self]
@api private
# File lib/rom/schema.rb, line 381 def finalize!(**_opts) return self if frozen? freeze end
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
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
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
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 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
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
Return primary key attributes
@return [Array<Attribute>]
@api public
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
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
@api private
# File lib/rom/schema.rb, line 454 def set!(key, value) instance_variable_set("@#{key}", value) options[key] = value end
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
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
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
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
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
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
@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
@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
@api private
# File lib/rom/schema.rb, line 467 def name_index map { |attr| [attr.name, attr] }.to_h end
@api private
# File lib/rom/schema.rb, line 480 def new(attributes) self.class.new(name, **options, attributes: attributes) end
@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