0

When I test my code with a non-numeric input, Ruby raises a default message. Instead, whenever an exception is raised, I would like my custom message to be printed with a default backtrace.inspect. I expect:

"oops... That was not a number"

to be raised instead of:

invalid value for Float(): "h\n" (ArgumentError)

I wrote the code below, inspired by the following documentation: stackoverflow, rubylearning, github.

class MyCustomError < StandardError
  def message
    "oops... That was not a number"
  end
end

def print_a_number
  begin
    puts "chose a number"
    number = Float(gets)
    raise MyCustomError unless number.is_a? Numeric
    puts "The number you chose is #{number}"
  rescue MyCustomError => err
    puts err.message  
    puts err.backtrace.inspect  
  end  
end

The following code instead does behave as I expect; I can't see why the code below prints my default message while the code above does not:

class MyCustomError < StandardError
  def message
    "The file you want to open does not exist"
  end
end

def open_a_file

  begin
    puts "What file do you want to open?"
    file2open = gets.chomp

    raise MyCustomError unless File.file?(file2open)

    File.open(file2open, 'r') { |x|  
            while line = x.gets 
                puts line
            end
        }

  rescue MyCustomError => err
    puts err.message
    puts err.backtrace.inspect

  end
end
Community
  • 1
  • 1
Asarluhi
  • 1,280
  • 3
  • 22
  • 43

2 Answers2

1

Your unless condition can never be false: Kernel#Float will either raise an exception (in which case your condition isn't even reached) or return a Float (which is a subclass of Numeric).

Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653
  • If the unless condition is commented out, ruby would raise an exception no matter what the input is. If the raise line is all commented out and if 'rescue false' is added as sawa suggests, then no exception would ever be raised. So what would you suggest to use in place of the unless condition? raise looks necessary but cannot stand alone – Asarluhi Jul 04 '15 at 11:40
1

Simply modify your script like this.

class MyCustomError < StandardError
  def message
    "oops... That was not a number"
  end
end

def print_a_number
  begin
    puts "chose a number"
    number = Float(gets) rescue false
    raise MyCustomError unless number.is_a? Numeric
    puts "The number you chose is #{number}"
  rescue MyCustomError => err
    puts err.message
    puts err.backtrace.inspect
  end
end
sawa
  • 165,429
  • 45
  • 277
  • 381
Bunti
  • 1,730
  • 16
  • 20
  • I understand the use of the inline rescue code: it would rescue the error raised by Float(gets) and return false (or anything else). So without begin-rescue-end Ruby it would return "The number you chose is false" or "The number you chose is bad input" if we used rescue "bad input" for instance instead of rescue false. With the begin-rescue-end construction this does not happen, so what we put after the inline rescue seems arbitrary, because any error would be rescued by MyCustomError. So how in this case the inline rescue does work? – Asarluhi Jul 04 '15 at 11:57
  • I don't understand what you intend to say. rescue keyword in "number = Float(gets) rescue false" guards any possible parsing errors of Float method since it's the only statement that comes after begin keyword that could possibly throw an error. This is an implicit rescue and using rescue "bad input" wouldn't change the output of the program because "bad input".is_a? Numeric evaluates to false. It's a matter of best coding practice to use or return a sensible value after an exception to direct the flow of the program in an acceptable manner knowing that erroneous condition has happened. – Bunti Jul 04 '15 at 12:29
  • Whatever we use after the inline rescue, 'false' or even 'true' or any string, in a begin-rescue-end method will not be used by Ruby, so it seems. Any exception would be handled by MyCustomError as far as I understood. – Asarluhi Jul 04 '15 at 12:51
  • So why a second rescue is necessary? – Asarluhi Jul 04 '15 at 12:57
  • No, All errors thrown by Float method will be caught by inline rescue and any value true or false or string value specified after inline rescue will be assigned to number variable and execution will then continue as if nothing has happened. Next line after inline rescue will then evaluate number variable's value and if "number.is_a? Numeric" evaluates to false, MyCustomError will be thrown and then it will be immediately caught by "rescue MyCustomError => err" statement. Hope this helps. – Bunti Jul 04 '15 at 13:04
  • That makes sense. Thanks for the exhaustive explanation. So it seems that an inline rescue is necessary for every line of code that can raise an exception – Asarluhi Jul 04 '15 at 13:54
  • Not every line requires an inline rescue. Any exception that we think should not alter our intended flow of execution might be caught like this. – Bunti Jul 04 '15 at 23:16