Reputation: 28793
I have the following example ruby code:
def example_method(obj)
ExampleClass.new(color1: @theme.try(obj['color1'].to_sym))
end
Which should pass either the obj['color1']
as a symbol or nil to the class.
However if color1
is not passed I get the error: NoMethodError: undefined method 'to_sym' for nil:NilClass
.
Shouldn't the try method be handling the exception?
Update: based on comments... I solved it by doing a ternary:
ExampleClass.new(color1: obj['color1'].present? ? @brand_theme.try(obj['color1'].try(:to_sym)) : nil)
Upvotes: 1
Views: 328
Reputation: 114178
You could write a helper method:
def theme_color(name)
return unless name
return unless @theme.respond_to?(name)
@theme.public_send(name)
end
def example_method(obj)
ExampleClass.new(color1: theme_color(obj['color1']))
end
theme_color
returns nil
if the argument is nil
, i.e. obj['color1']
. It also returns nil
if theme
does not respond to the given method. Otherwise, it invokes the method specified by name
.
Note that respond_to?
and public_send
accept either a string or a symbol, so no to_sym
is needed.
You could also define the helper method as an instance method of your @theme
class:
class Theme
def color(name)
return unless name
return unless respond_to?(name)
public_send(name)
end
def red
'FF0000'
end
end
@theme = Theme.new
@theme.red #=> "FF0000"
@theme.color(:red) #=> "FF0000"
@theme.color('red') #=> "FF0000"
@theme.color('green') #=> nil
@theme.color(nil) #=> nil
And invoke it via:
def example_method(obj)
ExampleClass.new(color1: @theme.color(obj['color1']))
end
Keep in mind that these approaches (using public_send
or try
) allow you to invoke arbitrary methods on your @theme
object. It might be safer to keep the colors in a hash.
Upvotes: 3
Reputation: 2242
The try
is called on the @theme
object. The nil error is thrown because obj['color1']
returns nil and then to_sym
is called on nil
.
You'd have to alter the code to
ExampleClass.new(color1: @theme.try(obj['color1'].try(:to_sym) || ''))
to catch that.
And then you would have to prettify the code.
How the prettification works will depend on the use case, so I can only offer some general pointer. One way would be to have a default value to avoid having to deal with the null object
Instead of passing around nil, one simply returns a default value:
color_key = obj.fetch('color') { 'default_color' }.to_sym
ExampleClass.new(color1: @theme.send(color_key)))
This makes use of the fetch method which enables returning a default value. That way you will always have a value defined.
Upvotes: 2
Reputation: 230336
From comments:
In this example
obj['color1']
would be nil. So we'd be passing nil to the try.
Yes, that's an error. You can't call a method with no name. Technically, you could avoid the error by doing .try(obj['color'].to_s)
, but it's super-wrong.
I would check for presence explicitly and bail early if it's not there.
def example_method(obj)
return unless obj['color1'].present?
ExampleClass.new(color1: @theme.try(obj['color1']))
end
Upvotes: 2