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:
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:
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.
>> (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.
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:
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.