Class: ROM::Repository::RelationProxy

Inherits:
Object
  • Object
show all
Extended by:
Initializer
Includes:
Combine, Wrap, Relation::Materializable
Defined in:
lib/rom/repository/relation_proxy.rb,
lib/rom/repository/relation_proxy/wrap.rb,
lib/rom/repository/relation_proxy/combine.rb

Overview

RelationProxy decorates a relation and automatically generates mappers that will map raw tuples into rom structs

Relation proxies are being registered within repositories so typically there’s no need to instantiate them manually.

Defined Under Namespace

Modules: Combine, Wrap

Constant Summary collapse

RelationRegistryType =
Types.Definition(RelationRegistry).constrained(type: RelationRegistry)

Instance Attribute Summary collapse

Instance Method Summary collapse

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(meth, *args, &block) ⇒ Object (private)

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 to relation and wrap it with proxy if response was a relation too

TODO: this will be simplified once ROM::Relation has lazy-features built-in

and ROM::Lazy is gone


284
285
286
287
288
289
290
291
292
293
294
295
296
# File 'lib/rom/repository/relation_proxy.rb', line 284

def method_missing(meth, *args, &block)
  if relation.respond_to?(meth)
    result = relation.__send__(meth, *args, &block)

    if result.kind_of?(Relation::Materializable) && !result.is_a?(Relation::Loaded)
      __new__(result)
    else
      result
    end
  else
    raise NoMethodError, "undefined method `#{meth}' for #{relation.class.name}"
  end
end

Instance Attribute Details

#relationRelation, ... (readonly)

Returns The decorated relation object.

Returns:

  • (Relation, Relation::Composite, Relation::Graph, Relation::Curried)

    The decorated relation object



27
# File 'lib/rom/repository/relation_proxy.rb', line 27

param :relation

Instance Method Details

#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



194
195
196
# File 'lib/rom/repository/relation_proxy.rb', line 194

def adapter
  relation.class.adapter
end

#call(*args) ⇒ Object

Materializes wrapped relation and sends it through a mapper

For performance reasons a combined relation will skip mapping since we only care about extracting key values for combining



50
51
52
# File 'lib/rom/repository/relation_proxy.rb', line 50

def call(*args)
  ((combine? || composite?) ? relation : (relation >> mapper)).call(*args)
end

#combine(*associations) ⇒ RelationProxy #combine(options) ⇒ RelationProxy Originally defined in module Combine

Combine with other relations

Overloads:

  • #combine(*associations) ⇒ RelationProxy

    Composes relations using configured associations

    Examples:

    users.combine(:tasks, :posts)

    Parameters:

    • *associations (Array<Symbol>)

      A list of association names

  • #combine(options) ⇒ RelationProxy

    Composes relations based on options

    Examples:

    # users have-many tasks (name and join-keys inferred, which needs associations in schema)
    users.combine(many: tasks)
    
    # users have-many tasks with custom name (join-keys inferred, which needs associations in schema)
    users.combine(many: { priority_tasks: tasks.priority })
    
    # users have-many tasks with custom view and join keys
    users.combine(many: { tasks: [tasks.for_users, id: :task_id] })
    
    # users has-one task
    users.combine(one: { task: tasks })

    Parameters:

    • options (Hash)

      Options for combine @option :many [Hash] Sets options for “has-many” type of association @option :one [Hash] Sets options for “has-one/belongs-to” type of association

Returns:

#combine?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 if this relation is combined aka a relation graph

Returns:

  • (Boolean)


178
179
180
# File 'lib/rom/repository/relation_proxy.rb', line 178

def combine?
  meta[:combine_type]
end

#combine_children(options) ⇒ RelationProxy Originally defined in module Combine

Shortcut for combining with children which infers the join keys

Examples:

# users have-many tasks
users.combine_children(many: tasks)

# users have-many tasks with custom mapping (requires associations)
users.combine_children(many: { priority_tasks: tasks.priority })

Parameters:

  • options (Hash)

Returns:

#combine_parents(options) ⇒ RelationProxy Originally defined in module Combine

Shortcut for combining with parents which infers the join keys

Examples:

# tasks belong-to users
tasks.combine_parents(one: users)

# tasks belong-to users with custom user view
tasks.combine_parents(one: users.task_owners)

Parameters:

  • options (Hash)

    Combine options hash

Returns:

#combined(name, keys, type) ⇒ RelationProxy Originally defined in module Combine

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 a combine representation of a loading-proxy relation

This will carry meta info used to produce a correct AST from a relation so that correct mapper can be generated

Returns:

#composite?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.

Return if this relation is a composite

Returns:

  • (Boolean)


187
188
189
# File 'lib/rom/repository/relation_proxy.rb', line 187

def composite?
  relation.is_a?(Relation::Composite)
end

#inspectString

Return a string representation of this relation proxy

Returns:

  • (String)


149
150
151
# File 'lib/rom/repository/relation_proxy.rb', line 149

def inspect
  %(#<#{relation.class} name=#{name} dataset=#{dataset.inspect}>)
end

#map_with(model) ⇒ RelationProxy #map_with(*mappers) ⇒ RelationProxy #map_with(*mappers, auto_map: true) ⇒ RelationProxy Also known as: as

Maps the wrapped relation with other mappers available in the registry

Overloads:

  • #map_with(model) ⇒ RelationProxy

    Map tuples to the provided custom model class

    Examples:

    users.as(MyUserModel)

    Parameters:

    • ] (Class)

      model Your custom model class

  • #map_with(*mappers) ⇒ RelationProxy

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

    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:

    • mappers (Array<Symbol>)

      A list of mapper identifiers

Returns:

  • (RelationProxy)

    A new relation proxy with pipelined relation



88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/rom/repository/relation_proxy.rb', line 88

def map_with(*names, **opts)
  if names.size == 1 && names[0].is_a?(Class)
    with(meta: meta.merge(model: names[0]))
  elsif names.size > 1 && names.any? { |name| name.is_a?(Class) }
    raise ArgumentError, 'using custom mappers and a model is not supported'
  else
    if opts[:auto_map] && !meta[:combine_type]
      mappers = [mapper, *names.map { |name| relation.mappers[name] }]
      mappers.reduce(self) { |a, e| a >> e }
    else
      names.reduce(self) { |a, e| a >> relation.mappers[e] }
    end
  end
end

#mapperROM::Mapper

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.

Infers a mapper for the wrapped relation

Returns:

  • (ROM::Mapper)


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

def mapper
  mappers[to_ast]
end

#nameROM::Relation::Name

Relation name

Returns:

  • (ROM::Relation::Name)


40
41
42
# File 'lib/rom/repository/relation_proxy.rb', line 40

def name
  @name ? relation.name.with(@name) : relation.name
end

#node(name) {|The| ... } ⇒ RelationProxy

Return a new graph with adjusted node returned from a block

Examples:

with a node identifier

aggregate(:tasks).node(:tasks) { |tasks| tasks.prioritized }

with a nested path

aggregate(tasks: :tags).node(tasks: :tags) { |tags| tags.where(name: 'red') }

Parameters:

  • name (Symbol)

    The node relation name

Yield Parameters:

Yield Returns:

Returns:



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/rom/repository/relation_proxy.rb', line 120

def node(name, &block)
  if name.is_a?(Symbol) && !nodes.map { |n| n.name.relation }.include?(name)
    raise ArgumentError, "#{name.inspect} is not a valid aggregate node name"
  end

  new_nodes = nodes.map { |node|
    case name
    when Symbol
      name == node.name.relation ? yield(node) : node
    when Hash
      other, *rest = name.flatten(1)
      if other == node.name.relation
        nodes.detect { |n| n.name.relation == other }.node(*rest, &block)
      else
        node
      end
    else
      node
    end
  }

  with_nodes(new_nodes)
end

#respond_to_missing?(meth, _include_private = false) ⇒ 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)


219
220
221
# File 'lib/rom/repository/relation_proxy.rb', line 219

def respond_to_missing?(meth, _include_private = false)
  relation.respond_to?(meth) || super
end

#to_astArray

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 AST for the wrapped relation

Returns:

  • (Array)


203
204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'lib/rom/repository/relation_proxy.rb', line 203

def to_ast
  @to_ast ||=
    begin
      attr_ast = schema.map { |attr| [:attribute, attr] }

      meta = self.meta.merge(dataset: base_name.dataset)
      meta.update(model: false) unless meta[:model] || auto_struct
      meta.delete(:wraps)

      header = attr_ast + nodes_ast + wraps_ast

      [:relation, [base_name.relation, meta, [:header, header]]]
    end
end

#with(new_options) ⇒ RelationProxy

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 a new instance with new options

Parameters:

  • new_options (Hash)

Returns:



169
170
171
# File 'lib/rom/repository/relation_proxy.rb', line 169

def with(new_options)
  __new__(relation, options.merge(new_options))
end

#wrap(*names, **options) ⇒ RelationProxy Originally defined in module Wrap

Wrap other relations

Examples:

tasks.wrap(owner: [users, user_id: :id])

Parameters:

  • options (Hash)

Returns:

#wrap_parent(options) ⇒ RelationProxy Originally defined in module Wrap

Shortcut to wrap parents

Examples:

tasks.wrap_parent(owner: users)

Returns:

#wrapped(name, keys, wrap_from_assoc = false) ⇒ RelationProxy Originally defined in module Wrap

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 wrapped representation of a loading-proxy relation

This will carry meta info used to produce a correct AST from a relation so that correct mapper can be generated

Returns:

#wraps_from_names(names) ⇒ Object Originally defined in module Wrap

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.

#wraps_from_options(options) ⇒ Object Originally defined in module Wrap

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.