How do I fetch multiple hash values at once?

35,310

Solution 1

Use Hash's values_at method:

from, to, name = hash.values_at(:from, :to, :name)

This will return nil for any keys that don't exist in the hash.

Solution 2

Ruby 2.3 finally introduces the fetch_values method for hashes that straightforwardly achieves this:

{a: 1, b: 2}.fetch_values(:a, :b)
# => [1, 2]
{a: 1, b: 2}.fetch_values(:a, :c)
# => KeyError: key not found: :c

Solution 3

hash = {from: :foo, to: :bar, name: :buz}

[:from, :to, :name].map{|sym| hash.fetch(sym)}
# => [:foo, :bar, :buz]
[:frog, :to, :name].map{|sym| hash.fetch(sym)}
# => KeyError

Solution 4

my_array = {from: 'Jamaica', to: 'St. Martin'}.values_at(:from, :to, :name)
my_array.keys.any? {|key| element.nil?} && raise || my_array

This will raise an error like you requested

 my_array = {from: 'Jamaica', to: 'St. Martin', name: 'George'}.values_at(:from, :to, :name)
 my_array.keys.any? {|key| element.nil?} && raise || my_array

This will return the array.

But OP asked for failing on a missing key...

class MissingKeyError < StandardError
end
my_hash = {from: 'Jamaica', to: 'St. Martin', name: 'George'}
my_array = my_hash.values_at(:from, :to, :name)
my_hash.keys.to_a == [:from, :to, :name] or raise MissingKeyError
my_hash = {from: 'Jamaica', to: 'St. Martin'}
my_array = my_hash.values_at(:from, :to, :name)
my_hash.keys.to_a == [:from, :to, :name] or raise MissingKeyError

Solution 5

You could initialise your hash with a default value of KeyError object. This will return an instance of KeyError if the key you are trying to fetch is not present. All you need to do then is check its (value's) class and raise it if its a KeyError.

hash = Hash.new(KeyError.new("key not found"))

Let's add some data to this hash

hash[:a], hash[:b], hash[:c] = "Foo", "Bar", nil

Finally look at the values and raise an error if key not found

hash.values_at(:a,:b,:c,:d).each {|v| raise v if v.class == KeyError}

This will raise an exception if and only if key is not present. It'll not complain in case you have a key with nil value.

Share:
35,310
Dmytrii Nagirniak
Author by

Dmytrii Nagirniak

Passionate Software Engineer dreaming about perfect solutions and tools.

Updated on December 19, 2020

Comments

  • Dmytrii Nagirniak
    Dmytrii Nagirniak over 3 years

    What is a shorter version of this?:

    from = hash.fetch(:from)
    to = hash.fetch(:to)
    name = hash.fetch(:name)
    # etc
    

    Note the fetch, I want to raise an error if the key doesn't exist.

    There must be shorter version of it, like:

    from, to, name = hash.fetch(:from, :to, :name) # <-- imaginary won't work
    

    It is OK to use ActiveSupport if required.