Upgrading to Ruby 1.9

September 11, 2012 at 16:50:08

Having established a way of switching between Ruby versions, I began to concern myself with trying to get RubyFrontier to work under Ruby 1.9.3. I wasn’t expecting it to work right out of the box, so I tried to prepare myself by guessing what sorts of problem might arise. A lot of interesting Ruby 1.9 features are of course not backward-compatible with Ruby 1.8, but my problem was the converse: what aspects of Ruby 1.9 are likely to break what I’m already doing?

Although I read quite a few articles in preparation for testing RubyFrontier under 1.9.3 to see what it would do, none of those articles really prepared me for what actually happened. These are the sorts of questions I was asking myself beforehand:

  • Are string encodings likely to be an issue? I don’t think so. If necessary I can mark my files as UTF-8 and that should be that. I use commands like downcase, but those will work just as they did before, namely, they affect only ASCII characters. And I’ve always worked in TextMate, so I’ve always pretended my strings represented UTF-8.

  • Do I ever rely on the assumption that a String “element” is a codepoint? (Example: "hello"[0] is 104.) I doubt it. And I certainly never use the ?h notation.

  • Do I ever treat a String as an Enumerable? The key case would probably be an assumption that each means each line. That doesn’t sound like something I’d do, but I’ll have to keep an eye out.

  • Do I ever expect a block variable to be the same object as a variable outside the block? It doesn’t seem probable. The fact that this could happen was never anything but surprising and troublesome. The interior of a block is a local scope, and one naturally expects a block variable to be local to that scope. If I want to affect a variable outside a block, I can assign to that variable from inside the block; I am unlikely to use the implicit iterative changing of the block variable as a way to do this.

So I went into the process feeling rather confident. However, that confidence was immediately dashed when RubyFrontier didn’t work at all initially under Ruby 1.9.3. So I started digging. In reality, the sorts of issue I encountered were these:

  • There was in fact one method where I was calling String#to_a, which no longer exists. This was not in order to split a string into lines, but merely a foolish way of guaranteeing that what I had was an array, regardless of whether I received a string or an array. This was easy to fix: use Array() instead. That’s what it’s for.

  • Ruby 1.8 allows a shorthand form of when...case involving a colon after the match for a case. Ruby 1.9 doesn’t permit this; my code wouldn’t even compile. Solution: substitute then for the colon. That’s compatible with both versions.

  • Some code I’d copied off the Internet for getting the dimensions of a JPEG file broke. To solve this, rather than figuring out and tweaking the code, I changed my dependency, requiring the Dimensions gem.

  • My code assumes that the current folder is in the require path ($:). This turns out to be no longer the case; it was deemed a security risk. There’s a new require_relative, but of course that’s not backward-compatible to Ruby 1.8.7. I wound up using a simple explicit expression:

      require Pathname.new(__FILE__).dirname + "myfile"
    
  • My code depends heavily on the Pathname library. Pathname used to convert itself implicitly to String as needed, but it no longer does so (its to_str has been withdrawn); I have to call to_s explicitly in all the places where this was happening, and there are many such places, as I was unconsciously depending on this behavior.

  • The behavior of URI::Generic#route_from has changed. That is, given the same inputs, it now sometimes produces a different output than it did. My code has to detect the inputs that can trigger this difference and compensate. This is a particularly tricky change to work around, and is the only one that really worries me.

Surprisingly, I had RubyFrontier running under Ruby 1.9.3 in just a couple of days. By “running” I mean:

  1. All tests pass. You could argue that the tests are a little weak and don’t hit certain edge cases or go very deep into the page/site-building mechanism, and that’s true enough. But it’s something. And…

  2. The documentation site (included in RubyFrontier) builds identically under Ruby 1.8.7 and Ruby 1.9.3. I regard that as a highly encouraging sign.

Another surprise is that the exercise of responding to issues that cropped up caused me to clean up my code considerably. I removed a bunch of unnecessary regex matches — unnecessary in the sense that they could be more easily and efficiently expressed as simple string tests. And, more important, I had to grapple with some trouble in my stdout diversion code, and by fixing this to work under Ruby 1.9.3, I ended up with code that was better in every way. The idea here is that when RubyFrontier raises an exception or sends a message to the user (raise, puts), this information should flow through a nice informative display to the user in TextMate’s RubyMate-style console window. To accomplish this, I wrap all RubyFrontier functionality in a stdout diversion world, where my own duck-typed class plays the role of stdout and does things like passing output through TextMate’s htmlize, reformats call stack information, etc. This now works better than ever, because Ruby 1.9.3 broke it and made me think harder about what I was doing.

Thus, I feel that RubyFrontier has passed through the fire of Ruby 1.9.3 and has been tempered to be better than ever. I’m writing and “publishing” these words with RubyFrontier under Ruby 1.9.3, which shows, I think, a certain level of confidence. Of course I’ll keep playing under Ruby 1.9.3 to see if I can nose out any further edge cases I may have missed over the past two days. But so far, so good!

Home

This page prepared February 14, 2014 by Matt Neuburg, phd = matt at tidbits dot com, using RubyFrontier. RubyFrontier is a port, written in the Ruby language, of the Web-site-creation features of UserLand Frontier. Works just like Frontier, but written in Ruby!
Download RubyFrontier from GitHub.