Handlers are specific logic processors that allow to customize entity processing.

Some common rules apply for all processors: they can be set as block or object (latter may help in avoiding block hell), they have always accept type instance (because it’s parameters may be resolved to something not yet known at the moment of processor definition) and they are wrapped in safety wrappers, so invalid signature will be reported, and errors would be wrapped in description errors with paths where that thing has happened.

Normalizer

Normalizer is a processor that takes entity and emits low-level representation of that entity - Hash, Array, String, or something like that. Normalizer can be set as standalone object or simple block with same signature:

normalizer = Object.new.tap do |instance|
  instance.define_singleton_method :normalize do |entity, type, context|
    data = yield(entity, type, context)
    data[:private] = nil
    data[:owner] = data[:owner][:id]
    data
  end
end
normalizer_block do |entity, type, context|
  # ...
end

Normalizer is provided with a block that allows for fallback to default processing. This may be used to perform pre- or post-processing.

Default normalizer implementation simply represents all instance variables as hash.

Denormalizer

Denormalizer returns entity with values from low-level structure. As with normalizer, it is fed with a block that may be used for fallback processing:

denormalizer = Object.new.tap do |instance|
  instance.define_singleton_method :denormalize do |input, type, context|
    yield(entity, input, type, context)
    entity.id ||= input[:user_id]
    entity
  end
end
denormalizer_block do |input, type, context, &block|
  # ...
end

Default denormalizer accepts only hash and sets instance variables / calls setter methods using it’s contents.

Common usage for denomralization is input unification:

denormalizer_block do |input, type, context, &block|
  input = {} if input.nil?
  input = { name: input } if input.is_a?(String)
  block.call(input, type, context)
end

Factory

Factory is plain simple and used to create new instances:

factory = Object.new.tap do |instance|
  instance.define_singleton_method :create do |type, input, context|
    type.type.new
  end
end
factory_block do |type, input, context|
  # ...
end

Default factory creates entity using Class#new and sets default values for attributes with default values.

Enumerator

Enumerator is a class that takes entity and returns standard ruby enumerator, which emits triplets of attribute, value, and segment. It allows mapper to delegate attribute location and allows resolution of complex cases with virtual types. It is used to inspect entity contents:

type.enumerator(entity).each do |attribute, value, segment|
  local_ctx = context.advance(segment)
  puts "#{local_ctx.path} value: #{value}" 
end

Setting examples:

enumerator = Object.new.tap do |instance|
  instance.define_singleton_method :enumerate do |entity, type, context = nil|
    ::Enumerator.new do |y|
      type.attributes.each do |attribute|
        segment = ::AMA::Entity::Mapper::Path::Segment.attribute(attribute.name)
        y << [attribute, entity.send(attribute.name), segment]
      end
    end
  end
end
enumerator_block do |entity, type, context = nil|
  # ...
end

Injector

Injector is enumerator counterpart: it takes in entity and attribute and sets the latter on the former

injector = Object.new.tap do |instance|
  instance.define_singleton_method :inject do |entity, type, attribute, value|
    entity.send("#{attribute.name}=", value)
  end
end
injector_block do |entity, type, attribute, value, context|
  # ...
end

The argument list is quite huge, but it is still easier that way than dealing with intermediate object.

Attribute validator

Allows to validate attribute correctness, returns list of violations as strings:

validator = Object.new.tap do |instance|
  instance.define_singleton_method :validate do |value, attribute, context|
    return [] if attribute.values.include?(value)
    ["Values #{attribute.values} don't contain #{value}"]
  end
end
validator_block do |value, attribute, context|
  # ...
end

Entity validator

The very same thing for entities (usually there is no need in that).

validator = Object.new.tap do |instance|
  instance.define_singleton_method :validate do |entity, type, context|
    if entity.policy == :read and entity.roles.include?(:admin)
      return ['Something\'s misty here!']
    end
    []
  end
end

```ruby validator_block do |value, attribute, context| # … end