class AMA::Entity::Mapper::Engine::RecursiveMapper

Recursively maps object to one of specified types

Public Class Methods

new(registry) click to toggle source

@param [AMA::Entity::Mapper::Type::Registry] registry

# File lib/ama-entity-mapper/engine/recursive_mapper.rb, line 19
def initialize(registry)
  @registry = registry
end

Public Instance Methods

map(source, types, context) click to toggle source

@param [Object] source @param [Array<AMA::Entity::Mapper::Type] types @param [AMA::Entity::Mapper::Context] context

# File lib/ama-entity-mapper/engine/recursive_mapper.rb, line 26
def map(source, types, context)
  map_unsafe(source, types, context)
rescue StandardError => e
  message = "Failed to map #{source.class} " \
    "to any of provided types (#{types.map(&:to_def).join(', ')}). " \
    "Last error: #{e.message} in #{e.backtrace_locations[0]}"
  mapping_error(message)
end
map_type(source, type, ctx) click to toggle source

@param [Object] source @param [AMA::Entity::Mapper::Type] type @param [AMA::Entity::Mapper::Context] ctx @return [Object]

# File lib/ama-entity-mapper/engine/recursive_mapper.rb, line 39
def map_type(source, type, ctx)
  ctx.logger.debug("Mapping #{source.class} to type #{type.to_def}")
  source, reassembled = request_reassembly(source, type, ctx)
  epithet = reassembled ? 'reassembled' : 'source'
  if type.attributes.empty?
    message = "#{type.to_def} has no attributes, " \
      "returning #{epithet} instance"
    ctx.logger.debug(message)
    return source
  end
  process_attributes(source, type, ctx)
end

Private Instance Methods

install_attributes(target, type, attributes, ctx) click to toggle source

@param [Object] target @param [AMA::Entity::Mapper::Type] type @param [Array] attributes @param [AMA::Entity::Mapper::Context] ctx

# File lib/ama-entity-mapper/engine/recursive_mapper.rb, line 130
def install_attributes(target, type, attributes, ctx)
  ctx.logger.debug("Installing updated attributes on #{type.to_def}")
  attributes.each do |_, attribute, value, local_ctx|
    type.injector.inject(target, type, attribute, value, local_ctx)
  end
  target
end
map_attribute(source, attribute, context) click to toggle source

@param [Object] source @param [AMA::Entity::Mapper::Type::Attribute] attribute @param [AMA::Entity::Mapper::Context] context

# File lib/ama-entity-mapper/engine/recursive_mapper.rb, line 110
def map_attribute(source, attribute, context)
  message = "Extracting attribute #{attribute.to_def} " \
    "from #{source.class}"
  context.logger.debug(message)
  successful(attribute.types, Mapper::Error) do |type|
    if source.nil? && attribute.nullable
      context.logger.debug('Found legal nil, short-circuiting')
      break nil
    end
    result = map_type(source, type, context)
    context.logger.debug("Validating resulting #{attribute.to_def}")
    attribute.valid!(result, context)
    result
  end
end
map_attributes(source, type, ctx) click to toggle source

Returns array of mapped attribute in format

[changed?, attribute, value, attribute_context],..

@param [Object] source @param [AMA::Entity::Mapper::Type] type @param [AMA::Entity::Mapper::Context] ctx @return [Array]

# File lib/ama-entity-mapper/engine/recursive_mapper.rb, line 93
def map_attributes(source, type, ctx)
  ctx.logger.debug("Mapping #{source.class} attributes")
  enumerator = type.enumerator.enumerate(source, type, ctx)
  enumerator.map do |attribute, value, segment|
    local_ctx = segment.nil? ? ctx : ctx.advance(segment)
    mutated = map_attribute(value, attribute, local_ctx)
    changed = !mutated.equal?(value)
    if changed
      ctx.logger.debug("Attribute #{attribute.to_def} has changed")
    end
    [changed, attribute, mutated, local_ctx]
  end
end
map_unsafe(source, types, context) click to toggle source

@param [Object] source @param [Array<AMA::Entity::Mapper::Type] types @param [AMA::Entity::Mapper::Context] context

# File lib/ama-entity-mapper/engine/recursive_mapper.rb, line 57
def map_unsafe(source, types, context)
  message = "Mapping #{source.class} into one of: " \
    "#{types.map(&:to_def).join(', ')}"
  context.logger.debug(message)
  successful(types, Mapper::Error, context) do |type|
    result = map_type(source, type, context)
    context.logger.debug("Validating resulting #{type.to_def}")
    type.valid!(result, context)
    result
  end
end
process_attributes(source, type, ctx) click to toggle source

@param [Object] source @param [AMA::Entity::Mapper::Type] type @param [AMA::Entity::Mapper::Context] ctx @return [Object]

# File lib/ama-entity-mapper/engine/recursive_mapper.rb, line 73
def process_attributes(source, type, ctx)
  attributes = map_attributes(source, type, ctx)
  if attributes.select(&:first).empty?
    message = 'No changes in attributes detected, ' \
      "returning #{source.class}"
    ctx.logger.debug(message)
    return source
  end
  ctx.logger.debug("Creating new #{type.to_def} instance")
  target = type.factory.create(type, source, ctx)
  ctx.logger.debug("Installing #{type.to_def} attributes")
  install_attributes(target, type, attributes, ctx)
end
reassemble(source, type, context) click to toggle source

@param [Object] source @param [AMA::Entity::Mapper::Type] type @param [AMA::Entity::Mapper::Context] context @return [Object]

# File lib/ama-entity-mapper/engine/recursive_mapper.rb, line 151
def reassemble(source, type, context)
  message = "Reassembling #{source.class} as #{type.type}"
  context.logger.debug(message)
  source_type = @registry.find(source.class)
  source_type ||= Type::Analyzer.analyze(source.class)
  normalizer = source_type.normalizer
  normalized = normalizer.normalize(source, source_type, context)
  [type.denormalizer.denormalize(normalized, type, context), true]
end
request_reassembly(source, type, context) click to toggle source

@param [Object] source @param [AMA::Entity::Mapper::Type] type @param [AMA::Entity::Mapper::Context] context @return [Array<Object, TrueClass, FalseClass>]

# File lib/ama-entity-mapper/engine/recursive_mapper.rb, line 142
def request_reassembly(source, type, context)
  # TODO: make reassembly optional
  reassemble(source, type, context)
end