class ROM::Relation

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

Constants

NOOP_OUTPUT_SCHEMA

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

Public Instance Methods

[](name) click to toggle source

Return schema attribute

@example accessing canonical attribute

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

@example 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)>

@return [Attribute]

@api public

# File lib/rom/relation.rb, line 206
def [](name)
  schema[name]
end
adapter() click to toggle source

@return [Symbol] The wrapped relation's adapter identifier ie :sql or :http

@api private

# File lib/rom/relation.rb, line 562
def adapter
  self.class.adapter
end
as(aliaz) click to toggle source

Return a new relation with an aliased name

@example

users.as(:people)

@param [Symbol] aliaz Aliased name

@return [Relation]

@api public

# File lib/rom/relation.rb, line 555
def as(aliaz)
  with(name: name.as(aliaz))
end
associations() click to toggle source

Return schema's association set (empty by default)

@return [AssociationSet] Schema's association set (empty by default)

@api public

# File lib/rom/relation.rb, line 461
def associations
  schema.associations
end
attr_ast() click to toggle source

@api private

# File lib/rom/relation.rb, line 475
def attr_ast
  schema.map(&:to_read_ast)
end
auto_map?() click to toggle source

@api private

# File lib/rom/relation.rb, line 487
def auto_map?
  (auto_map || auto_struct) && !meta[:combine_type]
end
auto_struct?() click to toggle source

@api private

# File lib/rom/relation.rb, line 492
def auto_struct?
  auto_struct && !meta[:combine_type]
end
call() click to toggle source

Loads a relation

@return [Relation::Loaded]

@api public

# File lib/rom/relation.rb, line 353
def call
  Loaded.new(self)
end
combine(*args) click to toggle source

Combine with other relations using configured associations

@overload combine(*associations)

@example
  users.combine(:tasks, :posts)

@param *associations [Array<Symbol>] A list of association names

@overload combine(*associations, **nested_associations)

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

@param *associations [Array<Symbol>] A list of association names
@param *nested_associations [Hash] A hash with nested association names

@overload combine(associations)

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

@param *associations [Hash] A hash with nested association names

@return [Relation]

@api public

# File lib/rom/relation.rb, line 253
def combine(*args)
  combine_with(*nodes(*args))
end
combine_with(*others) click to toggle source

Composes with other relations

@param [Array<Relation>] others The other relation(s) to compose with

@return [Relation::Graph]

@api public

# File lib/rom/relation.rb, line 264
def combine_with(*others)
  Combined.new(self, others)
end
curried?() click to toggle source

Returns if this relation is curried

@return [false]

@api private

# File lib/rom/relation.rb, line 371
def curried?
  false
end
each() { |struct| ... } click to toggle source

Yields relation tuples

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

@yield [Hash]

@return [Enumerator] if block is not provided

@api public

# File lib/rom/relation.rb, line 219
def each
  return to_enum unless block_given?

  if auto_map?
    mapper.(dataset.map { |tuple| output_schema[tuple] }).each { |struct| yield(struct) }
  else
    dataset.each { |tuple| yield(output_schema[tuple]) }
  end
end
eager_load(assoc) click to toggle source

Return a graph node prepared by the given association

@param [Association] assoc An association object

@return [Relation]

@api public

# File lib/rom/relation.rb, line 302
def eager_load(assoc)
  relation = assoc.prepare(self)

  if assoc.override?
    relation.(assoc)
  else
    relation.preload_assoc(assoc)
  end
end
foreign_key(name) click to toggle source

Return a foreign key name for the provided relation name

@param [Name] name The relation name object

@return [Symbol]

@api private

# File lib/rom/relation.rb, line 593
def foreign_key(name)
  attr = schema.foreign_key(name.dataset)

  if attr
    attr.name
  else
    :"#{Inflector.singularize(name.dataset)}_id"
  end
end
gateway() click to toggle source

Return name of the source gateway of this relation

@return [Symbol]

@api private

# File lib/rom/relation.rb, line 571
def gateway
  self.class.gateway
end
graph?() click to toggle source

Returns if this relation is a graph

@return [false]

@api private

# File lib/rom/relation.rb, line 380
def graph?
  false
end
map_to(klass, **opts) click to toggle source

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

@example

users.map_to(MyUserModel)

@param [Class] klass Your custom model class

@return [Relation]

@api public

# File lib/rom/relation.rb, line 541
def map_to(klass, **opts)
  with(opts.merge(auto_map: false, auto_struct: true, meta: { model: klass }))
end
map_with(*names, **opts) click to toggle source

Maps relation with custom mappers available in the 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.

@overload map_with(*mappers)

Map tuples using registered mappers

@example
  users.map_with(:my_mapper, :my_other_mapper)

@param [Array<Symbol>] mappers A list of mapper identifiers

@overload map_with(*mappers, auto_map: true)

Map tuples using custom registered mappers and enforce auto-mapping

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

@param [Array<Symbol>] mappers A list of mapper identifiers

@return [Relation::Composite] Mapped relation

@api public

Calls superclass method ROM::Pipeline#map_with
# File lib/rom/relation.rb, line 527
def map_with(*names, **opts)
  super(*names).with(opts)
end
mapper() click to toggle source

@api private

# File lib/rom/relation.rb, line 497
def mapper
  mappers[to_ast]
end
meta_ast() click to toggle source

@api private

# File lib/rom/relation.rb, line 480
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) click to toggle source

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.

@example with a new dataset

users.new(users.dataset.some_method)

@example with a new dataset and options

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

@param [Object] dataset @param [Hash] new_opts Additional options

@api public

# File lib/rom/relation.rb, line 420
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(dataset, **opts)
end
node(name) click to toggle source

Create a graph node for a given association identifier

@param [Symbol, Relation::Name] name

@return [Relation]

@api public

# File lib/rom/relation.rb, line 289
def node(name)
  assoc = associations[name]
  other = assoc.node
  other.eager_load(assoc)
end
nodes(*args) click to toggle source

@api private

# File lib/rom/relation.rb, line 269
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) click to toggle source

Preload other relation via association

This is used internally when relations are composed

@return [Relation::Curried]

@api private

# File lib/rom/relation.rb, line 319
def preload_assoc(assoc, other)
  assoc.preload(self, other)
end
schema?() click to toggle source

Returns true if a relation has schema defined

@return [TrueClass, FalseClass]

@api private

# File lib/rom/relation.rb, line 398
def schema?
  !schema.empty?
end
schemas() click to toggle source

Return all registered relation schemas

This holds all schemas defined via `view` DSL

@return [Hash<Symbol=>Schema>]

@api public

# File lib/rom/relation.rb, line 582
def schemas
  self.class.schemas
end
struct_namespace(ns) click to toggle source

Return a new relation configured with the provided struct namespace

@param [Module] ns Custom namespace module for auto-structs

@return [Relation]

@api public

# File lib/rom/relation.rb, line 610
def struct_namespace(ns)
  options[:struct_namespace] == ns ? self : with(struct_namespace: ns)
end
to_a() click to toggle source

Materializes a relation into an array

@return [Array<Hash>]

@api public

# File lib/rom/relation.rb, line 362
def to_a
  to_enum.to_a
end
to_ast() click to toggle source

Returns AST for the wrapped relation

@return [Array]

@api public

# File lib/rom/relation.rb, line 470
def to_ast
  [:relation, [name.relation, attr_ast, meta_ast]]
end
with(opts) click to toggle source

Returns a new instance with the same dataset but new options

@example

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

@param [Hash] opts New options

@return [Relation]

@api public

# File lib/rom/relation.rb, line 445
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) click to toggle source

Wrap other relations using association names

@example

tasks.wrap(:owner)

@param [Array<Symbol>] names A list with association identifiers

@return [Wrap]

@api public

# File lib/rom/relation.rb, line 333
def wrap(*names)
  wrap_around(*names.map { |n| associations[n].wrap })
end
wrap?() click to toggle source

Return if this is a wrap relation

@return [false]

@api private

# File lib/rom/relation.rb, line 389
def wrap?
  false
end
wrap_around(*others) click to toggle source

Wrap around other relations

@param [Array<Relation>] others Other relations

@return [Relation::Wrap]

@api public

# File lib/rom/relation.rb, line 344
def wrap_around(*others)
  wrap_class.new(self, others)
end

Private Instance Methods

composite_class() click to toggle source

Hook used by `Pipeline` to get the class that should be used for composition

@return [Class]

@api private

# File lib/rom/relation.rb, line 629
def composite_class
  Relation::Composite
end
wrap_class() click to toggle source

Return configured “wrap” relation class used in Relation#wrap

@return [Class]

@api private

# File lib/rom/relation.rb, line 638
def wrap_class
  self.class.wrap_class
end