lib/observed/hash/key_path_encoding.rb
module Observed
module Hash
module KeyPathEncoding
# Decodes the key path such as 'foo.bar' to dig into the hash and returns `hash[:foo][:bar]`
# @param [Hash] hash The hash to be dug
# @param [String] key_path The key path which is consisted of one or more keys from the parent-to-child order,
# e.g. 'foo.bar' which is consisted of the keys 'foo' and 'bar' where
# the former is the key for the root hash and the latter if is the key for
# the nested hash in `{foo: {bar: 'the_value'}}`
# @param [Hash<Symbol,Boolean>] options
# @option options [Boolean] :create_if_missing when `true` the intermediate hash objects under the consisting keys
# in the key path is created automatically.
# In other words, you automatically get `{foo:bar:{}}` when the
# hash is `{}` and the key_path is `foo.bar.baz`
# @yield yields the hash to be updated or read and the last key to reach the value at the specified key path
# @yieldparam [Hash] hash The hash which has the second to the last key in the key_path. e.g. `{bar:1}` where the
# input hash object is `{foo:{bar:1}}` and the key path is 'foo.bar'
# @yieldparam [String|Symbol] key 'bar' in the example for the parameter `hash` immediately above.
# @yieldreturn [Object] The return value of this method is the return value of the given block
# @returns The result of the given block
def at_key_path_on_hash(hash, key_path, options = {}, &block)
create_if_missing = options[:create_if_missing]
if create_if_missing.nil?
fail "The key :create_if_missing must be exist in #{options}"
end
if hash.nil?
fail 'The hash must not be nil'
end
first, *rest = case key_path
when Array
key_path
when String
key_path.split(".")
when Symbol
key_path
end
key_str = first.to_s
key_sym = first.intern
key = if hash.key? key_str
key_str
else
key_sym
end
if rest.empty?
block.call hash, key
else
child = hash[key]
if child
at_key_path_on_hash(child, rest, options, &block)
elsif create_if_missing
hash[key] = created = {}
at_key_path_on_hash(created, rest, options, &block)
end
end
end
end
end
end