Begin, Rescue and Ensure in Ruby

Begin, Rescue and Ensure in Ruby?

Yes, ensure ensures that the code is always evaluated. That's why it's called ensure. So, it is equivalent to Java's and C#'s finally.

The general flow of begin/rescue/else/ensure/end looks like this:

begin
# something which might raise an exception
rescue SomeExceptionClass => some_variable
# code that deals with some exception
rescue SomeOtherException => some_other_variable
# code that deals with some other exception
else
# code that runs only if *no* exception was raised
ensure
# ensure that this code always runs, no matter what
# does not change the final value of the block
end

You can leave out rescue, ensure or else. You can also leave out the variables in which case you won't be able to inspect the exception in your exception handling code. (Well, you can always use the global exception variable to access the last exception that was raised, but that's a little bit hacky.) And you can leave out the exception class, in which case all exceptions that inherit from StandardError will be caught. (Please note that this does not mean that all exceptions are caught, because there are exceptions which are instances of Exception but not StandardError. Mostly very severe exceptions that compromise the integrity of the program such as SystemStackError, NoMemoryError, SecurityError, NotImplementedError, LoadError, SyntaxError, ScriptError, Interrupt, SignalException or SystemExit.)

Some blocks form implicit exception blocks. For example, method definitions are implicitly also exception blocks, so instead of writing

def foo
begin
# ...
rescue
# ...
end
end

you write just

def foo
# ...
rescue
# ...
end

or

def foo
# ...
ensure
# ...
end

The same applies to class definitions and module definitions.

However, in the specific case you are asking about, there is actually a much better idiom. In general, when you work with some resource which you need to clean up at the end, you do that by passing a block to a method which does all the cleanup for you. It's similar to a using block in C#, except that Ruby is actually powerful enough that you don't have to wait for the high priests of Microsoft to come down from the mountain and graciously change their compiler for you. In Ruby, you can just implement it yourself:

# This is what you want to do:
File.open('myFile.txt', 'w') do |file|
file.puts content
end

# And this is how you might implement it:
def File.open(filename, mode='r', perm=nil, opt=nil)
yield filehandle = new(filename, mode, perm, opt)
ensure
filehandle&.close
end

And what do you know: this is already available in the core library as File.open. But it is a general pattern that you can use in your own code as well, for implementing any kind of resource cleanup (à la using in C#) or transactions or whatever else you might think of.

The only case where this doesn't work, if acquiring and releasing the resource are distributed over different parts of the program. But if it is localized, as in your example, then you can easily use these resource blocks.


BTW: in modern C#, using is actually superfluous, because you can implement Ruby-style resource blocks yourself:

class File
{
static T open<T>(string filename, string mode, Func<File, T> block)
{
var handle = new File(filename, mode);
try
{
return block(handle);
}
finally
{
handle.Dispose();
}
}
}

// Usage:

File.open("myFile.txt", "w", (file) =>
{
file.WriteLine(contents);
});

ruby when/how to use rescue/ensure?

In general exceptions should be used for exceptional events. Not regular application flow.

When catching exceptions always be specific and only rescue exceptions that you know what to do with.

begin 
pats "hello world"
rescue
nil
end

This example shows a serious flaw in the code in yout question - you created a black hole that swallows the NoMethodError that would have told us that there is a typo in the code. This makes debugging extremely difficult. This anti-pattern is known as Pokémon Exception Handling (Gotta catch em' all).

ensure just ensures that the code is run no matter if the code raised an exception or not. Its used to for example guarantee that the method closes a file handler that it has opened or rolls back a transaction. Its a really big hammer that should be used very sparingly.

HTTParty does not actually raise exceptions when the response code is a "error" code - because its not an exceptional event. Its a part of normal application flow when dealing with HTTP requests. Shit happens. HTTParty raises exceptions if you can't reach the server at all or you can't even reach the network. Those are exceptional events.

class GeolocationClient
include HTTParty
base_uri "https://www.googleapis.com/geolocation/v1"
format :json
attr_accessor :options

def initialize(api_key:)
@options = {
api_key: api_key
}
end

def geolocate
begin
response = self.class.get("/geolocate", options)
if response.successful?
response
else
logger.info("Geolocation API call was unsuccessful. Status code: #{response.code}")
handle_unsuccessful_request
end
rescue HTTParty::Error => e
logger.warn(e.message)
handle_unsuccessful_request
end
end

private

def handle_unsuccessful_request
{ "message" : "unable to find location" }
end
end

response.successful? tests if the response is in the 2xx "happy range". Use a switch statement if you want handle each code or range of codes separately.

Where to write begin rescue in Ruby (on Rails)

I would argue that when using rescue blocks it is a common pattern to follow two guidelines:

  1. Put the rescue block only around the lowest number of lines possible. Usually, this should only be one line. That certainly depends on your code but in general, exceptions are raised by specific methods. The rescue block should only be around that block if possible. That makes debugging and refactoring much easier.
  2. When adding a rescue block then name those exceptions that you expect to happen precisely. Otherwise you might catch errors that you didn't expect (syntax errors or method calls on nil). What again makes debugging much harder.

In your example, I agree with the PR reviewer but I would add the specific exceptions that might occur and that I want to handle. for example like this:

def get_list
http, uri_path, headers = set_request("whatever_api_url")

begin
http.get(uri_path, headers)
rescue Net::HTTPNotFound => e
# handle page not found
rescue Net::HTTPForbidden => e
# handle invalid credentials
rescue Net::HTTPServerError, Net::HTTPServerException
# handle server error
end
end

Of cause, how to handle exceptions and if that is even possible depends on your application. And therefore there is no hard rule but only there guidelines.

Is nested begin/rescue/ensure valid?

That is a perfectly valid approach. Nesting is often needed, sometimes in the same method as you've done here, and sometimes via the call stack.

Ruby: why rescue block value instead of ensure block?

ensure by default doesn't change the return value. You can bypass that by calling return explicitly inside ensure I haven't seen that pattern too often (if ever) though.

http://blog.leshill.org/blog/2009/11/17/ensure-with-explicit-return.html

What happens during `begin; return; ensure`?

This is just the way it's defined in MRI, more specifically in the function rb_ensure in eval.c (I snipped the actual implementation, but essentially it just generates a Ruby execution context, executes the block in that context and then makes sure that all ensure blocks are run one after the other:

* Equivalent to <code>begin .. ensure .. end</code>.
*
* Calls the function \a b_proc with \a data1 as the argument,
* then calls \a e_proc with \a data2 when execution terminated.
* \return The return value of \a b_proc if no exception occurred,
* or \c Qnil if otherwise.
* \sa rb_rescue
* \sa rb_rescue2
* \sa rb_protect
* \ingroup exception
*/
VALUE
rb_ensure(VALUE (*b_proc)(ANYARGS), VALUE data1, VALUE (*e_proc)(ANYARGS), VALUE data2)
{
...
}

If you're interested in how the parser decides to call this faction, look at parse.y it's part of the rules for bodystmt.

Using rescue and ensure in the middle of code

It seems like you need some error logging since you apparently don't know what type of error you're receiving or where. If this script is standalone you can redirect $stderr to file so that you can read what went wrong.

# put this line at the top of your script
$stderr = File.open("/path/to/your/logfile.log","a")

When an error occurs, ruby will automatically write the error message, class, and backtrace to the log file you specify so that you can trace back the line where things are not going as expected. (When you run a script from the command line, normally this information will just get blurted back to the terminal when an error happens.)

For example, on my desktop I created a file log_stderr.rb with the following (line numbers included):

1   $stderr = File.open("C:/Users/me/Desktop/my_log.log","w")
2
3 #require a file which will raise an error to see the backtrace
4 require_relative 'raise_error.rb'
5
6 puts "code that will never be reached"

Also on my desktop I created the file raise_error.rb with the following (to deepen the backtrace for better example output):

1   # call raise to generate an error arbitrarily
2 # to halt execution and exit the program.
3 raise RuntimeError, 'the program stopped working!'

When I run ruby log_stderr.rb from the command line, my_log.log is created on my desktop with the following:

C:/Users/me/Desktop/raise_error.rb:3:in `<top (required)>': the program stopped working! (RuntimeError)
from C:/Users/me/Desktop/log_stderr.rb:4:in `require_relative'
from C:/Users/me/Desktop/log_stderr.rb:4:in `<main>'

If you are working in a larger environment where your script is being called amidst other scripts then you probably do not want to redirect $stderr because this would affect everything else running in the environment. ($stderr is global as indicated by the $ variable prefix.) If this is the case you would want to implement a begin; rescue; end structure and also make your own logfile so that you don't affect $stderr.

Again, since you don't know where the error is happening you want to wrap the whole script with begin; end

# at the very top of the script, begin watching for weirdness
begin
logfile = File.open("/path/to/your/logfile.log", "w")

require 'csv-mapper'

#. . .

# rescue and end at the very bottom to capture any errors that have happened
rescue => e
# capture details about the error in your logfile
logfile.puts "ERROR:", e.class, e.message, e.backtrace

# pass the error along since you don't know what it is
# and there may have been a very good reason to stop the program
raise e
end

If you find that your error is happening only in the block (firstrow.to_i-1..lastrow.to_i-1).each do |row| you can place the begin; end inside of this block to have access to the local row variable, or else create a top level variable independent of the block and assign it during each iteration of the block to report to your logfile:

begin
logfile = File.open("/path/to/your/logfile.log", "w")
csv_row = "before csv"
#. . .

(firstrow.to_i-1..lastrow.to_i-1).each do |row|
csv_row = row
#. . .
end
csv_row = "after csv"

rescue => e
logfile.puts "ERROR AT ROW: #{csv_row}", e.class, e.message, e.backtrace
raise e
end

I hope this helps!



Related Topics



Leave a reply



Submit