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