Ruby 2.5 adds Exception#full_message method

Vishal Telangre

By Vishal Telangre

on March 13, 2018

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.