lib/hash_mapper.rb in ismasan-hash_mapper-0.0.2 vs lib/hash_mapper.rb in ismasan-hash_mapper-0.0.3
- old
+ new
@@ -1,110 +1,139 @@
$:.unshift(File.dirname(__FILE__)) unless
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
+# This allows us to call blah(&:some_method) instead of blah{|i| i.some_method }
+unless Symbol.instance_methods.include?('to_proc')
+ class Symbol
+ def to_proc
+ Proc.new {|obj| obj.send(self) }
+ end
+ end
+end
+
module HashMapper
- VERSION = '0.0.2'
+ VERSION = '0.0.3'
def maps
@maps ||= []
end
- def map(from, to, &blk)
- to.filter = blk if block_given?
- self.maps << [from, to]
+ def map(from, to, using=nil, &filter)
+ self.maps << Map.new(from, to, using)
+ to.filter = filter if block_given? # Useful if just one block given
end
- def from(path, coerce_method = nil)
- PathMap.new(path, coerce_method)
+ def from(path, &filter)
+ path_map = PathMap.new(path)
+ path_map.filter = filter if block_given? # Useful if two blocks given
+ path_map
end
alias :to :from
- def translate(incoming_hash)
+ def using(mapper_class)
+ mapper_class
+ end
+
+ def normalize(a_hash)
+ perform_hash_mapping a_hash, :normalize
+ end
+
+ def denormalize(a_hash)
+ perform_hash_mapping a_hash, :denormalize
+ end
+
+ protected
+
+ def perform_hash_mapping(a_hash, meth)
output = {}
- incoming_hash = simbolize_keys(incoming_hash)
- maps.each do |path_from, path_to|
- path_to.inject(output){|h,e|
- if h[e]
- h[e]
- else
- h[e] = (e == path_to.last ? path_to.resolve_value(path_from, incoming_hash) : {})
- end
- }
+ a_hash = symbolize_keys(a_hash)
+ maps.each do |m|
+ m.process_into(output, a_hash, meth)
end
output
end
# from http://www.geekmade.co.uk/2008/09/ruby-tip-normalizing-hash-keys-as-symbols/
#
- def simbolize_keys(hash)
+ def symbolize_keys(hash)
hash.inject({}) do |options, (key, value)|
options[(key.to_sym rescue key) || key] = value
options
end
end
- # This allows us to pass mapper classes as block arguments
+ # Contains PathMaps
+ # Makes them interact
#
- def to_proc
- Proc.new{|*args| self.translate(*args)}
+ class Map
+
+ attr_reader :path_from, :path_to, :delegated_mapper
+
+ def initialize(path_from, path_to, delegated_mapper = nil)
+ @path_from, @path_to, @delegated_mapper = path_from, path_to, delegated_mapper
+ end
+
+ def process_into(output, incoming_hash, meth = :normalize)
+ paths = [path_from, path_to]
+ paths.reverse! unless meth == :normalize
+ value = paths.first.inject(incoming_hash){|h,e| h[e]}
+ value = delegate_to_nested_mapper(value, meth) if delegated_mapper
+ add_value_to_hash!(output, paths.last, value)
+ end
+
+ protected
+
+ def delegate_to_nested_mapper(value, meth)
+ v = if value.kind_of?(Array)
+ value.map {|h| delegated_mapper.send(meth, h)}
+ else
+ delegated_mapper.send(meth, value)
+ end
+ end
+
+ def add_value_to_hash!(hash, path, value)
+ path.inject(hash) do |h,e|
+ if h[e]
+ h[e]
+ else
+ h[e] = (e == path.last ? path.apply_filter(value) : {})
+ end
+ end
+ end
+
end
+ # contains array of path segments
+ #
class PathMap
-
include Enumerable
attr_reader :segments
-
attr_writer :filter
- def initialize(path, coerce_method = nil)
+ def initialize(path)
@path = path.dup
- @coerce_method = coerce_method
- @index = extract_array_index!(path)
@segments = parse(path)
@filter = lambda{|value| value}# default filter does nothing
end
- def resolve_value(another_path, incoming_hash)
- coerce another_path.extract_from(incoming_hash)
+ def apply_filter(value)
+ @filter.call(value)
end
- def coerce(value)
- value = @filter.call(value)
- return value unless @coerce_method
- value.send(@coerce_method) rescue value
- end
-
- def extract_from(incoming_hash)
- value = inject(incoming_hash){|hh,ee| hh[ee]}
- return value unless @index
- value.to_a[@index]
- end
-
def each(&blk)
@segments.each(&blk)
end
def last
@segments.last
end
private
- def extract_array_index!(path)
- path.gsub! /(\[[0-9]+\])/, ''
- if idx = $1
- idx.gsub(/(\[|\])/, '').to_i
- else
- nil
- end
- end
-
def parse(path)
- p = path.split('/')
- p.shift
- p.collect{|e| e.to_sym}
+ path.sub(/^\//,'').split('/').map(&:to_sym)
end
end
end
\ No newline at end of file