What is the most ruby-ish way of accessing nested hash values at arbitrary depths? [duplicate]
Asked Answered
C

4

8

Given a hash such as:

AppConfig = {
  'service' => {
    'key' => 'abcdefg',
    'secret' => 'secret_abcdefg'
  },
  'other' => {
    'service' => {
      'key' => 'cred_abcdefg',
      'secret' => 'cred_secret_abcdefg'
    }
  }
}

I need a function to return service/key in some cases and other/service/key in other cases. A straightforward way is to pass in the hash and an array of keys, like so:

def val_for(hash, array_of_key_names)
  h = hash
  array_of_key_names.each { |k| h = h[k] }
  h
end

So that this call results in 'cred_secret_abcdefg':

val_for(AppConfig, %w[other service secret])

It seems like there should be a better way than what I've written in val_for().

Chon answered 6/11, 2012 at 20:50 Comment(1)
what should happen if a intermediate key is not found? and if the last key is not there?Herron
H
9
def val_for(hash, keys)
  keys.reduce(hash) { |h, key| h[key] }
end

This will raise an exception if some intermediate key is not found. Note also that this is completely equivalent to keys.reduce(hash, :[]), but this may very well confuse some readers, I'd use the block.

Herron answered 6/11, 2012 at 21:0 Comment(2)
Sure, it's up to the caller in my implementation to know what the config keys are. Thanks!Chon
Yea, yours is really better :).Balderdash
B
6
%w[other service secret].inject(AppConfig, &:fetch)
Bookrest answered 7/11, 2012 at 0:55 Comment(5)
oh, indeed, and you can even write :fetch. This will fail also if the last key is not found, but that seems ok for the OP.Herron
Your comment makes my answer look like it was written after you added the symbol to proc solution to your answer.Bookrest
I am puzzled, I +1'd your answer and commented it with "oh, indeed" precisely to show that I had forgotten about this inject simplification and I wanted to give credit before editing my answer! Apparently I failed in my intention :-(Herron
At first, I did not understand what you meant by "even" in the comment. It implies it is compared to something. Then I realized that your comment makes sense only after reading the edited part of your answer. This looked like assuming the reader will look at your answer, then read mine as a variant of it. But now I understand your intention.Bookrest
Ok. With the "even" I meant you can simplify it a bit more and drop the &, inject accepts a symbol so the Symbol#to_proc trick is not necessary.Herron
I
1
appConfig = {
  'service' => {
    'key' => 'abcdefg',
    'secret' => 'secret_abcdefg'
  },
  'other' => {
    'service' => {
      'key' => 'cred_abcdefg',
      'secret' => 'cred_secret_abcdefg'
    }
  }
}

def val_for(hash, array_of_key_names)
  eval "hash#{array_of_key_names.map {|key| "[\"#{key}\"]"}.join}"
end

val_for(appConfig, %w[other service secret]) # => "cred_secret_abcdefg"
Issuable answered 6/11, 2012 at 20:59 Comment(1)
Thanks Andres, but eval'ing a string and using escape characters isn't much prettier than what I originally wrote. :\Chon
B
1

Ruby 2.3.0 introduced a new method called dig on both Hash and Array that solves this problem entirely.

AppConfig.dig('other', 'service', 'secret')

It returns nil if the key is missing at any level.

Brassiere answered 6/1, 2016 at 3:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.