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:
- 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. Therescue
block should only be around that block if possible. That makes debugging and refactoring much easier. - 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 onnil
). 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
Convert Hash to Openstruct Recursively
How to Pass <Arguments> to Irb If I Don't Specify <Programfile>
Automatic Logging of Datamapper Queries
Cheat Sheet for All Design Patterns Implemented in Ruby
How to Print Information About a Net:Httprequest for Debug Purposes
How to Color Unit Tests with Lib Minitest or Test:Unit
Ruby Gem Listed, But Won't Load (Gem in User Dir, Not Ruby Dir)
Omniauth Facebook Expired Token Error
Ruby on Rails Webpacker Can't Find Images Under Asset_Pack_Path
Xpath Parent Attribute of Selection
How to Stop God from Leaving Stale Resque Worker Processes
How to Make a Ruby Script Run Once a Second
Where to Insert Rack::Deflater in the Rack