How do I fetch multiple hash values at once?
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.
Dmytrii Nagirniak
Passionate Software Engineer dreaming about perfect solutions and tools.
Updated on December 19, 2020Comments
-
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.