Boldly going forward — to Ruby 1.9

ruby
Published

May 2, 2012

From the very beginning of the project, we’ve developed Wallaby and its stack in Ruby 1.8 and not paid much attention to Ruby 1.9. We had done so for a few reasons, but primary among these is that we had internal Ruby 1.8 experience and that we needed to support Wallaby on 1.8.6 for the foreseeable future (and thus wouldn’t be writing code depending on 1.9 language or library features). All of that changed, of course, once we began packaging Wallaby for Fedora. Because Fedora 17 defaults to Ruby 1.9.3, we needed to port the code to work with both Ruby 1.9.3 and Ruby 1.8.6. Most major Ruby projects work with 1.9 these days, but I suspect a lot of code that is intended to be deployed on Ruby 1.8 is in the same boat we were. In this post, we’ll cover some of the pitfalls we ran into while boldly bringing our code base into 2009; we’ve kept an eye towards solutions that will work in 1.8 and 1.9, since we will be continuing to support Wallaby on Ruby 1.8.6.

Standard library interface changes

In Ruby 1.8, Module#instance_methods returned an Array of Strings; in Ruby 1.9, it returns an Array of Symbols. The return type of Module#constants has also changed similarly. Module#method_defined? and Module#const_defined? still accept String or Symbol parameters, though; when checking for the existence of a method or constant, prefer these. If you need to iterate through or grep for a substring in an Array of method or constant names, map each value to the result of calling to_s on itself first.

The sort of String returned by the to_s method on collection classes has changed in Ruby 1.9; it is now similar to the return value of the inspect method. So, in Ruby 1.8:

ls = %w{foo bar blah}
# => ["foo", "bar", "blah"]
ls << %w{fred barney}
# => ["foo", "bar", "blah", ["fred", "barney"]]
ls.to_s
# => "foobarblahfredbarney"
ls.inspect
# => "["foo", "bar", "blah", ["fred", "barney"]]"

Whereas in Ruby 1.9, the result is this:

ls = %w{foo bar blah}
# => ["foo", "bar", "blah"] 
ls << %w{fred barney}
# => ["foo", "bar", "blah", ["fred", "barney"]] 
ls.to_s
# => "[\"foo\", \"bar\", \"blah\", [\"fred\", \"barney\"]]" 
ls.inspect
# => "[\"foo\", \"bar\", \"blah\", [\"fred\", \"barney\"]]"

If you have code that depends on the old to_s behavior (we did, but I intend to conceal the identity of the developer responsible in order to protect the guilty), you can approximate it in several backwards-compatible ways: if you’re only worried about Arrays that contain String elements, the easiest thing to do is just to call ls.join("").

Semantic changes in the Ruby language

The big change that affected us — and will probably affect you, too, if you do a lot of metaprogramming or module trickery — involves block scoping. In some cases in our code, define_method blocks that referred to free variables exhibited different behavior on Ruby 1.8 and Ruby 1.9. The solution in these cases is ugly but straightforward: use a Proc object as you would use a let in Scheme, like this:

free_var = 

# ...

Proc.new do |fv|
  define_method "my_method" do
    fv
  end
end.call(free_var)

Text-encoding issues

A lot of people have written about Ruby 1.9’s support for multiple text encodings. If you’re using native extension libraries that create strings and haven’t been explicitly vetted for 1.9 compatibility, you’ll want to make sure that the Ruby String objects are created with the appropriate encoding metadata. In our case, the QMF library was returning UTF-8 strings that had encoding type BINARY in Ruby (i.e. raw bytes). Consider two String objects with identical sequences of bytes; one is tagged BINARY and the other is tagged UTF-8: these will be indistinguishable if you print them to a terminal or write them to a file, but Ruby’s comparison operators will not find them identical. We submitted a patch to QMF to ensure that strings returned from QMF are either tagged as the default external encoding or as UTF-8 (if no external encoding is specified).

In conclusion

This isn’t an exhaustive list of all of the changes between Ruby 1.8 and Ruby 1.9, of course (see here for that), but it is a set of problems that folks developing networked services and infrastructure for these might need to worry about. The standard-library interface changes were pretty minor; the other issues were listed roughly in order of increasing frustration. The important news, of course, is that Wallaby and its dependencies now work with Ruby 1.9.3 and are thus readily available to Fedora 17 users. Go forth, install, and configure!​