Yesterday I started my first upgrade of a rails application from 2.3 to 3.0. Technically, it is not even a rails application, as we mostly just use the ActiveRecord layer. Nevertheless it was a huge learning experience so far. In this post I want to concenrate on the obstacles I had to overcome and the lessons I learned from them.
The fun began, when I updated our gems and ran our specs for the first time. Well, actually I couldn’t because the RSpec rake tasks where missing. Google to the rescue, it turned out we had to add our test gems, like rspec-rails, to the bundler development group or rake wouldn’t load the corresponding targets. I had known about bundler groups, but never bothered to figure out, what they actually where intended for or how they worked.
With RSpec targets visible, I could finally run the test suite. Unfortunately it didn’t get very far before the first error struck. Some of the garb gem’s symbols where unresolvable. It turned out garb had gone through some major restructurings deprecating the way we use it. Not sure about those, I downgraded it to the old version we where using before, but the error prevailed. After some googling I finally found the reason, garb contains the file lib/support.rb which defines the missing symbols. Another gem, right_http_connection, also contains a file lib/support.rb. As it turned out, right’s file was loaded while garb’s wasn’t. The proposed solution was to downgrade to a previous version of right_http_connection that didn’t contain that file. Alternatively, it was proposed to load garb before right_http_connection. I played with my Gemfile, changing the order of entries and not requiring some gems, but I couldn’t get it working. So, with both gems downgraded, the test suite actually ran.
The lesson I learned in this case was, that a gem can be thought of as a namespace. There is absolutely no reason to leave this namespace and create a potential for collisions by providing files like lib/support.rb. Put your files into a subdirectory and be happy. If however, you feel there’s no way around providing a generic file like this, at least use a require statement with absolute pathname to not accidentaly refer to another, already loaded file.
The issue wasn’t over though. The test suite was running, but it showed strange ArgumentError: wrong number of arguments (1 for 0) on some of my own methods. A friend suggested a backtrace and so I learned about rspec -b. This showed the real problem deep in the garb gem. garb uses ActiveRecord’s camelize() method that seemingly was replaced by something different. This time the culprit turned out to be right_aws, which also defines a camelize method but with a different method signature. With the downgrade of right_http_connection, I had to downgrade right_aws as well. The older right_aws version however, was not rails 3 compatible and overwrote ActiveRecord’s camelize(). In this dilemma I decided to make right_http_connection a vendor gem and fix it locally so as to not collide with garb. I already had two other gems in vendor/gems as those needed trivial fixes as well.
At this point my gems where in a decent state, so I could run the test suite and fix a couple of minor issues, except for one. Again I got an argument error, but this time on one of my own methods called load(). On a mocked null object. How was that possible? Using Object#method it turned out my mock was somehow linked to ActiveSupport::Dependencies::Loadable which contains a load() method that was invoked. Explicitly stubbing load() on the mock object solved the issue, but I have yet to understand what is going on here.
The final problem was a deprecation notice from RSpec, that I was setting configuration values after defining examples. This was really strange, as all I was doing in spec_helper.rb was loading files from spec/support and setting configuration values afterwards. Just as it was supposed to work. I experimented a lot and finally came to the conclusion, that my support directory was fine and so I resorted to Google again. After a couple of pages I finally found the issue. RSpec doesn’t like it, when you mix require ‘spec_helper’ with require File.dirname(__FILE__) + ‘/../spec_helper’ as this leads to spec_helper.rb being loaded twice. Apparently this is the way require works. Once understood, I changed all require to be of the same form and had all my specs pass without further warnings.
The upgrade isn’t finished yet, I have to write more specs for some older code and actually test the running application. But for a first day the outcome wasn’t bad. I learned a lot and hope to have removed all major obstacles.