Class: ROM::Relation

Inherits:
Object
  • Object
show all
Extended by:
Dry::Core::ClassAttributes, AutoCurry, Initializer, ClassInterface
Includes:
Memoizable, Pipeline, Commands, Materializable
Defined in:
lib/rom/relation.rb,
lib/rom/relation/name.rb,
lib/rom/relation/wrap.rb,
lib/rom/relation/graph.rb,
lib/rom/relation/loaded.rb,
lib/rom/relation/curried.rb,
lib/rom/relation/combined.rb,
lib/rom/relation/commands.rb,
lib/rom/relation/view_dsl.rb,
lib/rom/relation/composite.rb,
lib/rom/relation/materializable.rb,
lib/rom/relation/class_interface.rb

Overview

Base relation class

Relation is a proxy for the dataset object provided by the gateway. It can forward methods to the dataset, which is why the “native” interface of the underlying gateway is available in the relation

Individual adapters sets up their relation classes and provide different APIs depending on their persistence backend.

API:

  • public

Direct Known Subclasses

Memory::Relation

Defined Under Namespace

Modules: ClassInterface, Commands, Materializable Classes: Combined, Composite, Curried, Graph, Loaded, Name, ViewDSL, Wrap

Constant Summary collapse

NOOP_OUTPUT_SCHEMA =

Default no-op output schema which is called in ‘Relation#each`

API:

  • public

-> tuple { tuple }.freeze

Constants included from ClassInterface

ClassInterface::DEFAULT_DATASET_PROC, ClassInterface::INVALID_RELATIONS_NAMES

Constants included from Memoizable

Memoizable::MEMOIZED_HASH

Instance Attribute Summary collapse

Attributes included from ClassInterface

#schema_proc

Attributes included from Memoizable

#__memoized__

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Initializer

extended

Methods included from ClassInterface

curried, default_name, default_schema, forward, relation_name, set_schema!, use, view, view_methods

Methods included from Notifications::Listener

#subscribe

Methods included from AutoCurry

auto_curried_methods, auto_curry, auto_curry_busy?, auto_curry_guard, extended

Methods included from Pipeline::Operator

#>>

Methods included from Materializable

#first, #one, #one!

Methods included from Memoizable

included

Methods included from Commands

#command

Instance Attribute Details

#auto_mapTrueClass, FalseClass (readonly)

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 Whether or not a relation and its compositions should be auto-mapped.

Returns:

  • Whether or not a relation and its compositions should be auto-mapped

API:

  • private



165
# File 'lib/rom/relation.rb', line 165

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

#auto_structTrueClass, FalseClass (readonly)

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 Whether or not tuples should be auto-mapped to structs.

Returns:

  • Whether or not tuples should be auto-mapped to structs

API:

  • private



170
# File 'lib/rom/relation.rb', line 170

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

#commandsCommandRegistry (readonly)

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 Command registry.

Returns:

  • Command registry

API:

  • private



184
# File 'lib/rom/relation.rb', line 184

option :commands, default: -> { CommandRegistry.new({}, relation_name: name.relation) }

#datasetObject (readonly)

Returns dataset used by the relation provided by relation’s gateway.

Returns:

  • dataset used by the relation provided by relation’s gateway

API:

  • public



136
# File 'lib/rom/relation.rb', line 136

param :dataset

#input_schemaObject#[] (readonly)

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 tuple processing function, uses schema or defaults to Hash[].

Returns:

  • tuple processing function, uses schema or defaults to Hash[]

API:

  • private



153
# File 'lib/rom/relation.rb', line 153

option :input_schema, default: -> { schema.to_input_hash }

#mappersMapperRegistry (readonly)

Returns an optional mapper registry (empty by default).

Returns:

  • an optional mapper registry (empty by default)



179
# File 'lib/rom/relation.rb', line 179

option :mappers, default: -> { MapperRegistry.new }

#metaHash (readonly)

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 Meta data stored in a hash.

Returns:

  • Meta data stored in a hash

API:

  • private



189
# File 'lib/rom/relation.rb', line 189

option :meta, reader: true, default: -> { EMPTY_HASH }

#nameObject (readonly)

Returns The relation name.

Returns:

  • The relation name

API:

  • public



148
# File 'lib/rom/relation.rb', line 148

option :name, default: -> { self.class.schema ? self.class.schema.name : self.class.default_name }

#output_schemaObject#[] (readonly)

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 tuple processing function, uses schema or defaults to NOOP_OUTPUT_SCHEMA.

Returns:

  • tuple processing function, uses schema or defaults to NOOP_OUTPUT_SCHEMA

API:

  • private



158
159
160
# File 'lib/rom/relation.rb', line 158

option :output_schema, default: -> {
  schema.any?(&:read?) ? schema.to_output_hash : NOOP_OUTPUT_SCHEMA
}

#schemaSchema (readonly)

Returns relation schema, defaults to class-level canonical schema (if it was defined) and sets an empty one as the fallback.

Returns:

  • relation schema, defaults to class-level canonical schema (if it was defined) and sets an empty one as the fallback

API:

  • public



143
# File 'lib/rom/relation.rb', line 143

option :schema, default: -> { self.class.schema || self.class.default_schema }

#struct_namespace(ns) ⇒ Relation (readonly)

Return a new relation configured with the provided struct namespace

Parameters:

  • Custom namespace module for auto-structs

Returns:

API:

  • public



175
# File 'lib/rom/relation.rb', line 175

option :struct_namespace, reader: false, default: -> { self.class.struct_namespace }

Class Method Details

.auto_mapBoolean .auto_map(value) ⇒ Object

Whether or not a relation and its compositions should be auto-mapped

Overloads:

  • .auto_mapBoolean

    Return auto_map setting value

    Returns:

  • .auto_map(value) ⇒ Object

    Set auto_map value



83
# File 'lib/rom/relation.rb', line 83

defines :auto_map

.auto_structBoolean .auto_struct(value) ⇒ Object

Whether or not tuples should be auto-mapped to structs

Overloads:

  • .auto_structBoolean

    Return auto_struct setting value

    Returns:

  • .auto_struct(value) ⇒ Object

    Set auto_struct value



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

defines :auto_struct

.gatewaySymbol .gateway(gateway_key) ⇒ Object

Manage the gateway

Overloads:

  • .gatewaySymbol

    Return the gateway key that the relation is associated with

    Returns:

  • .gateway(gateway_key) ⇒ Object

    Link the relation to a gateway. Change this setting if the relation is defined on a non-default gateway

    Examples:

    class Users < ROM::Relation[:sql]
      gateway :custom
    end
    

    Parameters:



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

defines :gateway

.struct_namespaceModule .struct_namespace(namespace) ⇒ Object

Get or set a namespace for auto-generated struct classes. By default, new struct classes are created within ROM::Struct

@example using custom namespace
  class Users < ROM::Relation[:sql]
    struct_namespace Entities
  end

  users.by_pk(1).one! # => #<Entities::User id=1 name="Jane Doe">

Overloads:

  • .struct_namespaceModule

    Returns Default struct namespace.

    Returns:

    • Default struct namespace

  • .struct_namespace(namespace) ⇒ Object

    Parameters:



113
# File 'lib/rom/relation.rb', line 113

defines :struct_namespace

Instance Method Details

#[](name) ⇒ Attribute

Return schema attribute

Examples:

accessing canonical attribute

users[:id]
# => #<ROM::SQL::Attribute[Integer] primary_key=true name=:id source=ROM::Relation::Name(users)>

accessing joined attribute

tasks_with_users = tasks.join(users).select_append(tasks[:title])
tasks_with_users[:title, :tasks]
# => #<ROM::SQL::Attribute[String] primary_key=false name=:title source=ROM::Relation::Name(tasks)>

Returns:

API:

  • public



205
206
207
# File 'lib/rom/relation.rb', line 205

def [](name)
  schema[name]
end

#adapterSymbol

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 The wrapped relation’s adapter identifier ie :sql or :http.

Returns:

  • The wrapped relation’s adapter identifier ie :sql or :http

API:

  • private



557
558
559
# File 'lib/rom/relation.rb', line 557

def adapter
  self.class.adapter
end

#as(aliaz) ⇒ Relation

Return a new relation with an aliased name

Examples:

users.as(:people)

Parameters:

  • Aliased name

Returns:

API:

  • public



550
551
552
# File 'lib/rom/relation.rb', line 550

def as(aliaz)
  with(name: name.as(aliaz))
end

#associationsAssociationSet

Return schema’s association set (empty by default)

Returns:

  • Schema’s association set (empty by default)

API:

  • public



448
449
450
# File 'lib/rom/relation.rb', line 448

def associations
  schema.associations
end

#attr_astObject

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.

API:

  • private



462
463
464
# File 'lib/rom/relation.rb', line 462

def attr_ast
  schema.map { |t| t.to_read_ast }
end

#auto_map?Boolean

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:

API:

  • private



474
475
476
# File 'lib/rom/relation.rb', line 474

def auto_map?
  (auto_map || auto_struct) && !meta[:combine_type]
end

#auto_struct?Boolean

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:

API:

  • private



479
480
481
# File 'lib/rom/relation.rb', line 479

def auto_struct?
  auto_struct && !meta[:combine_type]
end

#callRelation::Loaded

Loads a relation

Returns:

API:

  • public



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

def call
  Loaded.new(self)
end

#combine(*associations) ⇒ Relation

Combine with other relations

Composes relations using configured associations

Examples:

users.combine(:tasks, :posts)

Parameters:

  • A list of association names

Returns:

API:

  • public



240
241
242
# File 'lib/rom/relation.rb', line 240

def combine(*args)
  combine_with(*nodes(*args))
end

#combine_with(*others) ⇒ Relation::Graph

Composes with other relations

Parameters:

  • The other relation(s) to compose with

Returns:

API:

  • public



251
252
253
# File 'lib/rom/relation.rb', line 251

def combine_with(*others)
  Combined.new(self, others)
end

#curried?false

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 if this relation is curried

Returns:

API:

  • private



358
359
360
# File 'lib/rom/relation.rb', line 358

def curried?
  false
end

#each {|Hash| ... } ⇒ Enumerator

Yields relation tuples

Every tuple is processed through Relation#output_schema, it’s a no-op by default

Yields:

  • (Hash)

Returns:

  • if block is not provided

API:

  • public



218
219
220
221
222
223
224
225
226
# File 'lib/rom/relation.rb', line 218

def each
  return to_enum unless block_given?

  if auto_struct?
    mapper.(dataset.map { |tuple| output_schema[tuple] }).each { |struct| yield(struct) }
  else
    dataset.each { |tuple| yield(output_schema[tuple]) }
  end
end

#eager_load(assoc) ⇒ Relation

Return a graph node prepared by the given association

Parameters:

  • association object

Returns:

API:

  • public



289
290
291
292
293
294
295
296
297
# File 'lib/rom/relation.rb', line 289

def eager_load(assoc)
  relation = assoc.prepare(self)

  if assoc.override?
    relation.(assoc)
  else
    relation.preload_assoc(assoc)
  end
end

#foreign_key(name) ⇒ Symbol

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 a foreign key name for the provided relation name

Parameters:

  • The relation name object

Returns:

API:

  • private



588
589
590
591
592
593
594
595
596
# File 'lib/rom/relation.rb', line 588

def foreign_key(name)
  attr = schema.foreign_key(name.dataset)

  if attr
    attr.name
  else
    :"#{Dry::Core::Inflector.singularize(name.dataset)}_id"
  end
end

#gatewaySymbol

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 name of the source gateway of this relation

Returns:

API:

  • private



566
567
568
# File 'lib/rom/relation.rb', line 566

def gateway
  self.class.gateway
end

#graph?false

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 if this relation is a graph

Returns:

API:

  • private



367
368
369
# File 'lib/rom/relation.rb', line 367

def graph?
  false
end

#map_to(klass, **opts) ⇒ Relation::Composite

Return a new relation that will map its tuples to instance of the provided class

Examples:

users.map_to(MyUserModel)

Parameters:

  • Your custom model class

Returns:

API:

  • public



536
537
538
# File 'lib/rom/relation.rb', line 536

def map_to(klass, **opts)
  with(opts.merge(auto_struct: true, meta: { model: klass }))
end

#map_with(model) ⇒ Relation #map_with(*mappers) ⇒ Relation #map_with(*mappers, auto_map: true) ⇒ Relation

Maps the wrapped relation with other mappers available in the registry

Overloads:

  • #map_with(model) ⇒ Relation

    Map tuples to the provided custom model class

    Examples:

    users.map_with(MyUserModel)
    

    Parameters:

    • model Your custom model class

  • #map_with(*mappers) ⇒ Relation

    Map tuples using registered mappers

    Examples:

    users.map_with(:my_mapper, :my_other_mapper)
    

    Parameters:

    • A list of mapper identifiers

  • #map_with(*mappers, auto_map: true) ⇒ Relation

    Map tuples using auto-mapping and custom registered mappers

    If ‘auto_map` is enabled, your mappers will be applied after performing default auto-mapping. This means that you can compose complex relations and have them auto-mapped, and use much simpler custom mappers to adjust resulting data according to your requirements.

    Examples:

    users.map_with(:my_mapper, :my_other_mapper, auto_map: true)
    

    Parameters:

    • A list of mapper identifiers

Returns:

  • A new relation proxy with pipelined relation

API:

  • public



522
523
524
# File 'lib/rom/relation.rb', line 522

def map_with(*names, **opts)
  super(*names).with(opts)
end

#mapperObject

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.

API:

  • private



484
485
486
# File 'lib/rom/relation.rb', line 484

def mapper
  mappers[to_ast]
end

#meta_astObject

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.

API:

  • private



467
468
469
470
471
# File 'lib/rom/relation.rb', line 467

def meta_ast
  meta = self.meta.merge(dataset: name.dataset, alias: name.aliaz, struct_namespace: options[:struct_namespace])
  meta[:model] = false unless auto_struct? || meta[:model]
  meta
end

#new(dataset, new_opts = EMPTY_HASH) ⇒ Object

Return a new relation with provided dataset and additional options

Use this method whenever you need to use dataset API to get a new dataset and you want to return a relation back. Typically relation API should be enough though. If you find yourself using this method, it might be worth to consider reporting an issue that some dataset functionality is not available through relation API.

Examples:

with a new dataset

users.new(users.dataset.some_method)

with a new dataset and options

users.new(users.dataset.some_method, other: 'options')

Parameters:

  • (defaults to: EMPTY_HASH)

    Additional options

API:

  • public



407
408
409
410
411
412
413
414
415
416
417
418
# File 'lib/rom/relation.rb', line 407

def new(dataset, new_opts = EMPTY_HASH)
  opts =
    if new_opts.empty?
      options
    elsif new_opts.key?(:schema)
      options.merge(new_opts).reject { |k, _| k == :input_schema || k == :output_schema }
    else
      options.merge(new_opts)
    end

  self.class.new(dataset, opts)
end

#node(name) ⇒ Relation

Create a graph node for a given association identifier

Parameters:

Returns:

API:

  • public



276
277
278
279
280
# File 'lib/rom/relation.rb', line 276

def node(name)
  assoc = associations[name]
  other = assoc.node
  other.eager_load(assoc)
end

#nodes(*args) ⇒ 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.

API:

  • private



256
257
258
259
260
261
262
263
264
265
266
267
# File 'lib/rom/relation.rb', line 256

def nodes(*args)
  args.reduce([]) do |acc, arg|
    case arg
    when Symbol
      acc << node(arg)
    when Hash
      acc.concat(arg.map { |name, opts| node(name).combine(opts) })
    when Array
      acc.concat(arg.map { |opts| nodes(opts) }.reduce(:concat))
    end
  end
end

#preload_assoc(assoc, other) ⇒ Relation::Curried

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.

Preload other relation via association

This is used internally when relations are composed

Returns:

API:

  • private



306
307
308
# File 'lib/rom/relation.rb', line 306

def preload_assoc(assoc, other)
  assoc.preload(self, other)
end

#schema?TrueClass, FalseClass

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 true if a relation has schema defined

Returns:

API:

  • private



385
386
387
# File 'lib/rom/relation.rb', line 385

def schema?
  ! schema.empty?
end

#schemasHash<Symbol=>Schema>

Return all registered relation schemas

This holds all schemas defined via ‘view` DSL

Returns:

API:

  • public



577
578
579
# File 'lib/rom/relation.rb', line 577

def schemas
  self.class.schemas
end

#to_aArray<Hash>

Materializes a relation into an array

Returns:

API:

  • public



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

def to_a
  to_enum.to_a
end

#to_astArray

Returns AST for the wrapped relation

Returns:

API:

  • public



457
458
459
# File 'lib/rom/relation.rb', line 457

def to_ast
  [:relation, [name.relation, attr_ast, meta_ast]]
end

#with(opts) ⇒ Relation

Returns a new instance with the same dataset but new options

Examples:

users.with(output_schema: -> tuple { .. })

Parameters:

  • New options

Returns:

API:

  • public



432
433
434
435
436
437
438
439
440
441
# File 'lib/rom/relation.rb', line 432

def with(opts)
  new_options =
    if opts.key?(:meta)
      opts.merge(meta: meta.merge(opts[:meta]))
    else
      opts
    end

  new(dataset, options.merge(new_options))
end

#wrap(*names) ⇒ Wrap

Wrap other relations using association names

Examples:

tasks.wrap(:owner)

Parameters:

  • A list with association identifiers

Returns:

API:

  • public



320
321
322
# File 'lib/rom/relation.rb', line 320

def wrap(*names)
  wrap_around(*names.map { |n| associations[n].wrap })
end

#wrap?false

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 if this is a wrap relation

Returns:

API:

  • private



376
377
378
# File 'lib/rom/relation.rb', line 376

def wrap?
  false
end

#wrap_around(*others) ⇒ Relation::Wrap

Wrap around other relations

Parameters:

  • Other relations

Returns:

API:

  • public



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

def wrap_around(*others)
  wrap_class.new(self, others)
end