Skip to content

Instantly share code, notes, and snippets.

@cmer
Created December 19, 2011 03:16
Show Gist options
  • Save cmer/1495232 to your computer and use it in GitHub Desktop.
Save cmer/1495232 to your computer and use it in GitHub Desktop.
Hash#value_at_path
class Hash
# Returns the value at the specified path.
# For example, [:foo, :bar, :baz] as a path would return the
# value at self[:foo][:bar][:baz]. If self doesn't contain one of
# the keys specified in path, nil is returned.
#
# This method is useful as it simplifies statements such as:
# value = h[:a][:b][:c][:d] if h[:a] && h[:a][:b] && h[:a][:b][:c]
# to
# value = h.value_at_path(:a, :b, :c, :d)
# or
# value = h.value_at_path([:a, :b, :c, :d])
#
# @param Array path An array of keys specifying a deep-nested path
# @return Object the value object
def value_at_path(*path)
path = path.flatten
raise ArgumentError if path.include?(nil)
raise ArgumentError if path.empty?
if path.size == 1
return self[path[0]]
else
if self[path[0]].respond_to?(:value_at_path)
return self[path[0]].value_at_path(path[1..-1])
else
return nil
end
end
end
end
require 'test_helper'
class HashTest < ActiveSupport::TestCase
def setup
@h = { :a => { :b => {:c => 'foo'}}, :b => 'bar'}
end
test "value_at_path returns value if path exists and specified as array" do
assert_equal 'foo', @h.value_at_path([:a, :b, :c])
assert_equal 'bar', @h.value_at_path([:b])
assert_equal @h[:a], @h.value_at_path([:a])
assert_equal @h[:a][:b], @h.value_at_path([:a, :b])
end
test "value_at_path returns value if path exists and specified as splat" do
assert_equal 'foo', @h.value_at_path(:a, :b, :c)
assert_equal 'bar', @h.value_at_path(:b)
assert_equal @h[:a], @h.value_at_path(:a)
assert_equal @h[:a][:b], @h.value_at_path(:a, :b)
end
test "value_at_path returns value if path does not exist" do
assert_nil @h.value_at_path([:a, :b, :c, :d])
assert_nil @h.value_at_path([:c])
end
test "value_at_path raises an exception if specified path is invalid" do
assert_raise(ArgumentError) { @h.value_at_path() }
assert_raise(ArgumentError) { @h.value_at_path([]) }
assert_raise(ArgumentError) { @h.value_at_path([nil]) }
assert_raise(ArgumentError) { @h.value_at_path(nil) }
end
end
@resistorsoftware
Copy link

Nice. I can use this right now... playing with an API that returns nasty nested hash keys and results... perfect to make things cleaner.

@francois
Copy link

Nice idea, I'd simplify the calling convention by not requiring an Array as parameter and using the splat arg instead:

def value_at_path(*path)
  path = path.flatten

  ...
end

Things may be simplified a bit more if you'd use #inject instead of #value_at_path recursively. For one thing, you'd use constant memory. On the other hand, a path that could blow the stack would be a nightmare to work with. May not be the best solution.

@cmer
Copy link
Author

cmer commented Dec 19, 2011

@francois the *path idea is a good one! I'll implement it, thanks! I'm not sure I follow you with #inject. Mind giving me an example of that?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment