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

I ran this code:
begin
print 'Enter something:'
x = gets.to_i # Enter a string
rescue => err
print(err.to_s)
end
I don't get why the rescue block does not catch the exception. It always
returns zero when a string is input, and doesn't trigger the rescue block. I don't know why it isn't working. Can anyone please help?

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.

Related

How can I solve undefined method `[]' on Ruby?

I'm trying to get an if statement for users who put incorrect data.
Here's my code:
class Breweries::CLI
def start
puts "Hello!"
puts "---------------------------"
puts "Please enter your location:"
input = gets.strip.downcase
#data = Breweries::API.get_breweries(input)
#objects = Breweries::HoppyCode.all
if input.length < 1
puts "Sorry!!"
puts "```````"
start
else
display_info
end
end
def display_info
puts "You'll love the following spots!"
puts "********************************"
#objects.each.with_index(1) {|brewery, index| puts "#{index}. #{brewery.name}"}
puts "Please make a selection by index number for more information:"
input = gets.strip.downcase
if(input.to_i > 0)
#brewery = #objects[input.to_i - 1]
puts "name: #{#brewery.name}"
puts "street: #{#brewery.street}"
puts "city: #{#brewery.city}"
puts "phone: #{#brewery.phone}"
puts "website_url: #{#brewery.website_url}"
display_info
elsif (input == "quit")
quit
elsif (input == "menu")
start
end
end
def quit
puts "Goodbye. Drink responsibly and enjoy."
end
end
When I put something that would generate an error, it returns the following:
Please enter your location: nvifpejvf80ejvip
Traceback (most recent call last):
2: from bin/breweriesCLI:6:in `<main>'
1: from /home/munificent-format-5297/Development/breweries/lib/breweries/cli.rb:8:in `start' /home/munificent-format-5297/Development/breweries/lib/breweries/api.rb:6:in `get_breweries': undefined method `[]' for nil:NilClass (NoMethodError)
How can I solve the undefined method '[]' error? Here's the API code in case that's necessary.
class Breweries::API
def self.get_breweries(input)
#breweries_hash = HTTParty.get("https://api.openbrewerydb.org/breweries?by_city=#{input}")
breweries_obj = {
name: #breweries_hash[1]["name"],
street: #breweries_hash[3]["street"],
city: #breweries_hash[4]["city"],
phone: #breweries_hash[10]["phone"],
website_url: #breweries_hash[11]["website_url"]
}
Breweries::HoppyCode.new(breweries_obj)
end
end
When the input is invalid, the call to
#breweries_hash = HTTParty.get("...")
returns not the object you expect (I’d suggest it returns an empty hash.) That makes it impossible to get to details in the following lines. Depending on how are you to handle it, you might decide to e. g. early return from this function, or raise, or do something else.
To approach this, start with debugging the issue, like this:
#breweries_hash = HTTParty.get("...")
puts #breweries_hash.inspect
...
That way you’ll see what gets returned and get the ideas of how to handle it.
If I am right, and what is returned is an empty hash, you might want to early return from this function.
#breweries_hash = HTTParty.get("...")
return if #breweries_hash.empty?
...
Identifying the Problem
There are lots of ways to solve for the nil problem, but at a quick glance it seems like part of the problem here is that you're somehow expecting input to return a valid Hash object from your API call, but an empty String or an instance of FalseClass may not do that. Consider the following:
input = gets.strip.downcase # <RETURN> here gets an empty string
input #=> ""
input.to_i > 0 #=> false
Then consider that some downstream of Breweries::API.get_breweries is expecting to operate on a Hash object instead if an instance of NilClass. In this case, that looks like #breweries_hash[1]["name"] and other operations on #breweries_hash.
Some Options
Without knowing more about your code, I don't want to be prescriptive here. But in general, you can do one or more of the following:
Coerce arguments into the expected class in the method call, the method signature, or the method body. For example, for Array objects:
# coerce a String to an Array, raising an exception if it can't
input = ""
Array(input)
#=> [""]
# coerce some Array to a Hash
array = [:name, "foo", :street, "bar"]
Array(array.each_slice 2).to_h
#=> {:name=>"foo", :street=>"bar"}
Explicitly check that you have an Hash object:
fail "#breweries is not a Hash" unless #breweries.is_a? Hash
Raise an exception rather than return 0 if input isn't actually a valid Integer representation in the first place:
input = Integer(gets.strip.downcase)
Check if your Hash or Array object responds to the relevant method calls, and raise a more helpful exception message:
raise sprintf("#brewery: %s", #brewery.class) unless #brewery.respond_to? :[]
There are other things you might do as well. Broadly speaking, you need to adjust your code to check the return value of your call to ensure it's not nil, then branch/raise/rescue appropriately depending on whether or not you ever expect nils as a valid return value from Breweries::API.get_breweries.
A Note on Using Exceptions for Non-Exceptional Circumstances
As a rule of thumb, you should only raise exceptions for truly unexpected circumstances, or when the program should halt because some condition can't (or shouldn't) be handled within the program during runtime. Which is best in your particular use case is really a design decision, and outside the scope of the original question. However, you might want to read Avdi Grimm's Exceptional Ruby for a deeper explanation of when exceptions might better than branching or handlers (or vice versa), but the choice in your code is a little left of center of the problem you're actually dealing with right now.

Catch a parameter error within a function?

Is it possible for a method to handle an error that happens in a parameter?
e.g.
def add(arg1, arg2)
# If the value causes an error, turn it into 0
rescue nil
0
end
arg1 + arg2
end
add(2, 2+nil) => 2
I understand that shielding parameters and eval-ing it later can be a solution, but in my scenario it becomes too cumbersome to do that.
The problem with this is the error occurs on the line where you provide the arguments as arguments are evaluated prior to the method being called.
Every method call is roughly equivalent to this:
arg1 = 2
arg2 = 2 + nil
add(arg1, arg2)
In this case you can see how the argument itself produces an error. The only way to defer the evaluation of an argument is via a block:
add(2) do
2 + nil
end
Changing your definition to:
def add(v)
v + yield
rescue
v + 0
end
Capturing all exceptions blindly is usually a super bad plan, you may have some kind of fundamental mistake in there. It's best to avoid capturing exceptions unless you have expectations as to what kind they will be.
Here's a version of the method with no exception handling:
def add(*args)
args.map(&:to_i).inject(:+)
end
Now nil.to_i returns 0 so you're covered:
add(2,2,nil)
That will evaluate to 2+2+0 internally.
Generally it's a bad plan to try and bury errors like 2+nil. That's a fundamental mistake that should be addressed. If you wanted to handle nil values you would use .to_i to map them down to something usable.
In this case your add method isn't still called and you have to rescue in the caller.

multiple assignment in rescue clause?

I ran into this sample code as an idiom for exception handling in Ruby:
begin
eval string
rescue SyntaxError, NameError => boom
print "String doesn't compile: " + boom
rescue StandardError => bang
print "Error running script: " + bang
end
I'm confused particularly about the local variable assignment line with multiple exceptions:
rescue SyntaxError, NameError => boom. Does that mean the local var boom will take on either the SyntaxError or NameError object? Or is it just the NameError that will be assigned?
It's further confusing because the code itself throws a TypeError, I think perhaps because string is undefined, but that may be beside the point.
I found the code above at http://phrogz.net/programmingruby/tut_exceptions.html. Was that your source?
In any event, the local variable in that code is assigned whichever error is raised; it's just specified after the last one.
And yes, it's throwing TypeError because the errors do not implicitly coerce to a string in today's Ruby. Perhaps they used to when the book was initially published. You need to add .message to the local variable reference to get the error message (e.g. + boom.message).

Checking the type of a method parameter

I am not sure if an object I pass to a method is of the proper type. I might pass a string to a function that can only handle integers. What about some kind of runtime ensurance? I couldn't see a better option than:
def someFixNumMangler(input)
raise "wrong type: integer required" unless input.class == FixNum
other_stuff
end
Any better alternatives?
Use the Kernel#Integer method to convert the input before using it. It will raise an ArgumentError when the input could not be converted to an integer in any reasonable fashion.
def my_method(number)
number = Integer(number)
# do something with number, which is now guaranteed to be an integer
end
I recommend Avdi Grimm's new book Confident Ruby for more insight into this.
If you really need to do type checks, then yes, you only have runtime checking. Code in the question is ok. You can also use .is_a?.
def someFixNumMangler(input)
raise "wrong type: integer required" unless input.is_a?(FixNum)
other_stuff
end
The checks may take different forms. If you expect, say, a string and you call string methods on it (upcase, gsub, etc), the code will blow up if anything other than string is passed. Unless, of course, you pass an object that is not a string, but behaves just like one (has the same methods that you call). This is the essence of duck typing.
What if your method looked like this?
def someFixNumMangler(input)
input = input.to_i
puts "got this: #{input} of #{input.class}"
end
someFixNumMangler(10)
someFixNumMangler('42')
someFixNumMangler(File)
# >> got this: 10 of Fixnum
# >> got this: 42 of Fixnum
# ~> -:2:in `someFixNumMangler': undefined method `to_i' for File:Class (NoMethodError)
# ~> from -:9:in `<main>'
As long as an argument responds to #to_i, you don't actually care what its type is.

ruby "on error resume next" function

Is there a way of doing the old "on error resume next" routine in ruby?
I've got array of value filled in dynamically from elsewhere (read from MQTT topics to be precise) then I want to do a bunch of numeric calculations on them and publish the results. The values SHOULD be numeric but are possibly missing or non-numeric.
At the moment my code looks something like
values=[]
//values get loaded here
begin
Publish('topic1',value[0]*10+value[1])
rescue TypeError,NoMethodError,ZeroDivisionError
end
begin
Publish('topic2',value[3]/value[4])
rescue TypeError,NoMethodError,ZeroDivisionError
end
//etc etc
If the calculation fails for any reason the program should just skip that step and go on.
It works but surely theres a better way than all those identical begin..rescue blocks? Ruby is about "DRY" after all..
Is there a way of re-writing the above so that a single begin..rescue construct is used while still allowing all calculations to be attempted?
UPDATED
How safe to do something like
def safe_Publish(topic,value)
return if value.nil?
Publish(topic,value)
end
and call with
safe_Publish('topic2',(value[3]/value[4] rescue nil))
The main problem is that the above catches ALL exceptions not just the ones I'm expecting which makes me a little nervous.
The on error resume next coding style is really dangerous - as it makes finding new bugs you accidentally introduce to your program very hard to find. Instead, I would just write a different version of publish that doesn't throw those exceptions:
def try_publish(topic_name)
begin
Publish('topic1',yield)
rescue TypeError,NoMethodError,ZeroDivisionError
# are you sure you don't want to do anything here? Even logging the errors
# somewhere could be useful.
end
end
You can then call this with:
try_publish('topic1') { value[0]*10+value[1] }
If TypeError,NoMethodError or ZeroDivisionError are thrown by the expression, they will be caught and ignored.
Now your original method won't require any rescues.
If you really wanted an on error resume next, you could possibly do it by monkey patching the raise method in Kernel, but that would be a horrible idea.
If you think a bit more carefully about what you are doing, and why you want on error resume next, I think you will see that you don't really need to suppress all exceptions. As the other posters pointed out, that would make it hard to find and fix bugs.
Your problem is that you have a bunch of numbers scraped from the Internet, and want to run some calculations on them, but some may be invalid or missing. For invalid/missing numbers, you want to skip over any calculations which would use those numbers.
A few possible solutions:
Pre-filter your data and remove anything which is not a valid number.
Put each calculation you want to do into a method of its own. Put a rescue Exception on the method definition.
Define "safe" wrappers for the numeric classes which don't raise exceptions on divide by zero, etc. Use these wrappers for your calculations.
The "wrappers" might look something like this (don't expect complete, tested code; this is just to give you the idea):
# This is not designed for "mixed" arithmetic between SafeNumerics and ordinary Numerics,
# but if you want to do mixed arithmetic, that can also be achieved
# more checks will be needed, and it will also need a "coerce" method
class SafeNumeric
attr_reader :__numeric__
def initialize(numeric)
#__numeric__ = numeric.is_a?(String) ? numeric.to_f : numeric
end
def zero?
#__numeric__.zero?
end
def /(other)
if other.zero? || #__numeric__.nil? || other.__numeric__.nil?
SafeNumeric.new(nil) # could use a constant for this to reduce allocations
else
SafeNumeric.new(#__numeric__ / other.__numeric__)
end
end
def to_s; #__numeric__.to_s; end
def inspect; #__numeric__.inspect; end
# methods are also needed for +, -, *
end
Then use it like:
numbers = scraped_from_net.map { |n| SafeNumeric.new(n) }
# now you can do arithmetic on "numbers" at will
This shows how to wrap a bunch of quick operations into a loop with each one being protected by a begin/rescue:
values = [1,2,3,0,4]
ops = [ ->{values[0]/values[1]}, ->{values[2]/values[3]} ]
ops.each do |op|
begin
puts "answer is #{op.call}"
rescue ZeroDivisionError
puts "cannot divide by zero"
end
end
I prefer the safe_publish method, however, as you can unit test that and it encapsulates the logic of making safe calls and handling errors in a single place:
def safe_publish(topic, &block)
begin
value = block.call
publish(topic, value)
rescue
# handle the error
end
end
and then you can call this with code like:
safe_publish 'topic0' do
value[0]*10+value[1]
end

Resources