I have a hash, and I'm looking for a certain key inside it. But sometimes hash itself can be nil. In this event, I don't want hash["key"] to return an undefined method '[]' for nil:NilClass error. I want it to return nil instead.
With other similar situations, I can use try, like this: nil.try(:key) => nil but I can't get the same method to work with my hash.
In other words, I'm looking for a method that results in something like this:
> #hash = nil
> #hash.try(:["key"])
=> nil
The various to_X methods are often convenient ways to quietly convert nils to empty instances of what you're really interested in without complicated your logic with explicit conditionals.
In your case, you could take advantage of Hash#to_h returning the hash itself and NilClass#to_h returning an empty Hash:
> hash = nil
> hash.to_h['key']
=> nil
> hash = { 'key' => 11 }
> hash.to_h['key']
=> 11
Sometimes this is less noisy than an explicit if, sometimes it isn't.
The situation you describe (hash = nil) is not a "missing hash key", it's not a hash at all.
In addition to the other answers, another way could be this in Ruby (2.3)
hash&.fetch("key", nil)
Or:
(hash || {})["key"]
Both these will return nil if the hash is nil or if it's missing the key.
You could check if #hash is nil before using #hash["key"], like this:
#hash = nil
#hash["key"] unless #hash.nil?
#=> nil
To use try, you can do
#hash.try(:[], 'key')
or even
#hash&.[]('key')
if you are in ruby 2.3+
I get an nested array from facebook via omniauth and wanna check if it's empty?/nil?/exists?
the depending line looks like:
unless omniauth['extra']['raw_info']['location']['name'].nil?
This should check if this part of the array is empty or exists.
But always this error was thrown:
undefined method `[]' for nil:NilClass
Do I check arrays wrong?
I tried it with "has_key" "nil?" "empty?" "exists?" "blank?"
But no one of these works!
Please help me, many thanks in advance!
Ideally you should check each nested level to see if it is nil, however, this will also work.
unless (omniauth['extra']['raw_info']['location']['name'] rescue nil).nil?
You can also rescue the NoMethodError specifically.
This error is raised because one of the hash values in the chain of omniauth['extra']['raw_info']['location']['name'].nil? returns nil and it is not the last call ['name'].
If for example omniauth['extra']['raw_info'] returns nil, you're actually trying to call nil['location'] which raises an error in ruby.
You can catch this error simply:
res = omniauth['extra']['raw_info']['location']['name'].nil? rescue true
unless res
#your code here
end
Please notice that the code block above will fill the variable res with true if the ['name'] hash value is nil or any other hash value in the chain returns nil.
A bit late to the party, but, as pointed in this answer, Ruby 2.3.0 introduced a new method called dig, which would return nil if one of the chained keys is nil. Your omniauth auth hash could then be presented as:
omniauth = {
...
"extra"=>{ "raw_info"=>
{ "location"=>"New York",
"gravatar_id"=>"123456789"}}
...
}
omniauth.dig('extra',
'raw_info',
'location',
'name',
'foo',
'bar',
'baz') #<= nil
I have this line in my controller:
user = User.any_of({:user_name => login}, {:email => login})
if user.nil?
# ...
elsif user.legacy_password.nil?
And it creates this error:
undefined method `legacy_password' for []:Array
Why would this happen? the user object is supposed to be nil. At least that is what the debugger said.
I'm assuming your any_of method returns an array of results, not a single result. You probably want to add .first to the end of it, which will give you either a User record, or nil if any_of returned an empty array.
user = User.any_of({:user_name => login},{:email => login}).first
Looks like you are using mongoid (#any_of) and it's returning an array.
The error is because you are calling legacy_password on an array, but I assume it is defined on the User model.
This question already has answers here:
How to avoid NoMethodError for nil elements when accessing nested hashes? [duplicate]
(4 answers)
Closed 7 years ago.
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?
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].
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.
The most beautiful solution is an old answer by Mladen Jablanović, as it lets you to dig in the hash deeper than you could with using direct .try() calls, if you want the code still look nice:
class Hash
def get_deep(*fields)
fields.inject(self) {|acc,e| acc[e] if acc}
end
end
You should be careful with various objects (especially params), because Strings and Arrays also respond to :[], but the returned value may not be what you want, and Array raises exception for Strings or Symbols used as indexes.
That is the reason why in the suggested form of this method (below) the (usually ugly) test for .is_a?(Hash) is used instead of (usually better) .respond_to?(:[]):
class Hash
def get_deep(*fields)
fields.inject(self) {|acc,e| acc[e] if acc.is_a?(Hash)}
end
end
a_hash = {:one => {:two => {:three => "asd"}, :arr => [1,2,3]}}
puts a_hash.get_deep(:one, :two ).inspect # => {:three=>"asd"}
puts a_hash.get_deep(:one, :two, :three ).inspect # => "asd"
puts a_hash.get_deep(:one, :two, :three, :four).inspect # => nil
puts a_hash.get_deep(:one, :arr ).inspect # => [1,2,3]
puts a_hash.get_deep(:one, :arr, :too_deep ).inspect # => nil
The last example would raise an exception: "Symbol as array index (TypeError)" if it was not guarded by this ugly "is_a?(Hash)".
The proper use of try with a hash is #sesion.try(:[], :comments).
#session.try(:[], :comments).try(:[], commend.id).try(:[], 'temp_value')
Update: As of Ruby 2.3 use #dig
Most objects that respond to [] expect an Integer argument, with Hash being an exception that will accept any object (such as strings or symbols).
The following is a slightly more robust version of Arsen7's answer that supports nested Array, Hash, as well as any other objects that expect an Integer passed to [].
It's not fool proof, as someone may have created an object that implements [] and does not accept an Integer argument. However, this solution works great in the common case e.g. pulling nested values from JSON (which has both Hash and Array):
class Hash
def get_deep(*fields)
fields.inject(self) { |acc, e| acc[e] if acc.is_a?(Hash) || (e.is_a?(Integer) && acc.respond_to?(:[])) }
end
end
It can be used the same as Arsen7's solution but also supports arrays e.g.
json = { 'users' => [ { 'name' => { 'first_name' => 'Frank'} }, { 'name' => { 'first_name' => 'Bob' } } ] }
json.get_deep 'users', 1, 'name', 'first_name' # Pulls out 'Bob'
say you want to find params[:user][:email] but it's not sure whether user is there in params or not. Then-
you can try:
params[:user].try(:[], :email)
It will return either nil(if user is not there or email is not there in user) or otherwise the value of email in user.
As of Ruby 2.3 this gets a little easier. Instead of having to nest try statements or define your own method you can now use Hash#dig (documentation).
h = { foo: {bar: {baz: 1}}}
h.dig(:foo, :bar, :baz) #=> 1
h.dig(:foo, :zot) #=> nil
Or in the example above:
session.dig(:comments, #comment.id, "temp_value")
This has the added benefit of being more like try than some of the examples above. If any of the arguments lead to the hash returning nil then it will respond nil.
#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")
Another approach:
#myvar = session[:comments][#comment.id]["temp_value"] rescue nil
This might also be consider a bit dangerous because it can hide too much, personally I like it.
If you want more control, you may consider something like:
def handle # just an example name, use what speaks to you
raise $! unless $!.kind_of? NoMethodError # Do whatever checks or
# reporting you want
end
# then you may use
#myvar = session[:comments][#comment.id]["temp_value"] rescue handle
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
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 :[]
Try to use
#myvar = session[:comments][#comment.id]["temp_value"] if session[:comments]
I am trying to populate a nested field, product_name, it an Item.
In my Item model:
class Item < ActiveRecord::Base
attr_writer :product_name
belongs_to :order
belongs_to :product
def product_name
#Product.find_by_id(self.product_id) #=> returns the product object
#self.product #=> returns the product object
#Product.find_by_id(self.product_id).name #=> complains that 'name' is being called on nil
#self.product.name #=> complains that 'name' is being called on nil
#"foooobar" #=> returns "foooobar"
end
end
Each of those commented lines behaves as described.
What I don't get is, how can something return an object successfully, but then complain that the object is nil when you access an attribute of it?
Here is the whole error:
NoMethodError in Orders#edit
Showing app/views/orders/_form.haml where line #18 raised:
undefined method `name' for nil:NilClass
Extracted source (around line #18):
17: = item.hidden_field :product_id
18: = item.text_field :product_name, :class => 'auto_complete_input'
19: = item.text_field :quantity, :size => 2
Thanks
You may want to take a look at the andand plugin at http://github.com/raganwald/andand.
Basically, what it does is handle the errors that may arise from trying to call a method on another that has the possibility of being nil.
Based solely on your example:
Product.find_by_id(self.product_id).name #=> complains that 'name' is being called on nil
An implementation of andand would look like:
Product.find(self.product_id).andand.name
And even if the first statement, Product.find(self.product_id), returns nil it would no longer fire the undefined method on nil error.
PS:
Again, based solely on your code, I can see that you're reproducing the functionality generated by using the :belongs_to association. Essentially, what it means is that you can use:
self.product
insead of:
Product.find_by_id(self.product_id)
It sounds like you're asking a really general question. If you're asking a totally different question than I'm hearing, ignore this. :)
Yes, Ruby is successfully returning an object, and the object is nil. In Ruby, a method can return any value, and nil is just another value that a method can return. Those statements are probably doing a query that isn't returning any results, so the method returns nil instead of the object you are expecting.
The longer answer is that Product.find_by_id(x) calls a generated find_by_ attribute method for the id field, which is the same as calling Product.find(:first, :conditions => ["id = ?", x]). If you look at the documentation for .find, you will notice that it says
Find first - This will return [blah blah black...] If no record can be matched, nil is returned.
Have you given proper relationship in your product model and item model, i think giving proper association can solve your error.