How to avoid NoMethodError for missing elements in nested hashes, without repeated nil checks?
Ruby 2.3.0 introduced a new method called dig
on both Hash
and Array
that solves this problem entirely.
name = params.dig(:company, :owner, :name)
It returns nil
if the key is missing at any level.
If you are using a version of Ruby older than 2.3, you can use the ruby_dig gem or implement it yourself:
module RubyDig
def dig(key, *rest)
if value = (self[key] rescue nil)
if rest.empty?
value
elsif value.respond_to?(:dig)
value.dig(*rest)
end
end
end
end
if RUBY_VERSION < '2.3'
Array.send(:include, RubyDig)
Hash.send(:include, RubyDig)
end
How to avoid NoMethodError for nil elements when accessing nested hashes?
If I understand your question correctly i.e. make it forgiving in case an attribute value is missing, then you could try the following:
@param_info.try(:fetch, :drug).try(:fetch, :name)
This might return nil
as well, but this will get rid of the error undefined methods '[]' for nil:NilClass
Update:
In order to handle keys that do not exist, you could try the following. (Got this hint from Equivalent of try for a hash):
@param_info.try(:[], :drug).try(:[], :name)
Accessing values in nested hash securly (without tripping over nil)?
It is matter of coding style. With Ruby 2.0, you can also do this, which I may like better:
value = array[:first].to_h[:second].to_h[:four]
If you do not want to type to_h
each time, you can define a method:
class Hash
def safe_fetch k; self[k].to_h end
end
value = array.safe_fetch(:first).safe_fetch(:second)[:four]
Is there a clean way to avoid calling a method on nil in a nested params hash?
Check Ick's maybe. You don't need to significantly refactor your code, just intersperse maybe proxies when necessary:
params[:subject].maybe[:name]
The same author (raganwald) also wrote andand, with the same idea.
Elegantly handling blank values in a nested hash
#fetch
is your friend:
my_hash.fetch(:parent, {})[:child].blank?
Nested hash defined?()
When using ActiveSupport (Rails) or Backports, you can use try
:
@hash[:key1].try(:fetch, :key2)
You could even handle @hash
being nil
:
@hash.try(:fetch, :key1).try(:fetch, :key2)
If you want @hash
to always return a hash for a missing key:
@hash = Hash.new { |h,k| h[k] = {} }
@hash[:foo] # => {}
You could also define this recursive:
def recursive_hash
Hash.new { |h,k| h[k] = recursive_hash }
end
@hash = recursive_hash
@hash[:foo][:bar][:blah] = 10
@hash # => {:foo => {:bar => {:blah => 10}}}
But to answer your question:
module HasNestedKey
Hash.send(:include, self)
def has_nested_key?(*args)
return false unless sub = self[args.shift]
return true if args.empty?
sub.respond_to?(:has_nested_key?) and sub.has_nested_key?(*args)
end
end
@hash.has_nested_key? :key1, :key2
Equivalent of .try() for a hash to avoid undefined method errors on nil?
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]
.
Ruby hash/sub-hash existance check
Something like:
def follow_hash(hash, path)
path.inject(hash) { |accum, el| accum && accum[el] }
end
value = follow_hash(hash, [:foo, :bar, :baz])
puts value if value
Ruby Style: How to check whether a nested hash element exists
The most obvious way to do this is to simply check each step of the way:
has_children = slate[:person] && slate[:person][:children]
Use of .nil? is really only required when you use false as a placeholder value, and in practice this is rare. Generally you can simply test it exists.
Update: If you're using Ruby 2.3 or later there's a built-in
dig
method that does what's described in this answer.
If not, you can also define your own Hash "dig" method which can simplify this substantially:
class Hash
def dig(*path)
path.inject(self) do |location, key|
location.respond_to?(:keys) ? location[key] : nil
end
end
end
This method will check each step of the way and avoid tripping up on calls to nil. For shallow structures the utility is somewhat limited, but for deeply nested structures I find it's invaluable:
has_children = slate.dig(:person, :children)
You might also make this more robust, for example, testing if the :children entry is actually populated:
children = slate.dig(:person, :children)
has_children = children && !children.empty?
Related Topics
Ruby Array Access 2 Consecutive(Chained) Elements At a Time
How to Set Tls Context Options in Ruby (Like Openssl::Ssl::Ssl_Op_No_Sslv2)
How Does Instance_Eval Work and Why Does Dhh Hate It
How to Find an Item in Array Which Has the Most Occurrences
What's Different Between Each and Collect Method in Ruby
How to Configure Webrick to Use Ssl in Rails
How to Generate a List of N Unique Random Numbers in Ruby
Rbenv Not Changing Ruby Version
Rails 4: List of Available Datatypes
Submit Post Data from Controller to Another Website in Rails
Suppress Ruby Warnings When Running Specs