The Artima Developer Community
Sponsored Link

Ruby Buzz Forum
Ruby 1.9 Compatibility for Ri_Cal, What It Took, and Some Side Thoughts

0 replies on 1 page.

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 0 replies on 1 page
Rick DeNatale

Posts: 269
Nickname: rdenatale
Registered: Sep, 2007

Rick DeNatale is a consultant with over three decades of experience in OO technology.
Ruby 1.9 Compatibility for Ri_Cal, What It Took, and Some Side Thoughts Posted: Apr 26, 2009 6:07 PM
Reply to this message Reply

This post originated from an RSS feed registered with Ruby Buzz by Rick DeNatale.
Original Post: Ruby 1.9 Compatibility for Ri_Cal, What It Took, and Some Side Thoughts
Feed Title: Talk Like A Duck
Feed URL: http://talklikeaduck.denhaven2.com/articles.atom
Feed Description: Musings on Ruby, Rails, and other topics by an experienced object technologist.
Latest Ruby Buzz Posts
Latest Ruby Buzz Posts by Rick DeNatale
Latest Posts From Talk Like A Duck

Advertisement

I just wrote about the imminent release of my new gem rical which is a fresh implementation of the RFC 2445 (iCalendar) specification for Ruby.

Almost as an afterthought, I decided to look at what it would take to make the gem work with Ruby 1.8.6, 1.8.7 and 1.9.1. I hadn’t really thought about 1.9 compatibility while working on rical, so I didn’t know how much work would be needed.

I’d seen that David Chelimsky had said that a new version of RSpec which ran on Ruby 1.9 was imminent but that was over a month ago and I hadn’t heard an update, but David responded quickly to my email (using his iPhone while stopped at a traffic light!) that yes that rspec 1.2.4 does indeed work with 1.9, and I was off to the races.

Executive Summary

It turns out that making rical compatible with Ruby 1.9 was fairly easy. The problems fell into a few areas:

  • Ruby 1.9 has made DateTime parsing less flexible.
  • Ruby 1.9 Range#include? requires the range to be iterable
  • Hash enumeration order is different

Multiruby

The first step was to set up multiruby. I’d used multiruby before but not in a while. Actually I had just looked at it again, because the last few alpha drops of Maglev have required a specific patchlevel of Ruby 1.8.6 and ParseTree. Maglev currently relies on ParseTree to turn Ruby source code into an s-expression which it then turns into objects and extended Gemstone Smalltalk byte codes. Eventually this will be replaced by a native compiler of some sort.

I figured that the easiest way to get this version was via multiruby, but I was having troubles getting Maglev to work with it. It couldn’t find the Parsetree gem. It turned out that I had a back level of ZenTest (which contains multiruby along with autotest and a lot of other useful stuff). But I got tied up most of yesterday with the Radiant sprint, so I didn’t get things straightened out until I got home yesterday evening.

I updated zentest, blew away the .multiruby directory in my home directory, and reinstalled the ‘usual’ and ruby gems

multiruby_setup the_usual
multiruby_setup update:rubygems

Now I could start to try to run my specs under 1.8.6, 1.8.7, and 1.9.

multiruby -S rake spec

This led to a series of missing gems, first rspec. Multiruby by default has a gem directory for each version of ruby which is separate from your normal gem directory. It’s possible to share these, but going with the path of least resistance, I just installed each missing gem as I discovered it. For example to install rspec for all the multiruby installed versions:

multiruby -S gem install rspec

I used the bones gem to setup the rical gem. When I installed this gem I ran into a hitch. Although the rical gem doesn’t need it for deployment, the bones gem (like many of it’s other brother gem developer assistants) adds rake tasks which depend on other gems. Since the bones provided rakefile defined a spec:rcov task which requires the rcov gem, which doesn’t seem to be 1.9 compatible yet, I got to a point where although the specs would run on 1.8.6 and 1.8.7, the rakefile (or maybe it was a task file) was preventing rake from initializing under 1.9.

At this point I took advantage of the separate gem directories under the .multiruby directory structure and hacked the copy of the bones gem under the 1.9 install to remove the need for rcov, since I was really only interested in whether the specs ran on 1.9.

The Issues

As I said, I didn’t know what to expect in terms of compatibility. The spec suite for rical has 550 spec examples.

As it turns out, although there were lots of failures, they fell into large clumps.

DateTime.parse differences

In Ruby 1.8 the DateTime.parse method uses heuristics which attempt to figure out whether certain strings represent dates in different local formats. For example, here in the US a date like “12/8/2001” is interpreted as December 8, 2001, while in most of Europe it would be August 12, 2001. I’ve gotten in the personal habit of writing such a date as 8 December 2001, which uses the European order, but is also understandable to my countrymen.

I wrote many spec examples directly from the recurrence rule use cases in the RFC2445 spec which tend to use American style dates in the document, mm/dd/yyyy. The Ruby 1.8 datetime seems to get these right.

Ruby 1.9 dropped this and always interprets nn/nn/nnnn as dd/mm/yyyy, which broke LOTs of my spec examples.

Luckily the breakage was all in the specs, not in the rical code under test. So a bit of regex replacement magic with Textmate to reformat the test input data, and lots of spec examples now ran under 1.9 as well as 1.8.x.

Range#include? Difference

In the recurrence rule enumeration code I had a method:

def in_outer_cycle?(candidate)
  candidate && (outer_range.nil? || outer_range.include?(candidate))
end

The outer_range attribute is a Range of a starting and ending DateTime.

In Ruby 1.8.x Range#include? checks to see if the argument is >= the start of the range, and either <= or < the end of the range if the range is inclusive or exclusive.

In Ruby 1.9, was getting an error like “can’t iterate from DateTime.” Range#include? now actually works more like Array#include? and steps through the elements of the range.

The fix in this case was to change the method to:

def in_outer_cycle?(candidate)
  candidate && (outer_range.nil? || (outer_range.first <= candidate && outer_range.last >= candidate))
end

Hash enumeration order difference

In Ruby 1.9 hashes keep track of the order of key insertion, and when a hash is enumerated it yields the keys or values or key value pairs in that order.

In Ruby 1.8 the enumeration order is accidental and depends on the key hash values and whether or not there are key collisions.

My last problem was a spec which read:

    it "should properly format dtstart with a date-time with a local time zone" do
      @it.dtstart = date_time_with_tzinfo_zone(DateTime.parse("4/22/2009 17:55"), "America/New_York")
      @it.export.should match(/^DTSTART;TZID=America\/New_York;X-RICAL-TZSOURCE=TZINFO;VALUE=DATE-TIME:20090422T175500$/)

One thing that I’d already change was the string argument to DateTime.parse. But that wasn’t enough.

The match expectation was failing under 1.9 because the substrings, “;TZID=America/NewYork”, “;X-RICAL-TZSOURCE=TZINFO”, and “;VALUE=DATE-TIME” were appearing in a different order under Ruby 1.9 than 1.8.x.

This was coming from a method:

     # Return a string representing the receiver in RFC 2445 format
    def to_s
      if visible_params && !visible_params.empty?
        "#{visible_params.map {|key, val| ";#{key}=#{val}"}}:#{value}"
      else
         ":#{value}"
       end
    end

The order of those sub-pieces depends on the enumeration order of visible_params which is a hash.

I left this last problem overnight, to sleep on.

This example is over-specified. The order of those three substrings doesn’t really matter. Now I could write a somewhat more complicated spec example, but pragmatically I decided to leave it over-specified but specify an order which I could guarantee under all ruby versions. So the to_s method became:

     # Return a string representing the receiver in RFC 2445 format
    def to_s
        # We only sort for testability reasons
       if (vp = visible_params) && !vp.empty?
         "#{vp.keys.sort.map {|key| ";#{key}=#{vp[key]}"}.join}:#{value}"
      else
         ":#{value}"
       end
    end

And the example changed slightly to:

    it "should properly format dtstart with a local time zone" do
      @it.dtstart = date_time_with_tzinfo_zone(DateTime.parse("April 22, 2009 17:55"), "America/New_York")
      @it.export.should match(/^DTSTART;TZID=America\/New_York;VALUE=DATE-TIME;X-RICAL-TZSOURCE=TZINFO:20090422T175500$/)
   end

Note the change to the Ruby 1.9 friendly “April 22, 2009…” and the re-arrangement of the substrings in the regular expression.

So after I got the multiruby setup straight, it really only took an hour or two to make rical 1.9 compatible.

Read: Ruby 1.9 Compatibility for Ri_Cal, What It Took, and Some Side Thoughts

Topic: Hey Twitter: Teach me German Previous Topic   Next Topic Topic: I got accepted into Agile 2009

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use