Posted over 9 years ago. Visible to the public.

How to make Rational#to_s return strings without denominator 1 again

The way Rational#to_s works on Ruby has changed from Ruby 1.9 on. Here is how to get the old behavior back.

You may want this for things where Rationals are being used, like when subtracting Date objects from one another.

What's happening?

Converting a Rational to a String usually does something like this:

Copy
1.8.7 > Rational(2, 3).to_s => "2/3" 1.9.3 > Rational(2, 3).to_s => "2/3" 2.0.0 > Rational(2, 3).to_s => "2/3"

However, when you have a Rational that simplifies to an integer, you will only get a String that represents that integer on Ruby 1.8:

Copy
1.8.7 > Rational(8, 4).to_s => "2" 1.9.3 > Rational(8, 4).to_s => "2/1" 2.0.0 > Rational(8, 4).to_s => "2/1"

This will come bite you in the butt when you subtract dates, since they use Rational representation internally.

Copy
>> (Date.tomorrow - Date.yesterday).to_s >> "2/1"

Sure, you could just call to_i on that, but often times the results of such subtractions go into a view. When upgrading a Rails application from Ruby 1.8 to 1.9, this will cause significant pain, and under most circumstances you probably do not want a "2/1" representation anyway.

How to get the old behavior back

Ruby 1.8 behaves as expected, and for Ruby 1.9 and Ruby 2.0 we will just check if the rational number's denominator is 1 and omit it. Put this into a place like lib/core_ext/rational.rb or whatever floats your boat.

Copy
if RUBY_VERSION >= '1.9' class Rational def to_s_with_proper_integers if denominator == 1 numerator.to_s else to_s_without_proper_integers end end alias_method_chain :to_s, :proper_integers end end

If you don't have alias_method_chain (plain Ruby scripts, for example), just manually do what the method does by using 2 alias_method calls.

Here is a spec that goes along with the fix:

Copy
require 'spec_helper' describe Rational do describe '#to_s' do it 'should return correct results for actual rationals, as usual' do Rational(6, 4).to_s.should == '3/2' Rational(1, 11).to_s.should == '1/11' Rational(-2, 3).to_s.should == '-2/3' end it 'should not return a rational representation of integer results, like Ruby 1.8 did' do Rational(2, 1).to_s.should == '2' Rational(12, 4).to_s.should == '3' Rational(-10, 5).to_s.should == '-2' Rational(0, 5).to_s.should == '0' end end end

That patch should not have a negative impact on your application, since you are only changing the conversion of Rational to a String, not any mathematical logic of the Rational class.

Does your version of Ruby on Rails still receive security updates?
Rails LTS provides security patches for unsupported versions of Ruby on Rails (2.3, 3.2, 4.2 and 5.2).

Owner of this card:

Avatar
Arne Hartherz
Last edit:
over 9 years ago
About this deck:
We are makandra and do test-driven, agile Ruby on Rails software development.
License for source code
Posted by Arne Hartherz to makandra dev
This website uses short-lived cookies to improve usability.
Accept or learn more