How to automatically build a hash

Sorry if the title misled you, but I couldn’t come up with anything better and equally short. A YAML configuration file can be loaded into a hash, and this works pretty much out of the box. Sometimes, however, I need to also add configuration on the fly to the same hash. Then it becomes a little tedious to manually build missing keys, so I created my own Ando::Hash, which is a thin wrapper around Ruby’s Hash.

module Ando

  class Hash

    def initialize(hash=nil)
      hash ||= {}
      raise ArgumentError, "Expected a Hash object. (#{hash.class})" unless hash.respond_to?(:has_key?)
      @hash = hash
    end

    def [](*args)
      get_last_value(args)
    end

    def []=(*args)
      value = args.pop
      if args.first
        set_last_value(args, value)
      else
        initialize(value)
      end
      value # this is to preserve assignment semantics
    end

    protected

    # hook into @hash
    def get_previous_hash(path)
      current = @hash
      path[0..-2].each do |key|
        current = current[key] || current[key.is_a?(Symbol) ? key.to_s : key.to_sym]
      end
      current
    end

    # initialize intermediate keys to a hash
    def init_last(path, current = @hash)
      key = path.first
      return unless key
      raise ArgumentError, "Expected a Hash object. (#{current.class})" unless current.respond_to?(:has_key?)
      (current[key] = {}) unless (current.has_key?(key) && current[key].respond_to?(:has_key?))
      init_last(path[1..-1], current[key])
    end

    # set the given value to the last key
    def set_last_value(path, value)
      init_last(path)
      previous            = get_previous_hash(path)
      previous[path.last] = value
    end

    # get the value of the last key
    def get_last_value(path)
      previous = get_previous_hash(path)
      if previous.respond_to?(:has_key?) && path.last
        key = path.last
        return previous[key] || previous[key.is_a?(Symbol) ? key.to_s : key.to_sym]
      end
      previous
    end

  end

end

And here is a very short usage example.

>> h = Ando::Hash.new({:a => 1, :b => {:c => 3}})
=> #<Ando::Hash:0x007fd96a87d1b0 @hash={:a=>1, :b=>{:c=>3}}>
>> h[:b]
=> {:c=>3}
>> h[:b, :c]
=> 3
>> h[:a, :b, :c, :d, :e] = 5
=> 5
>> h[:a, :b, :c, :d, :e]
=> 5
>> h[:b]
=> {:c=>3}
>> h[:a]
=> {:b=>{:c=>{:d=>{:e=>5}}}}
>> h
=> #<Ando::Hash:0x007fd96a87d1b0 @hash={:a=>{:b=>{:c=>{:d=>{:e=>5}}}}, :b=>{:c=>3}}>

Leave a Reply

Your email address will not be published. Required fields are marked *