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.
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
Ruby - Access multidimensional hash and avoid access nil object
There are many approaches to this.
If you use Ruby 2.3 or above, you can use dig
my_hash.dig('key1', 'key2', 'key3')
Plenty of folks stick to plain ruby and chain the &&
guard tests.
You could use stdlib Hash#fetch too:
my_hash.fetch('key1', {}).fetch('key2', {}).fetch('key3', nil)
Some like chaining ActiveSupport's #try method.
my_hash.try(:[], 'key1').try(:[], 'key2').try(:[], 'key3')
Others use andand
myhash['key1'].andand['key2'].andand['key3']
Some people think egocentric nils are a good idea (though someone might hunt you down and torture you if they found you do this).
class NilClass
def method_missing(*args); nil; end
end
my_hash['key1']['key2']['key3']
You could use Enumerable#reduce (or alias inject).
['key1','key2','key3'].reduce(my_hash) {|m,k| m && m[k] }
Or perhaps extend Hash or just your target hash object with a nested lookup method
module NestedHashLookup
def nest *keys
keys.reduce(self) {|m,k| m && m[k] }
end
end
my_hash.extend(NestedHashLookup)
my_hash.nest 'key1', 'key2', 'key3'
Oh, and how could we forget the maybe monad?
Maybe.new(my_hash)['key1']['key2']['key3']
Elegantly handling blank values in a nested hash
#fetch
is your friend:
my_hash.fetch(:parent, {})[:child].blank?
Ruby: Nils in an IF statement
if you are just trying to see if its defined why not keep it simple and use the defined? function?
if defined?(params[:search][:tags_name_in])
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
Following Rails tutorial, undefined method 'create' for nil:NilClass, with nested resource
@maguri, that's not all necessary. The stumbling block I was running into was that Rails couldn't automatically determine the correct routes. When I provided my own urls in the form_with
declarations, everything went smoothly.
Observe the following change in my _form.html.erb
for the Insured model, which belongs_to
Policy, which has_one
Insured.
<%= form_with model: @insured, url: policy_insured_path(@policy) local: true do |form| %>
In my updated insureds_controller.rb
file, using @Phlip's suggestion:
def create
@policy = Policy.find(params[:policy_id])
@insured = @policy.create_insured(insured_params)
if @policy.insured.save
redirect_to policy_insured_path(params[:policy_id])
else
render 'new'
end
end
This allows me to keep routes.rb
clean and simple:
resources :policies do
resource: insured
end
Thank you for your answer, it helped me discover the problem was with my routes.
Related Topics
Understanding Private Methods in Ruby
In Ruby How to Overload the Initialize Constructor
Accessing Elements of Nested Hashes in Ruby
What Do 'I' and '-I' in Regex Mean
How to Keep the Delimiters When Splitting a Ruby String
Measure the Distance Between Two Strings With Ruby
Cannot Install Json Gem in Rails Using Windows
Printing the Source Code of a Ruby Block
Is the Ruby Operator ||= Intelligent
Error When Running Rails App - Execjs::Runtimeerror
Query , Can Not Select Column Count
How to Pass Arguments into a Rake Task With Environment in Rails
Rvm and Thin, Root Vs. Local User
How to Change My Ruby Version Using Rvm