I have upgraded several Rails 1.2.x programs to 2.x. This can be quite a leap, and some of the steps are counterintuitive, so this post puts everything together, like a recipe.
The process works for any platform upgrade; Rails is just an example here.
This is the working cycle:
- edit environment.rb, and tweak the RAILS_GEM_VERSION to the version you need, such as '2.2.2'
- rake rails:update
- run all the unit tests
(if you don't have unit tests, don't start the upgrade until you spend a very long time adding them! - one for each branch in all of your logic. "functional" and "integration" tests are also "unit" tests here - the name is just a hint they should be higher level...)
- if any test fails, or if the code emits a warning...
- fix the problem
- revert your config & scripts folders, so your version is now 1.2.x again
(svn revert -R config scripts)
- pass all your tests (and observe your problem remains fixed!)
- integrate your upgrade into the lower version of Rails. Your colleagues can now outegrate your changes, so you don't need to "fork" your application just to upgrade it. (And note that if you do fork, this cycle makes frequent merges safe, too.)
- Switch environment.rb back to the higher version number, run "rake rails:update", run all your tests, and repeat.
You will eventually pass all tests in the higher version, and that's when you stop reverting & start integrating to that version. You will also manually test, and you will be very careful for your next deployment! This "Final Integration" is not the end - it is only the beginning of the cleanup and refactoring! Rails 2 comes with new features that you ought to exploit to make your code tighter and leaner.
You never integrate broken code. (Almost never - see below!;) The code on your bench is invisible to your version controller until you "gate" it in, and the only reasonable gate is passing unit tests. So to keep mistakes and experiments invisible, you revert them, and you revert the Rails version with them. Then the version controller thinks you are awesome, because it only sees good code, and it never learns the awful truth about you! (Or about the Rails maintainers ;)
Another technique: Instead of leaping all the way from 1.2.2 to 2.2.2, you should instead step one version at a time; to 1.2.4, then 1.2.6, then 2.1.0, etc. Maybe I should have listed that step first, but note that each step has the potential to throw errors, forcing you to repeat the fix-revert-integrate dance for each version pair.
This cycle has two minor gaps. Some errors are too pathetically simple to bother fixing in the lower version. And some libraries inevitably change during any platform's leap to Version 2.
You fix the first problem using a Big Dark Ugly Secret of Test-Driven Development: Sometimes, in a pinch, when you are sure you can get away with it, you comment out a broken test. Put a "TODO" on it, and after the Final Integration you search out these tests and restore them with their features.
When libraries change out from under you, you can regrettably remove and re-add their features. For example, Rails 1 came with a built in Paginator module, and Rails 2 wisely booted it out into a "plugin".
However, the Rails maintainers also saw fit to "upgrade" that plugin and make it incompatible! While I can imagine taking the pagination feature temporarily out of our apps, and putting it back in heroically, I chose a different option. I copied the old Paginator out of Rails 1's home folders, stuck it in my application's lib/ folder, and wired it up into application.rb.
Your mileage may vary!
This post now needs to hear more stories about upgrading platforms; such stories may indeed emend my suggested hacks and tweaks. Yet the point of unit tests, and TDD, is to make the smallest changes possible, and relentlessly test each change. Upgrading a major version tick is a big change, so you must force the upgrade to work incrementally, as a series of small changes.