Upgrading an Application to Rails 3 — Part 1
Almost a week ago I posted What I Will Do for Rails 3 in which I promised to “upgrade a Rails 2.3.5 application, publish an article on the process, and report any bugs I stumble upon”. This guide will walk you through the steps I took to upgrade a Rails 2.3.5 application of mine, called “The School Portal” (or “Skoleportalen” in Danish).
Re-generating the Application
The very first thing I did was to clone the Rails Git repository and use the rails executable to re-generate the application to get all files up-to-date with Rails 3.
git clone git://github.com/rails/rails.git ruby rails/railties/bin/rails skoleportalen
The generator is smart enough to ask you for permission before overwriting the contents of a file. It will even let you see a diff of the current contents of the file and what Rails would like to insert. It’s important that you don’t let Rails delete code written by you. For example Rails wants to overwrite config/routes.rb but that would mean deleting your routes. So you should merge the two versions together to contain both your code and Rails’ updates to the file.
There is one exception, though: When Rails wants to delete the config.gem lines of config/environment.rb (and the configuration file for each environment) you can let it do so, given you have your application under version control (Of course you have, right?) so you can fetch the dependencies later and add them to the Gemfile.
When I was done I reviewed the changes, deleted public/index.html, and committed the current state of the process.
git diff rm public/index.html git add . git commit -m "Rails 3 upgrade: Re-generated application"
You should consider doing the upgrade in a separate branch. In my case it was not necessary since my application was made as part of an assignment at school and therefore never went into production.
Booting the Application
The next step I took was to add the Rails Git repository that I just cloned to the Gemfile and bundle it.
gem 'rails', '3.0.pre', :git => '../rails/'
In my case, my clone of Rails and the application were both in my ~/code/ directory, but if you have arranged your directories differently you should update the reference to the Rails repository to reflect that.
gem bundle
Then it was time to try starting the server.
david@david-laptop:~/code/skoleportalen$ script/server => Booting Mongrel => Rails 3.0.pre application starting in development on http://0.0.0.0:3000 => Call with -d to detach => Ctrl-C to shutdown server Exiting /usr/lib/ruby/gems/1.8/gems/activesupport-3.0.pre/lib/active_support/dependencies.rb:456:in `load_missing_constant': uninitialized constant ActiveSupport::CoreExtensions (NameError) ... from /home/david/code/skoleportalen/config/initializers/date_formats.rb:1 ...
In Active Support 3.0.pre all core extensions has been moved out of their respective modules and directly into the classes they extend. My config/initializers/date_formats.rb file looked like this:
ActiveSupport::CoreExtensions::Time::Conversions::DATE_FORMATS.merge!( :danish => "d. %d/%m-%Y" )
And what I changed it to, in order to accomplish the same thing, was this:
Time::DATE_FORMATS.merge!( :danish => "d. %d/%m-%Y" )
Then I tried starting the server again.
/home/david/code/skoleportalen/config/initializers/new_rails_defaults.rb:14: undefined method `generate_best_match=' for ActionDispatch::Routing:Module (NoMethodError)
The file that causes this error was intended to change the behavior of Rails 2 applications to what would be default in Rails 3, so I removed that.
git rm config/initializers/new_rails_defaults.rb
Now the server started successfully for the first time, so I committed the state of the upgrade once again.
git add . git commit -m "Rails 3 upgrade: Bundled Rails and made the application boot-able"
A Bundler Bug?
Then I navigated to http://localhost:3000/ and concluded it was time to add the application’s dependencies to the Gemfile.
gem 'formtastic', '~> 0.9' gem 'wysihat-engine', '~> 0.1' gem 'friendly_id', '~> 2.2' gem 'authlogic', '~> 2.1' gem 'cancan', '~> 0.2' gem 'paperclip', '~> 2.3' gem 'will_paginate', '~> 2.3'
With a bit of search and replace-fu it was a piece of cake. I ran gem bundle again, restarted the server, and refreshed. But weirdly I got the same error again telling me that even though I had bundled the application’s dependencies they didn’t seem to get loaded. I went digging: The config/boot.rb file requires vendor/gems/environment.rb which, in my case, requires vendor/gems/ruby/1.8/environment.rb.
Inside that a Bundler.require_env method is defined which creates an anonymous class with a gem method that requires a specified gem. It then evaluates the Gemfile within the context of that class. Just for fun I added a call to it just below the definition and restarted the server.
One of the gems raised an exception which means, on the bright side, it got required. Of course, as a good citizen of the open source community, I reported the bug. I did not provide a patch because in cases like this the decision of how the bug should be fixed is really up to the project owners. Adding a call to require_env under the definition might not be ideal. Also, I didn’t know if it was Bundler’s or actually Rails’ responsibility to call the method.
Conclusion
When the dependencies of my application have all been fixed to work with Rails 3 I will post part 2 of this guide. The following is a list of issues I posted to the respective gems’ issue trackers:
- Formtastic: Should require ‘action_view’
- FriendlyId: Should require ‘active_record’
- Authlogic: Should require ‘action_controller’
UPDATE As pointed out by José Valim in the comments: These issues can simply be solved by calling Bundler.require_env later on in the process, making sure the components of Rails have been loaded. At the end of config/environment.rb would be a good place.