Class: ROM::Relation

Inherits:
Object
  • Object
show all
Extended by:
AutoCurry, Initializer, Notifications::Listener, Plugins::ClassMethods, ClassInterface, SettingProxy
Includes:
Dry::Core::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/compat/relation.rb,
lib/rom/relation/loaded.rb,
lib/rom/relation/curried.rb,
lib/rom/relation/combined.rb,
lib/rom/relation/commands.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.

Direct Known Subclasses

Memory::Relation

Defined Under Namespace

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

Constant Summary collapse

NOOP_OUTPUT_SCHEMA =

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

-> tuple { tuple }.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#associationsSetup::registry (readonly)

Returns Relation associations.

Returns:

  • (Setup::registry)

    Relation associations



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

option :associations, default: -> { registry.associations.scoped(config.component.id) }

#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:

  • (TrueClass, FalseClass)

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



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

option :auto_map, default: -> { config.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:

  • (TrueClass, FalseClass)

    Whether or not tuples should be auto-mapped to structs



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

option :auto_struct, default: -> { config.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:

  • (Commandregistry)

    Command registry



176
177
178
# File 'lib/rom/relation.rb', line 176

option :commands, default: -> do
  registry.commands.scoped(config.component.id, opts: {adapter: adapter})
end

#configROM::Configurable::Config (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.



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

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

#datasetObject (readonly)

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

Returns:

  • (Object)

    dataset used by the relation provided by relation's gateway



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

option :dataset, default: -> { datasets.infer(config.component.id) }

#datasetsregistry (readonly)

Returns Relation associations.

Returns:



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

option :datasets, default: -> { registry.datasets.scoped(config.component.id, config: config) }

#inflectorDry::Inflector (readonly)

Returns The default inflector.

Returns:

  • (Dry::Inflector)

    The default inflector



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

option :inflector, default: -> { config.component.inflector }

#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:

  • (Object#[])

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



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

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

#mappersregistry (readonly)

Returns an optional mapper registry (empty by default).

Returns:

  • (registry)

    an optional mapper registry (empty by default)



159
160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/rom/relation.rb', line 159

option :mappers,
-> mappers {
  if mappers.is_a?(Hash)
    registry.mappers
      .scoped(config.component.id, opts: {adapter: config.component.adapter})
      .import(mappers)
  else
    mappers
  end
},
default: -> {
  registry.mappers.scoped(config.component.id, opts: {adapter: adapter})
}

#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:

  • (Hash)

    Meta data stored in a hash



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

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

#nameName (readonly)

Returns The relation name.

Returns:

  • (Name)

    The relation name



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

option :name, default: -> { Name[config.component.id, config.component.dataset] }

#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:

  • (Object#[])

    tuple processing function, uses schema or defaults to NOOP_OUTPUT_SCHEMA



138
139
140
# File 'lib/rom/relation.rb', line 138

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

#registryregistry (readonly)

Returns Registry::Root with runtime dependency resolving.

Returns:

  • (registry)

    Registry::Root with runtime dependency resolving



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

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

#schemaSetup::registry (readonly)

Returns The canonical schema.

Returns:

  • (Setup::registry)

    The canonical schema



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

option :schema, default: -> { schemas.infer(config.component.id) }

#schemasSetup::registry (readonly)

Returns Relation schemas.

Returns:

  • (Setup::registry)

    Relation schemas



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

option :schemas, default: -> { registry.schemas.scoped(config.component.id, config: config) }

#struct_namespace(ns) ⇒ Relation (readonly)

Return a new relation configured with the provided struct namespace

Parameters:

  • ns (Module)

    Custom namespace module for auto-structs

Returns:



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

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

Class Method Details

.[](adapter) ⇒ Class Originally defined in module ClassInterface

Return adapter-specific relation subclass

Examples:

ROM::Relation[:memory]
# => ROM::Memory::Relation

Returns:

  • (Class)

.auto_curried_methodsObject Originally defined in module AutoCurry

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.

.auto_curry(name, &block) ⇒ Object Originally defined in module AutoCurry

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.

Auto-curry a method

Parameters:

  • name (Symbol)

    The name of a method

.auto_curry_busy?Boolean Originally defined in module AutoCurry

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:

  • (Boolean)

.auto_curry_guardObject Originally defined in module AutoCurry

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.

.curriedObject Originally defined in module ClassInterface

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.

.forward(*methods) ⇒ Object Originally defined in module ClassInterface

Dynamically define a method that will forward to the dataset and wrap response in the relation itself

Examples:

class SomeAdapterRelation < ROM::Relation
  forward :super_query
end

.inherited(klass) ⇒ 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.



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/rom/relation.rb', line 59

def self.inherited(klass)
  super

  adapter = config.component.adapter

  klass.configure do |config|
    # Relations that inherit from an adapter subclass are not considered abstract anymore
    # You can override it later inside your class' config of course
    if adapter
      config.component.abstract = false

      # Use klass' name to set defaults
      #
      # ie `Relations::Users` assumes :users id and a corresponding dataset (table in case of SQL)
      #
      # TODO: make this behavior configurable?
      #
      if klass.name
        config.component.id = config.component.inflector.component_id(klass.name).to_sym
        config.component.dataset = config.component.id
      else
        config.component.id = :anonymous
      end
    end
  end
end

.new(dataset = nil, **opts) ⇒ Object



186
187
188
189
190
191
192
# File 'lib/rom/relation.rb', line 186

def self.new(dataset = nil, **opts)
  if dataset
    super(**opts, dataset: dataset)
  else
    super(**opts)
  end
end

.pluginsObject Originally defined in module Plugins::ClassMethods

Return all available plugins for the component type

.setting_mappingObject



11
12
13
14
15
16
17
18
19
20
21
22
23
24
# File 'lib/rom/compat/relation.rb', line 11

def setting_mapping
  @setting_mapping ||= {
    auto_map: [],
    auto_struct: [],
    struct_namespace: [],
    wrap_class: [],
    adapter: [:component, :adapter],
    gateway: [:component, :gateway],
    schema_class: [:schema, :constant],
    schema_dsl: [:schema, :dsl_class],
    schema_attr_class: [:schema, :attr_class],
    schema_inferrer: [:schema, :inferrer]
  }.freeze
end

.subscribe(event_id, query = EMPTY_HASH, &block) ⇒ Object Originally defined in module Notifications::Listener

Subscribe to events

Parameters:

  • event_id (String)

    The event key

  • query (Hash) (defaults to: EMPTY_HASH)

    An optional event filter

Returns:

  • (Object)

    self

.use(name, **options) ⇒ Object Originally defined in module Plugins::ClassMethods

Include a registered plugin in this relation class

Parameters:

  • plugin (Symbol)
  • options (Hash)

Options Hash (**options):

  • :adapter (Symbol) — default: :default

    first adapter to check for plugin

.view_methodsObject

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 is used by the deprecated command => relation view delegation syntax



31
32
33
34
35
36
# File 'lib/rom/compat/relation.rb', line 31

def self.view_methods
  ancestor_methods = ancestors.reject { |klass| klass == self }
    .map(&:instance_methods).flatten(1)

  instance_methods - ancestor_methods + auto_curried_methods.to_a
end

Instance Method Details

#>>(other) ⇒ Relation::Composite Originally defined in module Pipeline::Operator

Compose two relation with a left-to-right composition

Examples:

users.by_name('Jane') >> tasks.for_users

Parameters:

  • other (Relation)

    The right relation

Returns:

#[](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:



208
209
210
# File 'lib/rom/relation.rb', line 208

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:

  • (Symbol)

    The wrapped relation's adapter identifier ie :sql or :http



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

def adapter
  config.component.adapter
end

#as(aliaz) ⇒ Relation

Return a new relation with an aliased name

Examples:

users.as(:people)

Parameters:

  • aliaz (Symbol)

    Aliased name

Returns:



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

def as(aliaz)
  with(name: name.as(aliaz))
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.



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

def attr_ast
  schema.map(&: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:

  • (Boolean)


481
482
483
# File 'lib/rom/relation.rb', line 481

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:

  • (Boolean)


486
487
488
# File 'lib/rom/relation.rb', line 486

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

#callRelation::Loaded

Loads a relation

Returns:



355
356
357
# File 'lib/rom/relation.rb', line 355

def call
  Loaded.new(self)
end

#combine(*associations) ⇒ Relation #combine(*associations, **nested_associations) ⇒ Relation #combine(associations) ⇒ Relation

Combine with other relations using configured associations

Overloads:

  • #combine(*associations) ⇒ Relation

    Examples:

    users.combine(:tasks, :posts)

    Parameters:

    • *associations (Array<Symbol>)

      A list of association names

  • #combine(*associations, **nested_associations) ⇒ Relation

    Examples:

    users.combine(:tasks, posts: :authors)

    Parameters:

    • *associations (Array<Symbol>)

      A list of association names

    • *nested_associations (Hash)

      A hash with nested association names

  • #combine(associations) ⇒ Relation

    Examples:

    users.combine(posts: [:authors, reviews: [:tags, comments: :author])

    Parameters:

    • *associations (Hash)

      A hash with nested association names

Returns:



255
256
257
# File 'lib/rom/relation.rb', line 255

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

#combine_with(*others) ⇒ Relation::Graph

Composes with other relations

Parameters:

  • others (Array<Relation>)

    The other relation(s) to compose with

Returns:



266
267
268
# File 'lib/rom/relation.rb', line 266

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

#command(type, mapper: nil, use: EMPTY_ARRAY, plugins_options: EMPTY_HASH, **opts) ⇒ ROM::Command Originally defined in module Commands

Return a command for the relation

This method can either return an existing custom command identified by type param, or generate a command dynamically based on relation AST.

Examples:

build a simple :create command

users.command(:create)

build a command which returns multiple results

users.command(:create, result: many)

build a command which uses a specific plugin

users.command(:create, use: :timestamps)

build a command which sends results through a custom mapper

users.command(:create, mapper: :my_mapper_identifier)

return an existing custom command

users.command(:my_custom_command_identifier)

Parameters:

  • type (Symbol)

    The command type (:create, :update or :delete)

  • opts (Hash)

    Additional options

Options Hash (**opts):

  • :mapper (Symbol) — default: nil

    An optional mapper applied to the command result

  • :use (Array<Symbol>) — default: []

    A list of command plugins

  • :result (Symbol) — default: :one

    Set how many results the command should return. Can be :one or :many

Returns:

#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:

  • (false)


373
374
375
# File 'lib/rom/relation.rb', line 373

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:

  • (Enumerator)

    if block is not provided



221
222
223
224
225
226
227
228
229
# File 'lib/rom/relation.rb', line 221

def each(&block)
  return to_enum unless block_given?

  if auto_map?
    mapper.(dataset.map { |tuple| output_schema[tuple] }).each(&block)
  else
    dataset.each { |tuple| yield(output_schema[tuple]) }
  end
end

#eager_load(assoc) ⇒ Relation

Return a graph node prepared by the given association

Parameters:

  • assoc (Association)

    An association object

Returns:



304
305
306
307
308
309
310
311
312
# File 'lib/rom/relation.rb', line 304

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

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

#firstObject Originally defined in module Materializable

Return first tuple from a relation coerced to an array

Returns:

  • (Object)

#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:

  • name (Name)

    The relation name object

Returns:

  • (Symbol)


576
577
578
579
580
581
582
583
584
# File 'lib/rom/relation.rb', line 576

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

  if attr
    attr.name
  else
    :"#{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:

  • (Symbol)


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

def gateway
  config.component.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:

  • (false)


382
383
384
# File 'lib/rom/relation.rb', line 382

def graph?
  false
end

#map_to(klass, **opts) ⇒ Relation

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

Examples:

users.map_to(MyUserModel)

Parameters:

  • klass (Class)

    Your custom model class

Returns:



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

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

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

Maps relation with custom mappers available via registry

When 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.

Overloads:

  • #map_with(*mappers) ⇒ Relation::Composite

    Map tuples using registered mappers

    Examples:

    users.map_with(:my_mapper, :my_other_mapper)

    Parameters:

    • mappers (Array<Symbol>)

      A list of mapper identifiers

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

    Map tuples using custom registered mappers and enforce auto-mapping

    Examples:

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

    Parameters:

    • mappers (Array<Symbol>)

      A list of mapper identifiers

Returns:



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

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.



491
492
493
# File 'lib/rom/relation.rb', line 491

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.



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

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

  • dataset (Object)
  • new_opts (Hash)

    Additional options



422
423
424
425
426
427
428
429
430
431
432
433
# File 'lib/rom/relation.rb', line 422

def new(dataset, **new_opts)
  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(**opts, dataset: dataset)
end

#node(name) ⇒ Relation

Create a graph node for a given association identifier

Parameters:

Returns:



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

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.



271
272
273
274
275
276
277
278
279
280
281
282
# File 'lib/rom/relation.rb', line 271

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

#oneObject Originally defined in module Materializable

Delegate to loaded relation and return one object

Returns:

  • (Object)

See Also:

#one!Object Originally defined in module Materializable

Delegate to loaded relation and return one object

Returns:

  • (Object)

See Also:

#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:



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

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:

  • (TrueClass, FalseClass)


400
401
402
# File 'lib/rom/relation.rb', line 400

def schema?
  !schema.empty?
end

#to_aArray<Hash>

Materializes a relation into an array

Returns:

  • (Array<Hash>)


364
365
366
# File 'lib/rom/relation.rb', line 364

def to_a
  to_enum.to_a
end

#to_astArray

Returns AST for the wrapped relation

Returns:

  • (Array)


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

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:

  • opts (Hash)

    New options

Returns:



447
448
449
450
451
452
453
454
455
456
# File 'lib/rom/relation.rb', line 447

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

  new(dataset, **options, **new_options)
end

#wrap(*names) ⇒ Wrap

Wrap other relations using association names

Examples:

tasks.wrap(:owner)

Parameters:

  • names (Array<Symbol>)

    A list with association identifiers

Returns:



335
336
337
# File 'lib/rom/relation.rb', line 335

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:

  • (false)


391
392
393
# File 'lib/rom/relation.rb', line 391

def wrap?
  false
end

#wrap_around(*others) ⇒ Relation::Wrap

Wrap around other relations

Parameters:

  • others (Array<Relation>)

    Other relations

Returns:



346
347
348
# File 'lib/rom/relation.rb', line 346

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