This blog is part of our Ruby 2.5 series.
Before Ruby 2.5, if we want to log a caught exception, we would need to format it ourselves.
1class AverageService 2 attr_reader :numbers, :coerced_numbers 3 4 def initialize(numbers) 5 @numbers = numbers 6 @coerced_numbers = coerce_numbers 7 end 8 9 def average 10 sum / count 11 end 12 13 private 14 15 def coerce_numbers 16 numbers.map do |number| 17 begin 18 Float(number) 19 rescue Exception => exception 20 puts "#{exception.message} (#{exception.class})\n\t#{exception.backtrace.join("\n\t")}" 21 puts "Coercing '#{number}' as 0.0\n\n" 22 23 0.0 24 end 25 end 26 end 27 28 def sum 29 coerced_numbers.map(&:to_f).sum 30 end 31 32 def count 33 coerced_numbers.size.to_f 34 end 35end 36 37average = AverageService.new(ARGV).average 38puts "Average is: #{average}"
1$ RBENV_VERSION=2.4.0 ruby average_service.rb 5 4f 7 1s0 2invalid value for Float(): "4f" (ArgumentError) 3 average_service.rb:18:in `Float' 4 average_service.rb:18:in `block in coerce_numbers' 5 average_service.rb:16:in `map' 6 average_service.rb:16:in `coerce_numbers' 7 average_service.rb:6:in `initialize' 8 average_service.rb:37:in `new' 9 average_service.rb:37:in `<main>' 10 11Coercing '4f' as 0.0 12 13invalid value for Float(): "1s0" (ArgumentError) 14 average_service.rb:18:in `Float' 15 average_service.rb:18:in `block in coerce_numbers' 16 average_service.rb:16:in `map' 17 average_service.rb:16:in `coerce_numbers' 18 average_service.rb:6:in `initialize' 19 average_service.rb:37:in `new' 20 average_service.rb:37:in `<main>' 21 22Coercing '1s0' as 0.0 23 24Average of [5.0, 0.0, 7.0, 0.0] is: 3.0
It was proposed that there should be a simple method to print the caught exception using the same format that ruby uses while printing an uncaught exception.
Some of the proposed method names were display, formatted, to_formatted_s, long_message, and full_message.
Matz approved the Exception#full_message method name.
In Ruby 2.5, we can re-write above example as follows.
1class AverageService 2 attr_reader :numbers, :coerced_numbers 3 4 def initialize(numbers) 5 @numbers = numbers 6 @coerced_numbers = coerce_numbers 7 end 8 9 def average 10 sum / count 11 end 12 13 private 14 15 def coerce_numbers 16 numbers.map do |number| 17 begin 18 Float(number) 19 rescue Exception => exception 20 puts exception.full_message 21 puts "Coercing '#{number}' as 0.0\n\n" 22 23 0.0 24 end 25 end 26 end 27 28 def sum 29 coerced_numbers.map(&:to_f).sum 30 end 31 32 def count 33 coerced_numbers.size.to_f 34 end 35end 36 37average = AverageService.new(ARGV).average 38puts "Average is: #{average}"
1$ RBENV_VERSION=2.5.0 ruby average_service.rb 5 4f 7 1s0 2Traceback (most recent call last): 3 6: from average_service.rb:37:in `<main>' 4 5: from average_service.rb:37:in `new' 5 4: from average_service.rb:6:in `initialize' 6 3: from average_service.rb:16:in `coerce_numbers' 7 2: from average_service.rb:16:in `map' 8 1: from average_service.rb:18:in `block in coerce_numbers' 9average_service.rb:18:in `Float': invalid value for Float(): "4f" (ArgumentError) 10 11Coercing '4f' as 0.0 12 13Traceback (most recent call last): 14 6: from average_service.rb:37:in `<main>' 15 5: from average_service.rb:37:in `new' 16 4: from average_service.rb:6:in `initialize' 17 3: from average_service.rb:16:in `coerce_numbers' 18 2: from average_service.rb:16:in `map' 19 1: from average_service.rb:18:in `block in coerce_numbers' 20average_service.rb:18:in `Float': invalid value for Float(): "1s0" (ArgumentError) 21 22Coercing '1s0' as 0.0 23 24Average of [5.0, 0.0, 7.0, 0.0] is: 3.0
Note that, Ruby 2.5 prints exception backtrace in reverse order if STDERR is unchanged and is a TTY as discussed in our previous blog post.