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