Class: DeepHash
- Inherits:
-
Hash
- Object
- Hash
- DeepHash
- 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
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
-
#[](attr) ⇒ Object
Gets a member value.
-
#[]=(attr, val) ⇒ Object
Sets a member value.
-
#assert_valid_keys(*valid_keys) ⇒ Object
Validate all keys in a hash match *valid keys, raising ArgumentError on a mismatch.
-
#compact ⇒ Object
remove all key-value pairs where the value is nil.
-
#compact! ⇒ Object
Replace the hash with its compacted self.
-
#deep_delete(*args) ⇒ Object
Treat hash as tree of hashes:.
-
#deep_get(*args) ⇒ Object
Treat hash as tree of hashes:.
-
#deep_merge(hsh2) ⇒ Object
Merge hashes recursively.
- #deep_merge!(hsh2) ⇒ Object
-
#deep_set(*args) ⇒ Object
Treat hash as tree of hashes:.
- #delete(attr) ⇒ Object
-
#extract!(*keys) ⇒ Object
Removes the given keys from the hash Returns a hash containing the removed key/value pairs.
-
#fetch(key, *extras) ⇒ Object
The value at key or the default value.
-
#initialize(constructor = {}) ⇒ DeepHash
constructor
A new instance of DeepHash.
-
#key?(key) ⇒ Boolean
(also: #include?, #has_key?, #member?)
True if the key exists in the mash.
-
#merge(hash, &block) ⇒ DeepHash
A new deep_hash with the hash values merged in.
- #regular_update ⇒ Object
- #regular_writer ⇒ Object
-
#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.
-
#reverse_merge!(other_hash) ⇒ Object
Performs the opposite of merge, with the keys and values from the first hash taking precedence over the second.
-
#slice(*keys) ⇒ Object
Slice a hash to include only the given keys.
-
#slice!(*keys) ⇒ Object
Replaces the hash with only the given keys.
-
#stringify_keys ⇒ Hash
Return a new hash with all top-level keys converted to strings.
-
#symbolize_keys ⇒ DeepHash
This DeepHash with all its keys converted to symbols, as long as they respond to +to_sym+.
-
#symbolize_keys! ⇒ DeepHash
This DeepHash with all its keys converted to symbols, as long as they respond to +to_sym+.
-
#to_hash ⇒ Hash
Converts to a plain hash.
-
#update(other_hash, &block) ⇒ DeepHash
(also: #merge!)
The updated deep_hash.
-
#values_at(*indices) ⇒ Array
The values at each of the provided keys.
Constructor Details
#initialize(constructor = {}) ⇒ DeepHash
Returns a new instance of DeepHash.
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
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
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
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 |
#compact ⇒ Object
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
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
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.
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.
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.
72 73 74 |
# File 'lib/configliere/deep_hash.rb', line 72 def merge(hash, &block) self.dup.update(hash, &block) end |
#regular_update ⇒ Object
20 |
# File 'lib/configliere/deep_hash.rb', line 20 alias_method :regular_update, :update |
#regular_writer ⇒ Object
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
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_keys ⇒ Hash
Return a new hash with all top-level keys converted to strings.
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_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)
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)
129 |
# File 'lib/configliere/deep_hash.rb', line 129 def symbolize_keys!; self end |
#to_hash ⇒ Hash
Returns 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.
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.
53 54 55 |
# File 'lib/configliere/deep_hash.rb', line 53 def values_at(*indices) indices.collect{|key| self[convert_key(key)]} end |