class DeepHash

A magical hash: allows deep-nested access and proper merging of settings from different sources

Constants

DEEP_MERGER

Public Class Methods

new(constructor = {}) click to toggle source

@param constructor<Object>

The default value for the DeepHash. Defaults to an empty hash.
If constructor is a Hash, adopt its values.
Calls superclass method
# File lib/configliere/deep_hash.rb, line 10
def initialize(constructor = {})
  if constructor.is_a?(Hash)
    super()
    update(constructor) unless constructor.empty?
  else
    super(constructor)
  end
end

Public Instance Methods

[](attr) click to toggle source

Gets a member value.

Given a deep key (one that contains ‘.’), uses it as a chain of hash memberships. Otherwise calls the normal hash member getter

@example

foo = DeepHash.new({ :hi => 'there', :howdy => { :doody => 3 } })
foo['howdy.doody'] # => 3
foo['hi']          # => 'there'
foo[:hi]           # => 'there'
Calls superclass method
# File lib/configliere/deep_hash.rb, line 247
def [] attr
  attr = convert_key(attr)
  attr.is_a?(Array) ? deep_get(*attr) : super(attr)
end
[]=(attr, val) click to toggle source

Sets a member value.

Given a deep key (one that contains ‘.’), uses it as a chain of hash memberships. Otherwise calls the normal hash member setter

@example

foo = DeepHash.new :hi => 'there'
foo['howdy.doody'] = 3
foo # => { :hi => 'there', :howdy => { :doody => 3 } }
Calls superclass method
# File lib/configliere/deep_hash.rb, line 229
def []= attr, val
  attr = convert_key(attr)
  val  = convert_value(val)
  attr.is_a?(Array) ? deep_set(*(attr | [val])) : super(attr, val)
end
assert_valid_keys(*valid_keys) click to toggle source

Validate all keys in a hash match *valid keys, raising ArgumentError on a mismatch. Note that keys are NOT treated indifferently, meaning if you use strings for keys but assert symbols as keys, this will fail.

Examples

{ :name => "Rob", :years => "28" }.assert_valid_keys(:name, :age) # => raises "ArgumentError: Unknown key(s): years"
{ :name => "Rob", :age => "28" }.assert_valid_keys("name", "age") # => raises "ArgumentError: Unknown key(s): name, age"
{ :name => "Rob", :age => "28" }.assert_valid_keys(:name, :age) # => passes, raises nothing
# File lib/configliere/deep_hash.rb, line 214
def assert_valid_keys(*valid_keys)
  unknown_keys = keys - [valid_keys].flatten
  raise(ArgumentError, "Unknown key(s): #{unknown_keys.join(", ")}") unless unknown_keys.empty?
end
compact() click to toggle source

remove all key-value pairs where the value is nil

# File lib/configliere/deep_hash.rb, line 145
def compact
  reject{|key,val| val.nil? }
end
compact!() click to toggle source

Replace the hash with its compacted self

# File lib/configliere/deep_hash.rb, line 151
def compact!
  replace(compact)
end
deep_delete(*args) click to toggle source

Treat hash as tree of hashes:

x = { :a => :val, :subhash => { :a => :val1, :b => :val2 } }
x.deep_delete(:subhash, :a)
#=> :val
x
#=> { :a => :val, :subhash => { :b => :val2 } }
# File lib/configliere/deep_hash.rb, line 345
def deep_delete *args
  last_key  = args.pop                                   # key to delete
  last_hsh  = args.empty? ? self : (deep_get(*args)||{}) # hsh containing that key
  last_hsh.delete(last_key)
end
deep_get(*args) click to toggle source

Treat hash as tree of hashes:

x = { :a => :val_a, :subhash => { :b => :val_b } }
x.deep_get(:a)
# => :val_a
x.deep_get(:subhash, :c)
# => nil
x.deep_get(:subhash, :c, :f)
# => nil
x.deep_get(:subhash, :b)
# => nil
# File lib/configliere/deep_hash.rb, line 328
def deep_get *args
  last_key = args.pop
  # dig down to last subtree (building out if necessary)
  hsh = args.inject(self){|h, k| h[k] || self.class.new }
  # get leaf value
  hsh[last_key]
end
deep_merge(hsh2) click to toggle source

Merge hashes recursively. Nothing special happens to array values

x = { :subhash => { :a => :val_from_x, :b => :only_in_x, :c => :only_in_x }, :scalar => :scalar_from_x}
y = { :subhash => { :a => :val_from_y, :d => :only_in_y },                   :scalar => :scalar_from_y }
x.deep_merge y
=> {:subhash=>{:a=>:val_from_y, :b=>:only_in_x, :c=>:only_in_x, :d=>:only_in_y}, :scalar=>:scalar_from_y}
y.deep_merge x
=> {:subhash=>{:a=>:val_from_x, :b=>:only_in_x, :c=>:only_in_x, :d=>:only_in_y}, :scalar=>:scalar_from_x}

Nil values always lose.

x = {:subhash=>{:nil_in_x=>nil, a=>:val_a}, :nil_in_x=>nil}
y = {:subhash=>{:nil_in_x=>5},              :nil_in_x=>5}
y.deep_merge x
=> {:subhash=>{:a=>:val_a, :nil_in_x=>5}, :nil_in_x=>5}
x.deep_merge y
=> {:subhash=>{:a=>:val_a, :nil_in_x=>5}, :nil_in_x=>5}
# File lib/configliere/deep_hash.rb, line 283
def deep_merge hsh2
  merge hsh2, &DeepHash::DEEP_MERGER
end
deep_merge!(hsh2) click to toggle source
# File lib/configliere/deep_hash.rb, line 287
def deep_merge! hsh2
  update hsh2, &DeepHash::DEEP_MERGER
  self
end
deep_set(*args) click to toggle source

Treat hash as tree of hashes:

x = { :a => :val, :subhash => { :b => :val_b } }
x.deep_set(:subhash, :cat, :hat)
# => { :a => :val, :subhash => { :b => :val_b,   :cat => :hat } }
x.deep_set(:subhash, :b, :newval)
# => { :a => :val, :subhash => { :b => :newval, :cat => :hat } }
# File lib/configliere/deep_hash.rb, line 302
def deep_set *args
  val      = args.pop
  last_key = args.pop
  # dig down to last subtree (building out if necessary)
  hsh = self
  args.each  do |key|
    hsh.regular_writer(key, self.class.new) unless hsh.has_key?(key)
    hsh = hsh[key]
  end
  # set leaf value
  hsh[last_key] = val
end
delete(attr) click to toggle source

@param key<Object>

The key to delete from the DeepHash.
Calls superclass method
# File lib/configliere/deep_hash.rb, line 59
def delete attr
  attr = convert_key(attr)
  attr.is_a?(Array) ? deep_delete(*attr) : super(attr)
end
extract!(*keys) click to toggle source

Removes the given keys from the hash Returns a hash containing the removed key/value pairs

@example

hsh = {:a => 1, :b => 2, :c => 3, :d => 4}
hsh.extract!(:a, :b)
# => {:a => 1, :b => 2}
hsh
# => {:c => 3, :d =>4}
# File lib/configliere/deep_hash.rb, line 200
def extract!(*keys)
  result = self.class.new
  keys.each{|key| result[key] = delete(key) }
  result
end
fetch(key, *extras) click to toggle source

@param key<Object> The key to fetch. This will be run through convert_key. @param *extras<Array> Default value.

@return [Object] The value at key or the default value.

Calls superclass method
# File lib/configliere/deep_hash.rb, line 45
def fetch(key, *extras)
  super(convert_key(key), *extras)
end
has_key?(key)
Alias for: key?
include?(key)

def include? def has_key? def member?

Alias for: key?
key?(key) click to toggle source

@param key<Object> The key to check for. This will be run through convert_key.

@return [Boolean] True if the key exists in the mash.

Calls superclass method
# File lib/configliere/deep_hash.rb, line 25
def key?(key)
  attr = convert_key(key)
  if attr.is_a?(Array)
    fk = attr.shift
    attr = attr.first if attr.length == 1
    super(fk) && (self[fk].key?(attr)) rescue nil
  else
    super(attr)
  end
end
Also aliased as: include?, has_key?, member?
member?(key)
Alias for: key?
merge(hash, &block) click to toggle source

@param hash<Hash> The hash to merge with the deep_hash.

@return [DeepHash] A new deep_hash with the hash values merged in.

# File lib/configliere/deep_hash.rb, line 72
def merge(hash, &block)
  self.dup.update(hash, &block)
end
merge!(other_hash, &block)
Alias for: update
reverse_merge(other_hash) click to toggle source

Allows for reverse merging two hashes where the keys in the calling hash take precedence over those in the other_hash. This is particularly useful for initializing an option hash with default values:

def setup(options = {})
  options.reverse_merge! :size => 25, :velocity => 10
end

Using merge, the above example would look as follows:

def setup(options = {})
  { :size => 25, :velocity => 10 }.merge(options)
end

The default :size and :velocity are only set if the options hash passed in doesn’t already have the respective key.

# File lib/configliere/deep_hash.rb, line 107
def reverse_merge(other_hash)
  self.class.new(other_hash).merge!(self)
end
reverse_merge!(other_hash) click to toggle source

Performs the opposite of merge, with the keys and values from the first hash taking precedence over the second. Modifies the receiver in place.

# File lib/configliere/deep_hash.rb, line 113
def reverse_merge!(other_hash)
  merge!( other_hash ){|k,o,n| convert_value(o) }
end
slice(*keys) click to toggle source

Slice a hash to include only the given keys. This is useful for limiting an options hash to valid keys before passing to a method:

def search(criteria = {})
  assert_valid_keys(:mass, :velocity, :time)
end

search(options.slice(:mass, :velocity, :time))

If you have an array of keys you want to limit to, you should splat them:

valid_keys = [:mass, :velocity, :time]
search(options.slice(*valid_keys))
# File lib/configliere/deep_hash.rb, line 168
def slice(*keys)
  keys = keys.map!{|key| convert_key(key) } if respond_to?(:convert_key)
  hash = self.class.new
  keys.each { |k| hash[k] = self[k] if has_key?(k) }
  hash
end
slice!(*keys) click to toggle source

Replaces the hash with only the given keys. Returns a hash containing the removed key/value pairs @example

hsh = {:a => 1, :b => 2, :c => 3, :d => 4}
hsh.slice!(:a, :b)
# => {:c => 3, :d =>4}
hsh
# => {:a => 1, :b => 2}
# File lib/configliere/deep_hash.rb, line 183
def slice!(*keys)
  keys = keys.map!{|key| convert_key(key) } if respond_to?(:convert_key)
  omit = slice(*self.keys - keys)
  hash = slice(*keys)
  replace(hash)
  omit
end
stringify_keys() click to toggle source

Return a new hash with all top-level keys converted to strings.

@return [Hash]

# File lib/configliere/deep_hash.rb, line 134
def stringify_keys
  hsh = Hash.new(default)
  self.each do |key, val|
    hsh[key.to_s] = val
  end
  hsh
end
symbolize_keys() click to toggle source

This DeepHash with all its keys converted to symbols, as long as they respond to to_sym. (this is always true for a deep_hash)

@return [DeepHash] A copy of this deep_hash.

# File lib/configliere/deep_hash.rb, line 121
def symbolize_keys
  dup.symbolize_keys!
end
symbolize_keys!() click to toggle source

This DeepHash with all its keys converted to symbols, as long as they respond to to_sym. (this is always true for a deep_hash)

@return [DeepHash] This deep_hash unchanged.

# File lib/configliere/deep_hash.rb, line 129
def symbolize_keys!; self end
to_hash() click to toggle source

@return [Hash] converts to a plain hash.

# File lib/configliere/deep_hash.rb, line 65
def to_hash
  Hash.new(default).merge(self)
end
update(other_hash, &block) click to toggle source

@param other_hash<Hash>

A hash to update values in the deep_hash with. The keys and the values will be
converted to DeepHash format.

@return [DeepHash] The updated deep_hash.

# File lib/configliere/deep_hash.rb, line 81
def update(other_hash, &block)
  deep_hash = self.class.new
  other_hash.each_pair do |key, value|
    val = convert_value(value)
    deep_hash[key] = val
  end
  regular_update(deep_hash, &block)
end
Also aliased as: merge!
values_at(*indices) click to toggle source

@param *indices<Array>

The keys to retrieve values for. These will be run through +convert_key+.

@return [Array] The values at each of the provided keys

# File lib/configliere/deep_hash.rb, line 53
def values_at(*indices)
  indices.collect{|key| self[convert_key(key)]}
end

Protected Instance Methods

convert_key(attr) click to toggle source

@attr key<Object> The key to convert.

@attr [Object]

The converted key. A dotted attr ('moon.cheese.type') becomes
an array of sequential keys for deep_set and deep_get

@private

# File lib/configliere/deep_hash.rb, line 359
def convert_key(attr)
  case
  when attr.to_s.include?('.')   then attr.to_s.split(".").map{|sub_attr| sub_attr.to_sym }
  when attr.is_a?(Array)         then attr.map{|sub_attr| sub_attr.to_sym }
  else                                attr.to_sym
  end
end
convert_value(value) click to toggle source

@param value<Object> The value to convert.

@return [Object]

The converted value. A Hash or an Array of hashes, will be converted to
their DeepHash equivalents.

@private

# File lib/configliere/deep_hash.rb, line 375
def convert_value(value)
  if value.class == Hash   then self.class.new(value)
  elsif value.is_a?(Array) then value.collect{|e| convert_value(e) }
  else                          value
  end
end