Class: DeepHash

Inherits:
Hash
  • Object
show all
Defined in:
lib/configliere/deep_hash.rb

Overview

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

Direct Known Subclasses

Configliere::ParamParent

Constant Summary collapse

DEEP_MERGER =

lambda for recursive merges

proc do |key,v1,v2|
  if (v1.respond_to?(:update) && v2.respond_to?(:update))
    v1.update(v2.reject{|kk,vv| vv.nil? }, &DeepHash::DEEP_MERGER)
  elsif v2.nil?
    v1
  else
    v2
  end
end

Instance Method Summary collapse

Constructor Details

#initialize(constructor = {}) ⇒ DeepHash

Returns a new instance of DeepHash.

Parameters:

  • constructor (Object) (defaults to: {})

    The default value for the DeepHash. Defaults to an empty hash. If constructor is a Hash, adopt its values.



10
11
12
13
14
15
16
17
# 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

Instance Method Details

#[](attr) ⇒ Object

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

Examples:

foo = DeepHash.new({ :hi => 'there', :howdy => { :doody => 3 } })
foo['howdy.doody'] # => 3
foo['hi']          # => 'there'
foo[:hi]           # => 'there'


247
248
249
250
# 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) ⇒ Object

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

Examples:

foo = DeepHash.new :hi => 'there'
foo['howdy.doody'] = 3
foo # => { :hi => 'there', :howdy => { :doody => 3 } }


229
230
231
232
233
# 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) ⇒ Object

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

Raises:

  • (ArgumentError)


214
215
216
217
# 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

#compactObject

remove all key-value pairs where the value is nil



145
146
147
# File 'lib/configliere/deep_hash.rb', line 145

def compact
  reject{|key,val| val.nil? }
end

#compact!Object

Replace the hash with its compacted self



151
152
153
# File 'lib/configliere/deep_hash.rb', line 151

def compact!
  replace(compact)
end

#deep_delete(*args) ⇒ Object

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 } }


345
346
347
348
349
# 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) ⇒ Object

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


328
329
330
331
332
333
334
# 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) ⇒ Object

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}


283
284
285
# File 'lib/configliere/deep_hash.rb', line 283

def deep_merge hsh2
  merge hsh2, &DeepHash::DEEP_MERGER
end

#deep_merge!(hsh2) ⇒ Object



287
288
289
290
# File 'lib/configliere/deep_hash.rb', line 287

def deep_merge! hsh2
  update hsh2, &DeepHash::DEEP_MERGER
  self
end

#deep_set(*args) ⇒ Object

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 } }


302
303
304
305
306
307
308
309
310
311
312
313
# 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) ⇒ Object

Parameters:

  • key (Object)

    The key to delete from the DeepHash.



59
60
61
62
# 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) ⇒ Object

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

Examples:

hsh = {:a => 1, :b => 2, :c => 3, :d => 4}
hsh.extract!(:a, :b)
# => {:a => 1, :b => 2}
hsh
# => {:c => 3, :d =>4}


200
201
202
203
204
# 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) ⇒ Object

Returns The value at key or the default value.

Parameters:

  • key (Object)

    The key to fetch. This will be run through convert_key.

  • *extras (Array)

    Default value.

Returns:

  • (Object)

    The value at key or the default value.



45
46
47
# File 'lib/configliere/deep_hash.rb', line 45

def fetch(key, *extras)
  super(convert_key(key), *extras)
end

#key?(key) ⇒ Boolean Also known as: include?, has_key?, member?

Returns True if the key exists in the mash.

Parameters:

  • key (Object)

    The key to check for. This will be run through convert_key.

Returns:

  • (Boolean)

    True if the key exists in the mash.



25
26
27
28
29
30
31
32
33
34
# 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

#merge(hash, &block) ⇒ DeepHash

Returns A new deep_hash with the hash values merged in.

Parameters:

  • hash (Hash)

    The hash to merge with the deep_hash.

Returns:

  • (DeepHash)

    A new deep_hash with the hash values merged in.



72
73
74
# File 'lib/configliere/deep_hash.rb', line 72

def merge(hash, &block)
  self.dup.update(hash, &block)
end

#regular_updateObject



20
# File 'lib/configliere/deep_hash.rb', line 20

alias_method :regular_update, :update

#regular_writerObject



19
# File 'lib/configliere/deep_hash.rb', line 19

alias_method :regular_writer, :[]=

#reverse_merge(other_hash) ⇒ Object

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.



107
108
109
# 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) ⇒ Object

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



113
114
115
# 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) ⇒ Object

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))



168
169
170
171
172
173
# 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) ⇒ Object

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

Examples:

hsh = {:a => 1, :b => 2, :c => 3, :d => 4}
hsh.slice!(:a, :b)
# => {:c => 3, :d =>4}
hsh
# => {:a => 1, :b => 2}


183
184
185
186
187
188
189
# 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_keysHash

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

Returns:

  • (Hash)


134
135
136
137
138
139
140
# 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_keysDeepHash

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)

Returns:

  • (DeepHash)

    A copy of this deep_hash.



121
122
123
# File 'lib/configliere/deep_hash.rb', line 121

def symbolize_keys
  dup.symbolize_keys!
end

#symbolize_keys!DeepHash

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)

Returns:

  • (DeepHash)

    This deep_hash unchanged.



129
# File 'lib/configliere/deep_hash.rb', line 129

def symbolize_keys!; self end

#to_hashHash

Returns converts to a plain hash.

Returns:

  • (Hash)

    converts to a plain hash.



65
66
67
# File 'lib/configliere/deep_hash.rb', line 65

def to_hash
  Hash.new(default).merge(self)
end

#update(other_hash, &block) ⇒ DeepHash Also known as: merge!

Returns The updated deep_hash.

Parameters:

  • other_hash (Hash)

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

Returns:



81
82
83
84
85
86
87
88
# 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

#values_at(*indices) ⇒ Array

Returns The values at each of the provided keys.

Parameters:

  • *indices (Array)

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

Returns:

  • (Array)

    The values at each of the provided keys



53
54
55
# File 'lib/configliere/deep_hash.rb', line 53

def values_at(*indices)
  indices.collect{|key| self[convert_key(key)]}
end