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.
- #merge! ⇒ Object
- #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.
-
#symbolize_keys ⇒ Object
Return a new hash with all keys converted to symbols, as long as they respond to
to_sym
. -
#symbolize_keys! ⇒ DeepHash
Used to provide the same interface as Hash.
-
#to_hash ⇒ Hash
Converts to a plain hash.
-
#update(other_hash, &block) ⇒ DeepHash
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
232 233 234 235 236 |
# File 'lib/configliere/deep_hash.rb', line 232 def [] attr attr = convert_key(attr) raise if (attr == [:made]) 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
214 215 216 217 218 |
# File 'lib/configliere/deep_hash.rb', line 214 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
199 200 201 202 |
# File 'lib/configliere/deep_hash.rb', line 199 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
106 107 108 |
# File 'lib/configliere/deep_hash.rb', line 106 def compact reject{|key,val| val.nil? } end |
#compact! ⇒ Object
Replace the hash with its compacted self
112 113 114 |
# File 'lib/configliere/deep_hash.rb', line 112 def compact! replace(compact) end |
#deep_delete(*args) ⇒ Object
Treat hash as tree of hashes:
x = { 1 => :val, :subhash => { 1 => :val1, 2 => :val2 } }
x.deep_delete(:subhash, 1)
#=> :val
x
#=> { 1 => :val, :subhash => { 2 => :val2 } }
331 332 333 334 335 |
# File 'lib/configliere/deep_hash.rb', line 331 def deep_delete *args last_key = args.pop last_hsh = args.empty? ? self : (deep_get(*args)||{}) last_hsh.delete(last_key) end |
#deep_get(*args) ⇒ Object
Treat hash as tree of hashes:
x = { 1 => :val, :subhash => { 1 => :val1 } }
x.deep_get(:subhash, 1)
# => :val
x.deep_get(:subhash, 2)
# => nil
x.deep_get(:subhash, 2, 3)
# => nil
x.deep_get(:subhash, 2)
# => nil
314 315 316 317 318 319 320 |
# File 'lib/configliere/deep_hash.rb', line 314 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 => { 1 => :val_from_x, 222 => :only_in_x, 333 => :only_in_x }, :scalar => :scalar_from_x}
y = { :subhash => { 1 => :val_from_y, 999 => :only_in_y }, :scalar => :scalar_from_y }
x.deep_merge y
=> {:subhash=>{1=>:val_from_y, 222=>:only_in_x, 333=>:only_in_x, 999=>:only_in_y}, :scalar=>:scalar_from_y}
y.deep_merge x
=> {:subhash=>{1=>:val_from_x, 222=>:only_in_x, 333=>:only_in_x, 999=>:only_in_y}, :scalar=>:scalar_from_x}
Nil values always lose.
x = {:subhash=>{:nil_in_x=>nil, 1=>:val1,}, :nil_in_x=>nil}
y = {:subhash=>{:nil_in_x=>5}, :nil_in_x=>5}
y.deep_merge x
=> {:subhash=>{1=>:val1, :nil_in_x=>5}, :nil_in_x=>5}
x.deep_merge y
=> {:subhash=>{1=>:val1, :nil_in_x=>5}, :nil_in_x=>5}
269 270 271 |
# File 'lib/configliere/deep_hash.rb', line 269 def deep_merge hsh2 merge hsh2, &DeepHash::DEEP_MERGER end |
#deep_merge!(hsh2) ⇒ Object
273 274 275 276 |
# File 'lib/configliere/deep_hash.rb', line 273 def deep_merge! hsh2 update hsh2, &DeepHash::DEEP_MERGER self end |
#deep_set(*args) ⇒ Object
Treat hash as tree of hashes:
x = { 1 => :val, :subhash => { 1 => :val1 } }
x.deep_set(:subhash, :cat, :hat)
# => { 1 => :val, :subhash => { 1 => :val1, :cat => :hat } }
x.deep_set(:subhash, 1, :newval)
# => { 1 => :val, :subhash => { 1 => :newval, :cat => :hat } }
288 289 290 291 292 293 294 295 296 297 298 299 |
# File 'lib/configliere/deep_hash.rb', line 288 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
160 161 162 163 164 |
# File 'lib/configliere/deep_hash.rb', line 160 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 |
#merge! ⇒ Object
76 |
# File 'lib/configliere/deep_hash.rb', line 76 alias_method :merge!, :update |
#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( = {})
.reverse_merge! :size => 25, :velocity => 10
end
Using merge
, the above example would look as follows:
def setup( = {})
{ :size => 25, :velocity => 10 }.merge()
end
The default :size
and :velocity
are only set if the options
hash passed in doesn’t already have the respective key.
181 182 183 |
# File 'lib/configliere/deep_hash.rb', line 181 def reverse_merge(other_hash) 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.
187 188 189 |
# File 'lib/configliere/deep_hash.rb', line 187 def reverse_merge!(other_hash) merge!( other_hash ){|k,o,n| 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(.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(.slice(*valid_keys))
128 129 130 131 132 133 |
# File 'lib/configliere/deep_hash.rb', line 128 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
143 144 145 146 147 148 149 |
# File 'lib/configliere/deep_hash.rb', line 143 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 |
#symbolize_keys ⇒ Object
Return a new hash with all keys converted to symbols, as long as they respond to to_sym
.
94 95 96 |
# File 'lib/configliere/deep_hash.rb', line 94 def symbolize_keys dup.symbolize_keys! end |
#symbolize_keys! ⇒ DeepHash
Used to provide the same interface as Hash.
101 |
# File 'lib/configliere/deep_hash.rb', line 101 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
Returns The updated deep_hash.
83 84 85 86 87 88 89 90 |
# File 'lib/configliere/deep_hash.rb', line 83 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 |