I'm trying to understand exceptions in Ruby but I'm a little confused. The tutorial I'm using says that if an exception occurs that does not match any of the exceptions identified by the rescue statements, you can use an "else" to catch it:
begin
# -
rescue OneTypeOfException
# -
rescue AnotherTypeOfException
# -
else
# Other exceptions
ensure
# Always will be executed
end
However, I also saw later in the tutorial "rescue" being used without an exception specified:
begin
file = open("/unexistant_file")
if file
puts "File opened successfully"
end
rescue
file = STDIN
end
print file, "==", STDIN, "\n"
If you can do this, then do I ever need to use else? Or can I just use a generic rescue at the end like this?
begin
# -
rescue OneTypeOfException
# -
rescue AnotherTypeOfException
# -
rescue
# Other exceptions
ensure
# Always will be executed
end
The else is for when the block completes without an exception thrown. The ensure is run whether the block completes successfully or not. Example:
begin
puts "Hello, world!"
rescue
puts "rescue"
else
puts "else"
ensure
puts "ensure"
end
This will print Hello, world!, then else, then ensure.
Here's a concrete use-case for else in a begin expression. Suppose you're writing automated tests, and you want to write a method that returns the error raised by a block. But you also want the test to fail if the block doesn't raise an error. You can do this:
def get_error_from(&block)
begin
block.call
rescue => err
err # we want to return this
else
raise "No error was raised"
end
end
Note that you can't move the raise inside the begin block, because it'll get rescued. Of course, there are other ways without using else, like checking whether err is nil after the end, but that's not as succinct.
Personally, I rarely use else in this way because I think it's rarely needed, but it does come in handy in those rare cases.
EDIT
Another use case occurred to me. Here's a typical begin/rescue:
begin
do_something_that_may_raise_argument_error
do_something_else_when_the_previous_line_doesnt_raise
rescue ArgumentError => e
handle_the_error
end
Why is this less than ideal? Because the intent is to rescue when do_something_that_may_raise_argument_error raises ArgumentError, not when do_something_else_when_the_previous_line_doesnt_raise raises.
It's usually better to use begin/rescue to wrap the minimum code you want to protect from a raise, because otherwise:
you may mask bugs in the code that wasn't supposed to raise
the intention of rescue is harder to decipher. Someone (including your future self) may read the code and wonder "Which expression did I want to protect? It looks like expression ABC... but maybe expression DEF too???? What was the author intending?!" Refactoring becomes much more difficult.
You avoid those problems with this simple change:
begin
do_something_that_may_raise_argument_error
rescue ArgumentError => e
handle_the_error
else
do_something_else_when_the_previous_line_doesnt_raise
end
The else block in a begin rescue end block is used when you are perhaps expecting an exception of some sort to occur. If you run through all of your expected exceptions but still have nothing raised, then in your else block you can do whatever's needed now that you know that your original code ran error free.
The only reason I can see for the else block is if you want to execute something before the ensure block when the code in the begin block didn't raise any errors.
begin
puts "Hello"
rescue
puts "Error"
else
puts "Success"
ensure
puts "my old friend"
puts "I've come to talk with you again."
end
Thanks to else you sometimes can merge two nested begin end blocks.
So (simplified example from my current code) instead of:
begin
html = begin
NetHTTPUtils.request_data url
rescue NetHTTPUtils::Error => e
raise unless 503 == e.code
sleep 60
retry
end
redo unless html["market"]
end
you write:
begin
html = NetHTTPUtils.request_data url
rescue NetHTTPUtils::Error => e
raise unless 503 == e.code
sleep 60
retry
else
redo unless html["market"]
end
Related
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.
I want to catch the PuppetError::Error exception so it's not fatal, but a PuppetError::ExecError should be fatal.
My problem seems to be that the rescue clause is catching the PuppetError::Error then elevating it to ExecError status, so it isn't caught by the calling code. I need to limit the rescue clause to one line, the Open3.. line. But how?
module PuppetCert
def self.remove(instance_hostname)
begin
Open3::popen3("puppet cert clean #{instance_hostname}") do |stdin, stdout, stderr, wait_thr|
if wait_thr.value.success?
puts "success"
else
raise PuppetError::Error
return
end
end
rescue
raise PuppetError::ExecError
end
end
end
instances.each do |i|
begin
PuppetCert.remove(i)
rescue PuppetError::Error
logger.error("PuppetCert.remove failed. Not fatal")
end
end
Thanks in advance
EDIT: To clarify, both PuppetError type are custom types I have created (not shown here) as a way to identify different sorts of failure. PuppetError::Error is for when the system call to the puppet binary fails; PuppetError:ExecError is for when Open3::popen3 fails because it can't find the puppet binary.
You could use two rescue clauses, one to catch PuppetError::Error and raise it again, then on the other catch everything else:
module PuppetCert
def self.remove(instance_hostname)
begin
Open3::popen3("puppet cert clean #{instance_hostname}") do |stdin, stdout, stderr, wait_thr|
if wait_thr.value.success?
puts "success"
else
raise PuppetError::Error
return
end
end
rescue PuppetError::Error => error
raise error
rescue
raise PuppetError::ExecError
end
end
end
The first rescue clause (the order matters, so it must come first) rescues PuppetError::Error and simply raises it again, that is, it only intercepts it before getting to the more general rescue, so it won't get promoted. Every other exception will be rescued by the second rescue clause and promoted as PuppetError::ExecError.
I get exception Thrift::TransportException (end of file reached) and I want to rescue it with the message("end of file reached").
Now I do
begin
#...
rescue Thrift::TransportException => e
raise e unless "end of file reached" == e.message
# do whatever if it is not end of file reached.
end
Is there any method to do this rubyish?
If it can be avoided, don't depend on the message for logic - messages are just text and the developers of Thrift are free to change them at any given point in time, breaking your app.
TransportException in Thrift has a type, which is one of the constants you can see here. The one relevant for you is END_OF_FILE.
As for the implementation in code, there is no better way to check a property of the exception than doing it in the rescue block, so:
begin
#...
rescue Thrift::TransportException => e
raise e unless e.type == Thrift::TransportException::END_OF_FILE
# do whatever if it is not end of file reached.
end
You don't need to raise in a rescue block, i'll suggest take an instance variable show it wherever you want.Also, 1==a is very ugly, it's like blue is a sky
begin
#...
rescue Thrift::TransportException => e
#error = e.message if e.message == "end of file reached"
# do whatever if it is not end of file reached.
end
Any time my program stops execution (either when shut down by cmd-c or when it encounters an exception), I want to take a few actions to shut down properly.
When I do cmd-c, I receive the signal TERM. What signal is sent when the program encounters an exception that is raised? How do I trap this with Signal.trap(...)?
You could wrap your code in a begin-ensure-end block. It would catch exceptions and CTRL-C. (You could add a rescue clause before the ensure).
begin
sleep 10 #try CTRL-C here
raise "kaboom" #RuntimeError
ensure
puts "This must be printed no matter what."
end
An exception is not a signal. The Ruby interpreter handles exceptions all in user code; there's nothing to trap.
If you want to handle exceptions, you need to do so in a rescue block.
You can't catch the exception as a signal, but you can do something when it's raised using the 'EXIT' signal:
Signal.trap('EXIT') do
puts "Terminating..."
shutdown()
end
However, I just stated that you can do this; you really should use begin and rescue.
The point wit exceptions is not trapping the signal via Signal.trap but rather wrapping the code that may raise an exception in a begin-rescue-end block. You have more Options though:
begin
# here goes the code that may raise an exception
rescue ThisError
# this code is executed when 'ThisError' was raised
rescue ThatError, AnotherError
# this code is executed when 'ThatError' or 'AnotherError' was raised
rescue
# this code is executed when any other StandardError was raised
else
# this code is executed when NO exception was raised
ensure
# this code is always executed
end
Here are some bit more practical examples of how to use this:
def compute_something(x,y)
raise ArgumentError, 'x must not be lower than 0' if x < 0
x/y + y
end
begin
compute_something(-10,5)
rescue ArgumentError
puts "some argument is erroneous!"
end
puts "---"
x=100
y=0
begin
compute_something(x,y)
rescue ZeroDivisionError
puts "division by zero! trying to fix that..."
y=1
retry
else
puts "everything fine!"
end
puts "---"
begin
compute_something(1)
rescue => e
puts "the following error occured:"
puts e
end
puts "---"
begin
exit
ensure
puts "i am always called!"
end
this outputs:
some argument is erroneous!
---
division by zero! trying to fix that...
everything fine!
---
the following error occured:
wrong number of arguments (1 for 2)
---
i am always called!
As an alternative to the above solutions, you could look into the at_exit method.
short:
Is there a way in Ruby to DRY-ify this:
def entry_point_one
begin
do_something
rescue MySyntaxErrorOne, MySyntaxErrorTwo, MySyntaxErrorEtc => syn_err
raise syn_err.exception(syn_err.message)
end
end
def entry_point_two
begin
do_something_else
rescue MySyntaxErrorOne, MySyntaxErrorTwo, MySyntaxErrorEtc => syn_err
raise syn_err.exception(syn_err.message)
end
end
longer:
I'm building an interpreter. This interpreter can be called using different entry points. If I feed this interpreter a 'dirty' string, I expect it to raise an error. However, it would be nice if I don't get spammed by the by the entire back trace of every method called directly or indirectly by do_something, especially since the interpreter makes use of recursion.
As you can see in the above snippet, I already know a way to re raise an error and thereby removing the back trace. What I would like do is remove the duplication in the above example. The closest I have come thus far is this:
def entry_point_one
re_raise_known_exceptions {do_something}
end
def entry_point_two
re_raise_known_exceptions {do_something_else}
end
def re_raise_known_exceptions
yield
rescue MySyntaxErrorOne, MySyntaxErrorTwo, MySyntaxErrorEtc => syn_err
raise syn_err.exception(syn_err.message)
end
But that makes the method re-raise-known-exceptions show up in the back trace.
edit: I guess what I want would be something like a C pre-processing macro
You can just use the splat on an array.
Straight from IRB:
COMMON_ERRORS = [ArgumentError, RuntimeError] # add your own
def f
yield
rescue *COMMON_ERRORS => err
puts "Got an error of type #{err.class}"
end
f{ raise ArgumentError.new }
Got an error of type ArgumentError
f{ raise 'abc' }
Got an error of type RuntimeError
while thinking about it a bit more, I came up with this:
interpreter_block {do_something}
def interpreter_block
yield
rescue ExceptionOne, ExceptionTwo, ExceptionEtc => exc
raise exc.exception(exc.message)
end
Although it's still not quiet what I would like to have, at least now the extra entry in the back trace has become somewhat better looking.
It might be slightly evil, but I think you can simply remove the line from the backtrace ;-)
COMMON_ERRORS = [ArgumentError, RuntimeError]
def interpreter_block
yield
rescue *COMMON_ERRORS => err
err.backtrace.delete_if{ |line| line=~/interpreter_block/ }
raise err
end
I'm not sure it's such a good idea though. You'll have a hell of a lot of fun debugging your interpreter afterward ;-)
Side note: Treetop may be of interest to you.
This is a touch hackish, but as far as cleaning up the backtrace goes, something like this works nicely:
class Interpreter
def method1
error_catcher{ puts 1 / 0 }
end
def error_catcher
yield
rescue => err
err.set_backtrace(err.backtrace - err.backtrace[1..2])
raise err
end
end
The main trick is this line err.set_backtrace(err.backtrace - err.backtrace[1..2]). Without it, we get the following (from IRB):
ZeroDivisionError: divided by 0
from (irb):43:in `/'
from (irb):43:in `block in method1'
from (irb):47:in `error_catcher'
from (irb):43:in `method1'
from (irb):54
from /Users/peterwagenet/.ruby_versions/ruby-1.9.1-p129/bin/irb:12:in `<main>'
What we don't want in there are the second and third lines. So we remove them, ending up with:
ZeroDivisionError: divided by 0
from (irb):73:in `/'
from (irb):73:in `method1'
from (irb):84
from /Users/peterwagenet/.ruby_versions/ruby-1.9.1-p129/bin/irb:12:in `<main>'
If you have all of the information you need in the exceptions, and you do not need the backtrace at all, you can just define your own error and raise that, instead of reraising the existing exception. This will give it a fresh backtrace. (Of course, presumably your sample code is incomplete and there is other processing happening in the rescue block -- otherwise your best bet is to just let the error bubble up naturally.)
class MyError < StandardError; end
def interpreter_block
yield
rescue ExceptionOne, ExceptionTwo, ExceptionEtc => exc
raise MyError
end