Begin Rescue Not Catching Error

Begin Rescue not catching error

rescue without a parameter just rescues exceptions that inherit from StandardError. To rescue a SyntaxError use rescue SyntaxError.

To rescue all exceptions you would use rescue Exception, but note that that's a bad idea (which is why it's not the default behavior of rescue) as explained here and here. Especially this part:

Rescuing Interrupt prevents the user from using CTRLC to exit the program.

Rescuing SignalException prevents the program from responding correctly to signals. It will be unkillable except by kill -9.

Why is this exception not being caught by the rescue block?

Behavior Differs Between String#to_i and Kernel#Integer

The reason your exception handler is never called is because String#to_i doesn't raise an exception, even if it can't detect a valid integer within the String object. In such cases, it simply returns 0.

In comparison, the behavior of Kernel#Integer is more complex, but is expected to raise ArgumentError or TypeError if the contents of the string do not strictly conform to a numeric representation.

So, to minimally refactor your existing code to raise an exception on non-numeric inputs:

begin
print 'Enter something: '
x = Integer gets
rescue => err
# Do something other than just print err on STDERR, which is the
# default behavior anyway. Perhaps send it to STDOUT instead.
puts "I received an exception: #{err}"

# After handling, re-raise the original exception with or without
# passing the original exception object. `raise` and `raise err`
# will do the same thing here.
raise

# For more advanced uses, you can also do something else like raise
# a different exception (e.g. TypeError), or modify the exception
# object stored in err and raise that modified object instead.
end

The following user inputs will each convert cleanly:

  • 1
  • 2
  • 0xff

The code will even handle initial/trailing spaces, newlines, and carriage returns in most cases, without any additional effort on your part. However:

  • Enter something: one

    ArgumentError: invalid value for Integer(): "one\n"

  • Enter something: "1"

    ArgumentError: invalid value for Integer(): "\"1\"\n"

  • Enter something: nil

    ArgumentError: invalid value for Integer(): "nil\n"

In general, you can rely on Kernel#Integer to raise an exception when necessary, which simplifies your code a lot. However, see caveats below.

Caveats

These examples don't require it, but you might also want to sanitize your input with #strip, #chomp, or other string transformations when necessary. Your mileage in this regard will vary greatly with your real-world use case, but while Kernel#Integer generally does the right thing, and Ruby encourages relying on exceptions to handle non-standard edge cases, it's often unwise to trust user-tainted inputs.

It's also worth noting that both String#to_i and Kernel#Integer might operate on values other than user input, in which case know that Integer(nil) will raise:

Integer nil

TypeError: can't convert nil into Integer

This might be important. Again, your mileage may vary.

Begin and Rescue block exception handling

What if params[:game][:tier] was "[100,200]; system('rm -rf /')"?

Since the incoming data is expected to be an array, I would not use eval but JSON.parse instead:

> JSON.parse("[100,200]")
=> [100, 200]
> JSON.parse("[100,200] abc")
JSON::ParserError: 746: unexpected token at 'abc'...

Then rescue from only a JSON::ParserError exception

rescue JSON::ParserError => e

This will also solve the rescue not catching the exception problem you're having.

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.

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.



Related Topics



Leave a reply



Submit