Equivalent of .try() for a hash to avoid “undefine

2019-01-12 17:42发布

In Rails we can do the following in case a value doesn't exist to avoid an error:

@myvar = @comment.try(:body)

What is the equivalent when I'm digging deep into a hash and don't want to get an error?

@myvar = session[:comments][@comment.id]["temp_value"] 
# [:comments] may or may not exist here

In the above case, session[:comments]try[@comment.id] doesn't work. What would?

12条回答
孤傲高冷的网名
2楼-- · 2019-01-12 18:11

The proper use of try with a hash is @sesion.try(:[], :comments).

@session.try(:[], :comments).try(:[], commend.id).try(:[], 'temp_value')
查看更多
放我归山
3楼-- · 2019-01-12 18:12

When you do this:

myhash[:one][:two][:three]

You're just chaining a bunch of calls to a "[]" method, an the error occurs if myhash[:one] returns nil, because nil doesn't have a [] method. So, one simple and rather hacky way is to add a [] method to Niclass, which returns nil: i would set this up in a rails app as follows:

Add the method:

#in lib/ruby_extensions.rb
class NilClass
  def [](*args)
    nil
  end
end

Require the file:

#in config/initializers/app_environment.rb
require 'ruby_extensions'

Now you can call nested hashes without fear: i'm demonstrating in the console here:

>> hash = {:foo => "bar"}
=> {:foo=>"bar"}
>> hash[:foo]
=> "bar"
>> hash[:doo]
=> nil
>> hash[:doo][:too]
=> nil
查看更多
Bombasti
4楼-- · 2019-01-12 18:13

The announcement of Ruby 2.3.0-preview1 includes an introduction of Safe navigation operator.

A safe navigation operator, which already exists in C#, Groovy, and Swift, is introduced to ease nil handling as obj&.foo. Array#dig and Hash#dig are also added.

This means as of 2.3 below code

account.try(:owner).try(:address)

can be rewritten to

account&.owner&.address

However, one should be careful that & is not a drop in replacement of #try. Take a look at this example:

> params = nil
nil
> params&.country
nil
> params = OpenStruct.new(country: "Australia")
#<OpenStruct country="Australia">
> params&.country
"Australia"
> params&.country&.name
NoMethodError: undefined method `name' for "Australia":String
from (pry):38:in `<main>'
> params.try(:country).try(:name)
nil

It is also including a similar sort of way: Array#dig and Hash#dig. So now this

city = params.fetch(:[], :country).try(:[], :state).try(:[], :city)

can be rewritten to

city = params.dig(:country, :state, :city)

Again, #dig is not replicating #try's behaviour. So be careful with returning values. If params[:country] returns, for example, an Integer, TypeError: Integer does not have #dig method will be raised.

查看更多
三岁会撩人
5楼-- · 2019-01-12 18:13

Andrew's answer didn't work for me when I tried this again recently. Maybe something has changed?

@myvar = session[:comments].try('[]', @comment.id)

The '[]' is in quotes instead of a symbol :[]

查看更多
我命由我不由天
6楼-- · 2019-01-12 18:14
@myvar = session.fetch(:comments, {}).fetch(@comment.id, {})["temp_value"]

From Ruby 2.0, you can do:

@myvar = session[:comments].to_h[@comment.id].to_h["temp_value"]

From Ruby 2.3, you can do:

@myvar = session.dig(:comments, @comment.id, "temp_value")
查看更多
劳资没心,怎么记你
7楼-- · 2019-01-12 18:16

You forgot to put a . before the try:

@myvar = session[:comments].try(:[], @comment.id)

since [] is the name of the method when you do [@comment.id].

查看更多
登录 后发表回答