How do I fetch multiple hash values at once?

Ruby on-RailsRubyHashDestructuring

Ruby on-Rails Problem Overview


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.

Ruby on-Rails Solutions


Solution 1 - Ruby on-Rails

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 on-Rails

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 - Ruby on-Rails

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 - Ruby on-Rails

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 - Ruby on-Rails

The simplest thing I would go for would be

from, to, name = [:from, :to, :name].map {|key| hash.fetch(key)}

Alternatively, if you want to use values_at, you can use a Hash with a default value block:

hash=Hash.new {|h, k| raise KeyError.new("key not found: #{k.inspect}") }
# ... populate hash
from, to, name = hash.values_at(:from, :to, :name) # raises KeyError on missing key

Or, if you're so inclined, monkey-patch Hash

class ::Hash
  def fetch_all(*args)
    args.map {|key| fetch(key)}
  end
end
from, to, name = hash.fetch_all :from, :to, :name

Solution 6 - Ruby on-Rails

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.

Solution 7 - Ruby on-Rails

Pattern matching is an experimental feature in Ruby 2.7.

hash = { from: 'me', to: 'you', name: 'experimental ruby' }

hash in { from:, to:, name: }

See https://rubyreferences.github.io/rubyref/language/pattern-matching.html

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDmytrii NagirniakView Question on Stackoverflow
Solution 1 - Ruby on-RailsDylan MarkowView Answer on Stackoverflow
Solution 2 - Ruby on-Railsmatthew.tuckView Answer on Stackoverflow
Solution 3 - Ruby on-RailssawaView Answer on Stackoverflow
Solution 4 - Ruby on-RailsvgoffView Answer on Stackoverflow
Solution 5 - Ruby on-RailsAlistair A. IsraelView Answer on Stackoverflow
Solution 6 - Ruby on-RailssaihgalaView Answer on Stackoverflow
Solution 7 - Ruby on-RailsJacquenView Answer on Stackoverflow