Skip to content

Commit

Permalink
Finalized first low-IQ acceptance test along with some amendments
Browse files Browse the repository at this point in the history
  • Loading branch information
etki committed Jul 29, 2017
1 parent 6dd7e2a commit df8fdb2
Show file tree
Hide file tree
Showing 17 changed files with 393 additions and 74 deletions.
13 changes: 11 additions & 2 deletions lib/mapper.rb
Expand Up @@ -3,6 +3,7 @@
require_relative 'mapper/version'
require_relative 'mapper/engine'
require_relative 'mapper/type/concrete'
require_relative 'mapper/type/registry'

module AMA
module Entity
Expand All @@ -11,7 +12,7 @@ class Mapper
attr_reader :engine

def initialize(engine = nil)
@engine = engine || Engine.new
@engine = engine || Engine.new(Type::Registry.new.with_default_types)
end

def types
Expand All @@ -27,12 +28,20 @@ def resolve(type)
@engine.resolve(type)
end

def map(input, *types, **options)
@engine.map(input, *types, **options)
end

def [](klass)
@engine.registry[klass]
end

class << self
def initialize
@mapper = Mapper.new
end

def swap(mapper)
def handler=(mapper)
@mapper = mapper
end

Expand Down
2 changes: 1 addition & 1 deletion lib/mapper/api/default/denormalizer.rb
Expand Up @@ -36,7 +36,7 @@ def denormalize(source, type, context = nil)

def validate_source!(source, type, context)
return if source.is_a?(Hash)
message = "Expected hash, #{source.class} provided " \
message = "Expected Hash, #{source.class} provided " \
"(while denormalizing #{type})"
mapping_error(message, context: context)
end
Expand Down
3 changes: 2 additions & 1 deletion lib/mapper/api/default/enumerator.rb
Expand Up @@ -18,10 +18,11 @@ class Enumerator < API::Injector

# @param [Object] entity
# @param [AMA::Entity::Mapper::Type] type
# @param [AMA::Entity::Mapper::Context] context
# @param [AMA::Entity::Mapper::Context] _context
def enumerate(entity, type, _context = nil)
::Enumerator.new do |yielder|
type.attributes.values.reject(&:virtual).each do |attribute|
next unless object_variable_exists(entity, attribute.name)
value = object_variable(entity, attribute.name)
segment = Path::Segment.attribute(attribute.name)
yielder << [attribute, value, segment]
Expand Down
4 changes: 2 additions & 2 deletions lib/mapper/dsl.rb
Expand Up @@ -10,10 +10,10 @@ class Mapper
module DSL
class << self
def included(klass)
klass.class_eval do
klass.singleton_class.instance_eval do
include ClassMethods
self.mapper = Mapper.handler
end
klass.mapper = Mapper.handler
end
end
end
Expand Down
27 changes: 21 additions & 6 deletions lib/mapper/dsl/class_methods.rb
Expand Up @@ -19,7 +19,7 @@ def mapper=(mapper)

# @return [AMA::Entity::Mapper::Type::Concrete]
def bound_type
@mapper.types[self]
mapper[self]
end

# @param [String, Symbol] name
Expand All @@ -30,6 +30,12 @@ def bound_type
def attribute(name, *types, **options)
types = types.map { |type| @mapper.resolve(type) }
bound_type.attribute!(name, *types, **options)
define_method(name) do
instance_variable_get("@#{name}")
end
define_method("#{name}=") do |value|
instance_variable_set("@#{name}", value)
end
end

# @param [String, Symbol] id
Expand All @@ -38,13 +44,22 @@ def parameter(id)
bound_type.parameter!(id)
end

%i[factory enumerator injector normalizer denormalizer].each do |m|
define_method m do |handler|
bound_type.send(m, handler)
handlers = {
factory: :create,
enumerator: :enumerate,
injector: :inject,
normalizer: :normalize,
denormalizer: :denormalize
}
handlers.each do |name, method_name|
setter_name = "#{name}="
define_method setter_name do |handler|
wrapper = API::Wrapper.const_get(name.capitalize).new(handler)
bound_type.send(setter_name, wrapper)
end

define_method "#{m}_block" do |&block|
bound_type.send(m, &block)
define_method "#{name}_block" do |&block|
send(setter_name, method_object(method_name, &block))
end
end
end
Expand Down
14 changes: 10 additions & 4 deletions lib/mapper/engine.rb
Expand Up @@ -17,9 +17,14 @@ class Mapper
class Engine
include Mixin::Errors

# @!attribute [r] registry
# @return [Type::Registry]
attr_reader :registry
# @!attribute [r] resolver
# @return [Type::Resolver]
attr_reader :resolver

# @param [Type::Registry] registry
def initialize(registry = nil)
@registry = registry || Type::Registry.new
@resolver = Type::Resolver.new(@registry)
Expand All @@ -43,8 +48,10 @@ def map(source, *types, **context_options)
end
end

def resolve(type)
@resolver.resolve(type)
# Resolves provided definition, creating type hierarchy.
# @param [Array<Class, Module, Type, Array>] definition
def resolve(definition)
@resolver.resolve(definition)
end

private
Expand Down Expand Up @@ -105,8 +112,7 @@ def map_attributes(entity, type, context)
instance = type.factory.create(type, entity, context)
enumerator = type.enumerator.enumerate(entity, type, context)
enumerator.each do |attribute, value, segment = nil|
segment = Path::Segment.attribute(attribute.name) unless segment
next_context = context.advance(segment)
next_context = segment ? context.advance(segment) : context
unless attribute.satisfied_by?(value)
value = recursive_map(value, attribute.types, next_context)
end
Expand Down
4 changes: 4 additions & 0 deletions lib/mapper/mixin/reflection.rb
Expand Up @@ -44,6 +44,10 @@ def object_variable(object, name)
object.instance_variable_get(name)
end

def object_variable_exists(object, name)
object.instance_variables.include?("@#{name}".to_sym)
end

def install_object_method(object, name, handler)
compliance_error('Handler not provided') unless handler
object.define_singleton_method(name, &handler)
Expand Down
4 changes: 4 additions & 0 deletions lib/mapper/path.rb
Expand Up @@ -78,6 +78,10 @@ def size
@segments.size
end

def segments
@segments.clone
end

# @return [Array<AMA::Entity::Mapper::Path::Segment>]
def to_a
@segments.clone
Expand Down
Expand Up @@ -6,22 +6,22 @@ class Mapper
class Type
module Aux
# Simple class to store paired data items
class Pair
attr_accessor :left
attr_accessor :right
class HashTuple
attr_accessor :key
attr_accessor :value

def initialize(left: nil, right: nil)
@left = left
@right = right
def initialize(key: nil, value: nil)
@key = key
@value = value
end

def hash
@left.hash ^ @right.hash
@key.hash ^ @value.hash
end

def eql?(other)
return false unless other.is_a?(Pair)
@left == other.left && @right == other.right
return false unless other.is_a?(HashTuple)
@key == other.key && @value == other.value
end

def ==(other)
Expand Down
33 changes: 33 additions & 0 deletions lib/mapper/type/hardwired/hash_tuple_type.rb
@@ -0,0 +1,33 @@
# frozen_string_literal: true

require_relative '../concrete'
require_relative '../aux/hash_tuple'

module AMA
module Entity
class Mapper
class Type
module Hardwired
# Pair class definition
class HashTupleType < Concrete
def initialize
super(Aux::HashTuple)

attribute!(:key, parameter!(:K))
attribute!(:value, parameter!(:V))

enumerator_block do |entity, type, *|
::Enumerator.new do |yielder|
yielder << [type.attributes[:key], entity.key, nil]
yielder << [type.attributes[:value], entity.value, nil]
end
end
end

INSTANCE = new
end
end
end
end
end
end
14 changes: 7 additions & 7 deletions lib/mapper/type/hardwired/hash_type.rb
Expand Up @@ -4,8 +4,8 @@
require_relative '../../path/segment'
require_relative '../../mixin/errors'
require_relative '../../mixin/reflection'
require_relative 'pair_type'
require_relative '../aux/pair'
require_relative 'hash_tuple_type'
require_relative '../aux/hash_tuple'

module AMA
module Entity
Expand All @@ -29,10 +29,10 @@ def initialize
private

def define_attribute
type = PairType.new
type = HashTupleType.new
type = type.resolve(
type.parameter!(:L) => parameter!(:K),
type.parameter!(:R) => parameter!(:V)
type.parameter!(:K) => parameter!(:K),
type.parameter!(:V) => parameter!(:V)
)
attribute!(:_tuple, type, virtual: true)
end
Expand All @@ -41,7 +41,7 @@ def define_enumerator
enumerator_block do |entity, type, *|
::Enumerator.new do |yielder|
entity.each do |key, value|
tuple = Aux::Pair.new(left: key, right: value)
tuple = Aux::HashTuple.new(key: key, value: value)
attribute = type.attributes[:_tuple]
yielder << [attribute, tuple, Path::Segment.index(key)]
end
Expand All @@ -51,7 +51,7 @@ def define_enumerator

def define_injector
injector_block do |entity, _, _, tuple, *|
entity[tuple.left] = tuple.right
entity[tuple.key] = tuple.value
end
end

Expand Down
26 changes: 0 additions & 26 deletions lib/mapper/type/hardwired/pair_type.rb

This file was deleted.

4 changes: 2 additions & 2 deletions lib/mapper/type/registry.rb
Expand Up @@ -5,7 +5,7 @@
require_relative 'parameter'
require_relative 'hardwired/enumerable_type'
require_relative 'hardwired/hash_type'
require_relative 'hardwired/pair_type'
require_relative 'hardwired/hash_tuple_type'
require_relative 'hardwired/set_type'
require_relative 'hardwired/primitive_type'

Expand All @@ -28,7 +28,7 @@ def with_default_types
register(Hardwired::EnumerableType::INSTANCE)
register(Hardwired::HashType::INSTANCE)
register(Hardwired::SetType::INSTANCE)
register(Hardwired::PairType::INSTANCE)
register(Hardwired::HashTupleType::INSTANCE)
Hardwired::PrimitiveType::ALL.each do |type|
register(type)
end
Expand Down

0 comments on commit df8fdb2

Please sign in to comment.