Hommer Smith
Hommer Smith

Reputation: 27852

Reraise (same exception) after catching an exception in Ruby

I am trying to improve my Ruby skills by catching exceptions. I want to know if it is common to reraise the same kind of exception when you have several method calls. So, would the following code make sense? Is it ok to reraise the same kind of exception, or should I not catch it on the process method?

class Logo
  def process
    begin
      @processed_logo = LogoProcessor::create_image(self.src)
    rescue CustomException
      raise CustomException
    end
  end
end

module LogoProcessor
  def self.create_image
    raise CustomException if some_condition
  end
end

Upvotes: 121

Views: 59492

Answers (5)

Matheus Moreira
Matheus Moreira

Reputation: 17020

Sometimes we just want to know an error happened, without having to actually handle the error.

It is often the case that the one responsible for handling errors is user of the object: the caller. What if we are interested in the error, but don't want to assume that responsibility? We rescue the error, do whatever we need to do and then propagate the signal up the stack as if nothing had happened.

For example, what if we wanted to log the error message and then let the caller deal with it?

begin
  this_will_fail!
rescue Failure => error
  log.error error.message
  raise
end

Calling raise without any arguments will raise the last error. In our case, we are re-raising error.

In the example you presented in your question, re-raising the error is simply not necessary. You could simply let it propagate up the stack naturally. The only difference in your example is you're creating a new error object and raising it instead of re-raising the last one.

Upvotes: 246

ZedTuX
ZedTuX

Reputation: 3027

A slightly better way to do the same thing as FreePender is to use the exception method from the Exception class, which is the ancestor class to any error classes, like StandardError, so that the method is available to any error classes.

Here the method's documentation that you can find on ApiDock:

With no argument, or if the argument is the same as the receiver, return the receiver. Otherwise, create a new exception object of the same class as the receiver, but with a message equal to string.to_str.

Now let's see how it works:

begin
  this_will_fail!
rescue Failure => error
  raise error.exception("Message: #{error.message}")
end

Upvotes: 4

Vasanth Saminathan
Vasanth Saminathan

Reputation: 585

Adding to above answers here:

  • In some applications you may need to log the error twice.
  • For example exception need to be notified to monitoring tools like Nagios/Newrelic/Cloudwatch.
  • At the same time you may have your own kibana backed summary logging tool, internally for your reference.

In those cases you might want to log & handle the errors multiple times.

Example:

begin
  begin
    nil.to_sym
  rescue => e
    puts "inner block error message: #{e.message}"
    puts "inner block backtrace: #{e.backtrace.join("\n")}"
    raise e
  end
rescue => e
  puts "outer block error message: #{e.message}"
  puts "outer block backtrace: #{e.backtrace.join("\n")}"
end

I am using puts here, for your the ease of verifying this code in rails console, in actual production you may need to use rails logger

Upvotes: 1

sam2426679
sam2426679

Reputation: 3837

I had the same question as in the comment thread here, i.e. What if the line before (re)raise fails?

My understanding was limited by the missing knowledge that the global variable of $! is "kinda garbage collected" // "scoped to its functional context", which the below example demonstrates:

def func
  begin
    raise StandardError, 'func!'
  rescue StandardError => err
    puts "$! = #{$!.inspect}"
  end
end

begin
  raise StandardError, 'oh no!'
rescue StandardError => err
  func
  puts "$! = #{$!.inspect}"
  raise
end

The output of the above is:

$! = #<StandardError: func!>
$! = #<StandardError: oh no!>
StandardError: oh no!
from (pry):47:in `__pry__'

This behavior is different than how Python's (re)raise works.

The documentation for Exception states:

When an exception has been raised but not yet handled (in rescue, ensure, at_exit and END blocks), two global variables are set:

  • $! contains the current exception.
  • $@ contains its backtrace.

So these variables aren't true global variables, they are only defined inside the block that's handling the error.

begin
  raise
rescue
  p $!  # StandardError
end

p $!    # nil

Upvotes: 5

FreePender
FreePender

Reputation: 4879

This will raise the same type of error as the original, but you can customize the message.

rescue StandardError => e
  raise e.class, "Message: #{e.message}"

Upvotes: 6

Related Questions