Created
December 19, 2011 03:16
-
-
Save cmer/1495232 to your computer and use it in GitHub Desktop.
Hash#value_at_path
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
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.
@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
Nice. I can use this right now... playing with an API that returns nasty nested hash keys and results... perfect to make things cleaner.