lib/rom/repository/relation_proxy.rb in rom-repository-1.0.2 vs lib/rom/repository/relation_proxy.rb in rom-repository-1.1.0
- old
+ new
@@ -28,10 +28,11 @@
option :name, type: Types::Strict::Symbol
option :mappers, reader: true, default: proc { MapperBuilder.new }
option :meta, reader: true, default: proc { EMPTY_HASH }
option :registry, type: RelationRegistryType, default: proc { RelationRegistry.new }, reader: true
+ option :auto_struct, optional: true
# Relation name
#
# @return [ROM::Relation::Name]
#
@@ -66,24 +67,82 @@
# @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 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.
+ #
+ # @example
+ # users.map_with(:my_mapper, :my_other_mapper, auto_map: true)
+ #
+ # @param [Array<Symbol>] mappers A list of mapper identifiers
+ #
# @return [RelationProxy] A new relation proxy with pipelined relation
#
# @api public
- def map_with(*names)
+ 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
- names.reduce(self) { |a, e| a >> relation.mappers[e] }
+ if opts[:auto_map]
+ 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
alias_method :as, :map_with
+ # Return a new graph with adjusted node returned from a block
+ #
+ # @example with a node identifier
+ # aggregate(:tasks).node(:tasks) { |tasks| tasks.prioritized }
+ #
+ # @example with a nested path
+ # aggregate(tasks: :tags).node(tasks: :tags) { |tags| tags.where(name: 'red') }
+ #
+ # @param [Symbol] name The node relation name
+ #
+ # @yieldparam [RelationProxy] The relation node
+ # @yieldreturn [RelationProxy] The new relation node
+ #
+ # @return [RelationProxy]
+ #
+ # @api public
+ 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
+
# Return a string representation of this relation proxy
#
# @return [String]
#
# @api public
@@ -145,27 +204,43 @@
@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
+ # TODO: add a proper interface to `Relation::Graph` and `Relation::Curried` to rom core
+ #
# @api private
+ def with_nodes(nodes)
+ if relation.curried?
+ __new__(relation.send(:__new__, relation.relation.class.new(relation.root, nodes)))
+ else
+ __new__(relation.class.new(relation.root, nodes))
+ end
+ end
+
+ # @api private
def respond_to_missing?(meth, _include_private = false)
relation.respond_to?(meth) || super
end
private
# @api private
def schema
- meta[:wrap] ? relation.schema.wrap.qualified : relation.schema.reject(&:wrapped?)
+ if meta[:wrap]
+ relation.schema.wrap
+ else
+ relation.schema.reject(&:wrapped?)
+ end
end
# @api private
def base_name
relation.base_name