Can you rescue from specific errors with messages in ruby? - ruby

I'm trying to understand how errors propogate between classes in Ruby. I have this so far:
class User
def charge
puts "charging order soon"
raise RuntimeError.new("This is a runtime error")
rescue ArgumentError
puts "should never gets here"
end
end
class Runner
def run
begin
User.new.charge
rescue RuntimeError => e
puts e.message
end
end
end
Runner.new.run
When I run this, I get this which seems right:
$ ruby errors.rb
charging order soon
This is a runtime error
Inside runner, can I rescue from the RuntimeError with a specific message? If I have multiple RuntimeErrors being raised around my application, is there any way for the Runner's rescue clause to be raised only for RuntimeErrors with specific messages?

See https://stackoverflow.com/a/23771227/2981429
If you call raise inside a rescue block, the last raised exception will be re-raised.
In your exception block, you can check the message and choose to re-raise or not:
begin
User.new.charge
rescue RuntimeError => e
case e.message
when "This is a runtime error"
# put your handler code here
else
raise # re-raise the last exception
end
end
However if it's your goal to solely rescue errors that you yourself raise manually, then it's probably easier to define a custom error class instead:
class MyError < StandardError; end
Then instead of raise RuntimeError.new("message") use raise MyError.new("message"), and rescue it normally:
begin
User.new.charge
rescue MyError => e
# handler
end
This way you don't have to worry about your rescues interfering with the built-in exceptions.

Related

ruby how to stop the execution after rescue

I have a func where when an exception was raised, I am rescuing it.
But the program continues to the next line and calls the next func create_request
But when there is exception, I do not want to continue
def validate_request_code options
if check_everything is good
#code to validate
else
errors << "something is gone bad"
end
[errors.size == 0, errors.size == 0 ? options : raise(ArgumentError, "Error while validating #{errors}")]
end
I am trying to catch/rescue the exception
def validate_request options
begin
validate_request_code options
rescue ArgumentError => e
log :error
rescue Exception => e
log :error
end
sleep 20
if options['action'] == "create"
create_request options
end
end
If by 'not continue' you mean that you want the original error to continue (i.e., you just want to take action on the way by), you can call raise inside the rescue block, which re-raises the original error.
def foo
begin
# stuff
rescue StandardError => e
# handle error
raise
end
end
You can also simply return from within the rescue block as well.
def foo
begin
# stuff
rescue StandardError => e
# handle error
return some_value
end
end
As an aside, generally you want to rescue StandardError rather than Exception. All the things that you can reasonably handle within your application are covered under the StandardError. The things outside that are things like out-of-memory, etc., that are outside your control.

Abort using custom error instead of SystemExit

I'm working on an application that I had been failing out of using raise, like this:
raise 'Some error happened!'
This caused caused an unsightly stack trace to be displayed, so I adjusted the code to use abort, like this:
abort 'Some error happened!'
Perfect! Now I can exit with a clear message and no stacktrace.
The problem comes in because, in one instance I need to rescue from this situation. I can do something like:
begin
abort 'Some error happened!'
rescue SystemExit
puts 'Rescued'
end
puts 'Moving on...'
# Outputs:
# Some error happened!
# Rescued
# Moving on...
This has the disadvantages of displaying the abort message despite being rescued and rescuing a fairly vague error. What I would really like to do, is something like this:
class MySuperFancyCustomError < StandardError
def initialize
super
abort 'Some error happened!'
end
end
begin
raise MySuperFancyCustomError
rescue MySuperFancyCustomError
puts 'Rescued'
end
puts 'Moving on...'
# Outputs:
# Some error happened!
But I haven't been able to figure out a way to set this up so that I can rescue from it. I just need it to keep running and output:
Rescued
Moving on...
Instead of failing with:
Some error happened!
Does anyone know of an elegant way to make this happen?
I'd strongly advise against using abort for flow control. Instead try this:
class CustomException < StandardError
end
class Example
def explode
raise CustomException, "Michael Bay here!"
end
end
begin
Example.new.explode
rescue CustomException => e
puts "This happened: %s" % e
end
Here you define your own exception type, and then explicitly catch it.
Now you'll get this output, but you have full control over how, if, and when it's displayed:
This happened: Michael Bay here!

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
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.
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}"
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
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
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

Ruby Rspec message expectations for a caught exception

Is there any way with Rspec to set an expectation for an exception that gets caught? I want to verify that MyException gets raised, but since I am catching the exception Rspec does not appear to know it ever happened.
begin
if success
do good stuff
else
raise MyException.new()
end
rescue MyException => e
clean up
end
I've tried a few things like the following without success. MyException.should_receive(:new) and
Kernel.should_receive(:raise).with(MyException)
You could test the behavior of the rescue block instead of checking for the exception:
class Test
def my_method
if success
# do good stuff
else
raise MyException.new()
end
rescue MyException => e
clean_up
end
end
describe Test do
it "should clean up when unsuccessful" do
subject.stub(:success) { false }
subject.should_receive(:clean_up)
subject.my_method
end
end
I figured out how to do what I needed.
class MyClass
def my_method
begin
if success
do good stuff
else
raise MyException.new
end
rescue MyException => e
# clean up
end
end
end
describe MyClass do
it "Expects caught exception" do
my_instance = MyClass.new()
my_instance.should_receive(:raise).with(any_instance_of(MyException))
my_instance.my_method()
end
end
Thanks for your other suggestions.
I would do as below:
RSpec.describe "matching error message with string" do
it "matches the error message" do
expect { raise StandardError, 'this message exactly'}.
to raise_error('this message exactly')
end
end
copied verbatim from Rspec Documentation

Handle default exception in ruby

How do I implement default behavior for an Exception? begin rescue else doesn't work(which I think should).
And, isn't else meaningless in the scenario? Any code that has to run when no exception is raised does run between the begin-rescue block.
By the way I have the following workaround, but I'm not satisfied with it.
class MyException < Exception
end
class YourException < Exception
end
begin
raise MyException if 2 > 50
raise YourException if 1 < 90
rescue Exception => e
case e.message
when /MyException/
puts "MyException Caught"
else
puts "Default Exception Caught"
end
end
First of all, you really shouldn't subclass Exception. It is the superclass of all Ruby exceptions, including NoMemoryError, SyntaxError, Interrupt, SystemExit; all of which you don't normally need to rescue from. Doing so, whether accidentally or on purpose, is discouraged since it can prevent a program from exiting properly, even if it was interrupted by the user. It can also hide or produce some quite obscure bugs.
What you want to subclass is StandardError, which is the superclass of most Ruby errors we see in day-to-day programming. This class is also the one which will be rescued should you not specify one:
begin
object.do_something!
rescue => error # will rescue StandardError and all subclasses
$stderr.puts error.message
end
I believe this is the "default behavior" you are looking for. You can handle a specific error, then all other errors in general:
class CustomApplicationError < StandardError
end
begin
object.do_something!
rescue CustomApplicationError => error
recover_from error
rescue => error
log.error error.message
raise
end
The else clause is not meaningless in error handling. It will execute the nested code if and only if no exceptions were raised, as opposed to the ensure clause which will execute code regardless. It allows you to handle success cases.
begin
object.do_something!
rescue => error
log.error error.message
else
log.info 'Everything went smoothly'
end
First, I do not understand why you are case-conditioning with the error message. Why not do it with the error itself by their class? Then, it would be like this:
begin
raise MyException if 2 > 50
raise YourException if 1 < 90
rescue Exception => e
case e
when MyException
puts "MyException Caught"
else
puts "Default Exception Caught"
end
end
Second, doing it as above is not the straightforward way. The correct way to do it is:
begin
raise MyException if 2 > 50
raise YourException if 1 < 90
rescue MyException
puts "MyException Caught"
rescue Exception
puts "Default Exception Caught"
end
If YourException were a subclass of StandardError, then it could be captured by rescue without specifying the exception class.

Resources