UPDATED: October 11th, 2010 Now with more working code!
August 29th, 2010 was a glorious day for Rails developers around the globe, not only because it is my birthday, but also because Rails 3.0 was officially released. I would like to send congratulations and thanks to all 1600 contributors that helped make this release a reality.
I’ve spent the better part of September upgrading our main application to Rails 3. My hopes are that you will read this and gain some insight as what to expect when upgrading your own application.
Baby Steps
Luckily for us, just as I was about to dive into this project, Rails 2.3.9 was released, and said to make the upgrade process easier. This seemed like a great first step. I upgraded to Rails 2.3.9, and fixed a few minor deprecations that were introduced in this version.
We use Paperclip extensively within our application, the latest version 2.3.3 works with both Rails 3 and Rails 2.3.x. I upgraded this gem, as it seemed and was a quick adjustment.
All tests pass at this point, time to move onto Rails 3.
Gem Research
I should mention before I actually started touching code; time was well spent researching our gem dependencies and plugins. I wanted to ensure we had full compatibility before this upgrade process occurred. A simple chart was created listing all our gem dependencies and plugins, if they work in Rails 3, and if not what actions were required to make it work.
To test the gems and plugins, I setup a blank Rails 3 application, and manually built up similar context to what our actual application has. Looking back this was fairly time consuming. Though it seems like the best approach when you don’t want to make a mess of your existing codebase.
Authlogic had quite a few deprecation warnings, but for the most part it worked, until I tried using the AuthenticatesMany module. This outright failed, so more research was needed. I ended up forking the repository and pulling in some fixes from other contributors. Still waiting for a pull request to be accepted. As of now, Authlogic is working in our Rails 3 application.
I forked NestedHasManyThrough, and made a bug fix. This resolves an issue where the plugin fails when using polymorphic associations. Waiting on a pull request here as well.
Overall, the initial gem research was about 90% effective.
Upgrading
My first task was simple, I created a new Rails 3 application on top of our existing application. Since our application is kept in version control, this seemed like an easy approach. Merging differences usually produces expected results, while using an external upgrade plugin may produce unexpected results. Overall the merging approach was quick and quite painless. This felt like a big win.
Next in queue was to move the gem dependencies out of the config/environment.rb file. Bundler is your friend. Simply move the gem dependencies into the Gemfile. I’ve put our .bundle/config file in version control to ensure all developers get the same bundler setup regardless of machine.
Following that, I upgraded the files in the config directory. Some of the changes merged in from installing Rails 3 on top of our application needed some minor syntax changes. The router was the biggest change in this directory, but the new DSL is easy to translate from the old router DSL. My hopes with the config directory is that by changing these files as needed, it would lead to the application being bootable in the shortest amount of time.
My approach with the router was to disable all routes, and convert one resource at a time, while testing the actual application. Obviously the first component that failed would get moved into the new router DSL and fixed accordingly.
Finally I upgraded all our models and controllers to use AREL. Thankfully, our controllers are very light, so almost no AREL changes were needed here. Our models are thick and juicy, and had my attention most of the time. I also moved our mailers out of the models directory and into their own mailers directory which is now the default.
The controllers needed virtually no updates, aside from moving filter_parameters into config/application.rb and some to_json adjustments (see below).
Pain Points (Rails3 Specific)
Due to the nature of our application, we render JSON responses on almost every request. This was also our first big gotcha. In Rails 2.3.x we called to_json directly in some controllers, and passed in a hash of options used to return a specific representation of the object. This method was defined in most of our models. Rails 3 now calls as_json on each object in order to get the JSON representation. In order to resolve this, I went through each controller and adjusted where we called as_json vs. to_json. For our application this meant as_json was called on individual objects, and to_json was called on collections of objects. In some cases, neither method is called directly because the default JSON representation is acceptable.
I should also mention, that the as_json method normally returns an object easily converted to JSON, our implementation currently returns an actual JSON object. Your application may not need any adjustment here.
One of our key features is the ability to generate a CSV progress report in real-time. These files are often quite large (20MB+ ) and a generated on the fly, as the client downloads them. In Rails 2.3.x we simply called “render :text =>” and provided it a Proc. This allowed us to write to the Proc, and in-turn, stream the content as it was being generated. Quite effective. In Rails 3 this no longer works due to how the :text responder converts the output to a string. I created a custom responder that behaves like the Rails 2.3.x functionality.
The code samples below are based on what we are using now, feel free to adopt.
I am still interested in hearing if there is a better solution, but for now this is working well.
Pain Points (General)
When this project was originally planned, I had estimated roughly a week to upgrade to Rails 3. However, as the project unfolded; unforeseen issues surfaced (gem in-compatibility, Rails 3 behavior changes, etc) it pushed that original estimate out of scope. The plus side is that my time-boxing skills improved drastically. What was originally a single task (“Upgrade to Rails 3”), quickly became broken down into smaller obtainable daily tasks.
I started this project on September 1st, and had the application deployed on our staging environment by September 23rd. It is difficult to measure the size of this application in a broad sense. So with that in mind, our application has almost no views outside of a login screen, and everything else is a JSON response.
In conclusion, I hope that this article gives you (fellow Rails 3 up-grader) insight into some of my pain points and wins while upgrading our application from Rails 2.3.8 to Rails 3.
Feel free to contact me @releod