Reputation: 9822
So I want to conditionally assign variables based on whether or not the input has been given.
For example
@name = params[:input]['name'] || "Name not yet given"
However, if the params have not been passed yet, this gives an error
method [] does not exist for nil class
I have two ideas to get around this. One is adding a [] method to nil class. Something like:
class NilClass
def []
self
end
end
And the other idea that I have is to use if statements
if params[:input].nil?
@name = params[:input]['name']
else
@name = "Name not yet given"
end
However, neither of these solutions feel quite right.
What is the "ruby way"?
Upvotes: 3
Views: 1460
Reputation: 12520
You could use Hash#dig introduced in Ruby 2.3.0.
@name = params.dig(:input, 'name') || "Name not yet given"
Similarly if you want to gracefully handle nil returns when chaining methods, you can use the safe navigation operator also introduced in Ruby 2.3.0.
@name = object&.input&.name || "Name not yet given"
Upvotes: 1
Reputation: 110675
As I understand, for the hash h
, you want to know if
h
has a key :input
and if soh[:input]
is a hash and if soh[:input]
has a key "name"
If "yes" to all three, return h[:input]["name"]
; else return "Name not yet given"
.
So just write that down:
def get_name(h)
if (h[:input].is_a? Hash) && h[:input].key?("name")
h[:input]["name"]
else
"Name not yet given"
end
end
params = { hat: "cat" }
get_name(params)
#=> "Name not yet given"
params = { input: "cat" }
get_name(params)
#=> "Name not yet given"
params = { input: {} }
get_name(params)
#=> "Name not yet given"
params = { input: { "moniker"=>"Jake" } }
get_name(params)
#=> "Name not yet given"
params = { input: { "name"=>"cat" } }
get_name(params)
#=> "cat"
Another way:
def get_name(h)
begin
v = h[:input]["name"]
v ? v : "Name not yet given"
rescue NoMethodError
"Name not yet given"
end
end
Upvotes: 1
Reputation: 7725
Try to fetch the key:
params[:input].try(:fetch, 'name', "Name not yet given")
Assuming you are on rails which seems likely, otherwise you can concatenate fetchs:
params.fetch(:input, {}).fetch 'name', "Name not yet given"
It's a common practice to define params like this:
def input_params
params.fetch(:input, {})
end
Which reduces the problem to:
input_params[:name] || 'Whatever'
Upvotes: 0
Reputation: 118271
One way is use Hash#fetch
.
params[:input].to_h.fetch('name', "Name not yet given")
Upvotes: 6
Reputation: 75488
@name = params[:input].nil? ? "Name not yet given" : params[:input]['name']
.nil?
may optionally be excluded.Also see my solution for recursions: https://stackoverflow.com/a/24588976/445221
Upvotes: 2
Reputation: 18037
I like to use NullObjects, or, more specifically, Black Hole objects for this sort of thing. Avdi Grimm has blessed us with a great ruby gem for this construct called naught. So for your situation, I'd install the gem and then start by creating my project-specific Null Object:
# add this to a lib file such as `lib/null_object.rb`
require 'naught'
NullObject = Naught.build do |config|
config.define_explicit_conversions
config.define_implicit_conversions
config.black_hole
if $DEBUG
config.traceable
else
config.singleton
end
end
Then, include NullObject::Conversions
where needed and go to town, confidently!
# my_class.rb
require 'null_object.rb'
include NullObject::Conversions
Maybe(params[:input])["name"].to_s.presence || "Name not yet given"
# => "Name not yet given"
The great thing about this Black Hole approach is that there's no extra steps needed for any additional chaining. You simply chain methods together as long as you want under the (confident) assumption that it will turn out well. Then, at the end you convert the value to the expected type and the explicit conversions will give you a basic version of that back if something in the chain returned nil before you expected it to.
Maybe(params[:thing1])[:thing2][:thing3].map(&:to_i).sum.to_i
# => 0
Or, if you prefer, you can use Actual
to convert a Black Hole object back to its actual value:
Actual(Maybe(params[:input])["name"]) || "Name not yet given"
For more on the Null Object pattern, check out Avdi Grimm's post on the subject. All in all it's a great way to gain confidence and stop type checking (and remember, even checking for nil
as with .try()
is type checking!). Duck typing is supposed to free us from type checking!
Upvotes: 1
Reputation: 230336
You can always write some code to sweeten your other code.
class Hash
def deep_fetch(*path)
path.reduce(self) { |memo, elem| memo ? memo[elem] : nil }
end
end
params = { input: { name: 'sergio' } }
params.deep_fetch(:input, :name) # => "sergio"
params.deep_fetch(:omg, :lol, :wtf) # => nil
Upvotes: 2