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
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
@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
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
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
@api private
# File lib/rom/relation.rb, line 475 def attr_ast schema.map(&:to_read_ast) end
@api private
# File lib/rom/relation.rb, line 487 def auto_map? (auto_map || auto_struct) && !meta[:combine_type] end
@api private
# File lib/rom/relation.rb, line 492 def auto_struct? auto_struct && !meta[:combine_type] end
Loads a relation
@return [Relation::Loaded]
@api public
# File lib/rom/relation.rb, line 353 def call Loaded.new(self) end
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
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
Returns if this relation is curried
@return [false]
@api private
# File lib/rom/relation.rb, line 371 def curried? false end
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
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
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
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
Returns if this relation is a graph
@return [false]
@api private
# File lib/rom/relation.rb, line 380 def graph? false end
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
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
ROM::Pipeline#map_with
# File lib/rom/relation.rb, line 527 def map_with(*names, **opts) super(*names).with(opts) end
@api private
# File lib/rom/relation.rb, line 497 def mapper mappers[to_ast] end
@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
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
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
@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 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
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
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
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
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
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
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
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
Return if this is a wrap relation
@return [false]
@api private
# File lib/rom/relation.rb, line 389 def wrap? false end
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
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
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