Ruby 2.5 adds Exception#full_message method

Vishal Telangre

Vishal Telangre

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.

class AverageService
  attr_reader :numbers, :coerced_numbers

  def initialize(numbers)
    @numbers = numbers
    @coerced_numbers = coerce_numbers
  end

  def average
    sum / count
  end

  private

  def coerce_numbers
    numbers.map do |number|
      begin
        Float(number)
      rescue Exception => exception
        puts "#{exception.message} (#{exception.class})\n\t#{exception.backtrace.join("\n\t")}"
        puts "Coercing '#{number}' as 0.0\n\n"

        0.0
      end
    end
  end

  def sum
    coerced_numbers.map(&:to_f).sum
  end

  def count
    coerced_numbers.size.to_f
  end
end

average = AverageService.new(ARGV).average
puts "Average is: #{average}"
$ RBENV_VERSION=2.4.0 ruby average_service.rb 5 4f 7 1s0
invalid value for Float(): "4f" (ArgumentError)
	average_service.rb:18:in `Float'
	average_service.rb:18:in `block in coerce_numbers'
	average_service.rb:16:in `map'
	average_service.rb:16:in `coerce_numbers'
	average_service.rb:6:in `initialize'
	average_service.rb:37:in `new'
	average_service.rb:37:in `<main>'

Coercing '4f' as 0.0

invalid value for Float(): "1s0" (ArgumentError)
	average_service.rb:18:in `Float'
	average_service.rb:18:in `block in coerce_numbers'
	average_service.rb:16:in `map'
	average_service.rb:16:in `coerce_numbers'
	average_service.rb:6:in `initialize'
	average_service.rb:37:in `new'
	average_service.rb:37:in `<main>'

Coercing '1s0' as 0.0

Average 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.

class AverageService
  attr_reader :numbers, :coerced_numbers

  def initialize(numbers)
    @numbers = numbers
    @coerced_numbers = coerce_numbers
  end

  def average
    sum / count
  end

  private

  def coerce_numbers
    numbers.map do |number|
      begin
        Float(number)
      rescue Exception => exception
        puts exception.full_message
        puts "Coercing '#{number}' as 0.0\n\n"

        0.0
      end
    end
  end

  def sum
    coerced_numbers.map(&:to_f).sum
  end

  def count
    coerced_numbers.size.to_f
  end
end

average = AverageService.new(ARGV).average
puts "Average is: #{average}"
$ RBENV_VERSION=2.5.0 ruby average_service.rb 5 4f 7 1s0
Traceback (most recent call last):
	6: from average_service.rb:37:in `<main>'
	5: from average_service.rb:37:in `new'
	4: from average_service.rb:6:in `initialize'
	3: from average_service.rb:16:in `coerce_numbers'
	2: from average_service.rb:16:in `map'
	1: from average_service.rb:18:in `block in coerce_numbers'
average_service.rb:18:in `Float': invalid value for Float(): "4f" (ArgumentError)

Coercing '4f' as 0.0

Traceback (most recent call last):
	6: from average_service.rb:37:in `<main>'
	5: from average_service.rb:37:in `new'
	4: from average_service.rb:6:in `initialize'
	3: from average_service.rb:16:in `coerce_numbers'
	2: from average_service.rb:16:in `map'
	1: from average_service.rb:18:in `block in coerce_numbers'
average_service.rb:18:in `Float': invalid value for Float(): "1s0" (ArgumentError)

Coercing '1s0' as 0.0

Average 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.

If this blog was helpful, check out our full blog archive.

Stay up to date with our blogs.

Subscribe to receive email notifications for new blog posts.