fetch vs. [] when working with hashes?
By default, using #[]
will retrieve the hash value if it exists, and return nil if it doesn't exist *.
Using #fetch
gives you a few options (see the docs on #fetch):
fetch(key_name)
: get the value if the key exists, raise aKeyError
if it doesn'tfetch(key_name, default_value)
: get the value if the key exists, returndefault_value
otherwisefetch(key_name) { |key| "default" }
: get the value if the key exists, otherwise run the supplied block and return the value.
Each one should be used as the situation requires, but #fetch
is very feature-rich and can handle many cases depending on how it's used. For that reason I tend to prefer it over accessing keys with #[]
.
* As Marc-André Lafortune said, accessing a key with #[]
will call #default_proc
if it exists, or else return #default
, which defaults to nil
. See the doc entry for ::new
for more information.
Benefits of using `Hash#fetch` over `Hash#[]`
Three main uses:
When the value is mandatory, i.e. there is no default:
options.fetch(:repeat).times{...}
You get a nice error message too:
key not found: :repeat
When the value can be
nil
orfalse
and the default is something else:if (doit = options.fetch(:repeat, 1))
doit.times{...}
else
# options[:repeat] is set to nil or false, do something else maybe
endWhen you don't want to use the
default
/default_proc
of a hash:options = Hash.new(42)
options[:foo] || :default # => 42
options.fetch(:foo, :default) # => :default
Could someone explain me what is the difference between Hash#dig vs Hash#fetch
Hash#fetch is not relevant here. That's because fetch
is the same as Hash#[] when, as here, fetch
has only a single argument. So let's concentrate on dig
.
A family of three dig
methods were introduced in Ruby v2.3: Hash#dig, Array#dig and OpenStruct#dig. An interesting thing about these methods is that they invoke each other (but that's not explained in the docs, not even in the examples). In your problem we can write:
response.dig(:results, 0, :value, :destination)
#=> "Rome"
response
is a hash so Hash#dig
evaluates response[:results]
. If it's value is nil
then the expression returns nil
. For example,
response.dig(:cat, 0, :value, :destination)
#=> nil
In fact, response[:results]
is an array:
arr = response[:results]
#=> [{:type=>"product_group", :value=>{:destination=>"Rome"}},
# {:type=>"product_group", :value=>{:destination=>"Paris"}},
# {:type=>"product_group", :value=>{:destination=>"Madrid"}}]
Hash#dig
therefore invokes Array#dig
on arr
, obtaining the hash
h = arr.dig(0)
#=> {:type=>"product_group", :value=>{:destination=>"Rome"}}
Array#dig
then invokes Hash#dig
on h
:
g = h.dig(:value)
#=> {:destination=>"Rome"}
Lastly, g
being a hash, Hash#dig
invokes Hash#dig
on g
:
g.dig(:destination)
#=> "Rome"
Caution needs to be exercised when using any of the dig
's. Suppose
arr = [[1,2], [3,[4,5]]]
and we wish to pull out the object that is now occupied by 4
. We could write either
arr[1][1][0]
#=> 4
or
arr.dig(1,1,0)
#=> 4
Now suppose arr
were changed as follows:
arr = [[1,2]]
Then
arr[1][1][0]
#=> NoMethodError: undefined method `[]' for nil:NilClass
and
arr.dig(1,1,0)
#=> nil
Both are indicative of there being an error in our code, so raising an exception would be preferable to nil
being returned, which may go unnoticed for some time.
How do I use the fetch method for nested hash?
EDIT: there is a built-in way now, see this answer.
There is no built-in method that I know of. I have this in my current project
class Hash
def fetch_path(*parts)
parts.reduce(self) do |memo, key|
memo[key.to_s] if memo
end
end
end
# usage
hash.fetch_path('name', 'Mike', 'age')
You can easily modify it to use #fetch
instead of #[]
(if you so wish).
`try` method when trying to fetch hash value
If you need to allow for object.content.nil?
, then you'd use try
. If you want to allow for a missing key then you don't want fetch
(as Priti notes), you want the normal []
method. Combining the two yields:
object.content.try(:[], 'en')
Observe:
> h = { :a => :b }
=> {:a=>:b}
> h.try(:[], :a)
=> :b
> h.try(:[], :c)
=> nil
> h = nil
=> nil
> h.try(:[], :a)
=> nil
You could also use object.content.try(:fetch, 'en', nil)
if :[]
looks like it is mocking you.
How to get a default value with hashes in ruby
You just need to give fetch
a default it can handle. It doesn't know what to do with january
as you haven't declared any variable with that name. If you want to set the default value to the string "january"
, then you just need to quote it like this:
cohort.fetch(:cohort, "january")
There are some decent examples in the documentation for fetch
.
Also, cohort
isn't a Hash
, it's a String
since gets.chomp
returns a String
. fetch
is for "fetching" values from a Hash
. The way you're using it should be throwing an error similar to: undefined method 'fetch' for "whatever text you entered":String
.
Finally, since you're using it in a conditional, the result of your call to fetch
is being evaluated for its truthiness. If you're setting a default, it will always be evaluated as true
.
If you just want to set a default for cohort
if it's empty, you can just do something like this:
cohort = gets.chomp
cohort = "january" if cohort.empty?
while !name.empty? && !hobbies.empty? && !country.empty?
students << {
name: name,
hobbies: hobbies,
country: country,
cohort: cohort
}
... # do more stuff
Hope that's helpful.
hash[key] is to hash.fetch(key) as @ivar is to what?
There is no built in method that provides this functionality in respect to instance variables, however;
Since Hash#[]
is analogous to @ivar
(or instance_variable_get(:@ivar)
) in your example then Hash#fetch
would be analogous to
def instance_variable_fetch(sym)
raise(NameError, "instance variable not found: #{sym}") unless instance_variable_defined?(sym)
instance_variable_get(sym)
end
Example:
@var = 42
instance_variable_fetch(:@var)
#=> 42
@ivar = nil
instance_variable_fetch(:@ivar)
#=> nil
instance_variable_fetch(:@other_var)
#=> NameError: instance variable not found: @other_var
Related Topics
What Does the |Variable| Syntax Mean
Mail_Form Gem with Sidekiq Worker
List of All/Best Gems for Ruby
Check Whether a Variable Is a String in Ruby
Create .Ruby-Version and .Ruby-Gemset with Rvm
How to Insert Erb-Tags with Vim
Cocoapods Not Working in MACos High Sierra
Test Whether a Ruby Class Is a Subclass of Another Class
How to Remove the Extra Double Quote
Why Can't I Create Active Records That Count for Something in Rspec
Practical Example of Architecture Using Ebc
Writing a Ruby Extension in Go (Golang)
Rails 3 - Has_And_Belongs_To_Many
Escaping Parameters in Set_Form_Data Post