Cameron
Cameron

Reputation: 28793

Ruby try not handling exception

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

Answers (3)

Stefan
Stefan

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

ulferts
ulferts

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

Sergio Tulentsev
Sergio Tulentsev

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

Related Questions