UPDATE 1 (6/29/2010): Today I purchased a copy of “Rails 3 Upgrade Handbook” by Jeremey McAnally, who wrote the rails_upgrade plugin. I would highly recommend reading this book as your starting point for Rails 3 upgrade, as I wish I read this before I started my upgrade. He goes into alot of detail about what you need to change. The steps differ some from what I wrote in this post, and you might find it easier to follow his steps.
This is going to be quite a long post and I will probably be updating it daily for the next few weeks. Now that Rails 3 RC is right around the corner, I figured it would be a good time to upgrade CouponShack.com. I have been meaning to refactor my code for quite a while and clean up the mess I have created in the last 3 years. I am finally getting around to it and I thought upgrading to Rails 3 would be a good start, so here is my experience upgrading to Rails 3, from Rails 2.3.2. I am writing some of this after the fact, so hopefully I didn’t leave out any steps:
Current stable setup on CouponShack.com
- Co-location hosting in my city
- CentOS 5
- Mongrel 1.1.5
- Rails 2.3.2
- Ruby 1.8.7
- Rackspace Cloud Servers hosting
- Rails 3 beta4 (upgrade to RC when ready)
- Ruby 1.9.2dev
- New app server… looking at Passenger right now
The upgrade list may change depending on how everything pans out. I was planning on moving over to Rackspace Cloud (where most of my other apps are currently), so I figured I would go with Rails 3 as a part of that move.
Note: I use Mac OSX 10.5.8 and Textmate for development
Another Note: A good place to start is to read the Rails 3 Release Notes. This document explains the new features, upgrade path, and has alot of good links to blog posts by the rails developers that relate to the new features. My blog post reiterates alot of this info while showing you how it relates to my specific upgrade. I recommend reading as much as you can about Rails 3 before you get your feet wet with the upgrade… I know that helped me.
Step 1 – Install RVM
RVM will allow you to run multiple versions of Ruby on your development box. This is a must if you are upgrading to Rails 3 and still want to use Rails 2. Follow the instructions on the RVM website and install it from github.
Step 2 – Install Ruby 1.9.2
After RVM is installed, you will want to install Ruby 1.9.2 using RVM.
rvm install 1.9.2
It will take a few minutes. On my system it installed ruby-1.9.2-preview3. After its done you can switch to 1.9.2 by using this command.
Now your Mac is using 1.9.2 instead of the system ruby (you can switch back with “rvm system” if you need to code some rails 2 stuff again)
Step 3 – Install Rails 3
Using Ruby 1.9.2 on your system, you can now install Rails 3. One thing to note here is that you now have a clean slate for all your gems. RVM conveniently stores your gems in a directory under your account ~/.rvm/gems/ruby-1.9.2-preview3/gems. This will allow you to keep your Rails 2 gems separate, but that means that you have to reinstall any gems that you might be using in your Rails 2.3 app. You will probably get plenty of errors when you finally start up your app as a result of the missing gems… I will get into that later. So here is all you need to do to install the Rails 3 beta 4.
sudo gem install rails --pre
You should be set now with Rails 3. You can do a version check just to be sure with “rails -v”.
Step 4 – Generate a blank Rails 3 application
NOTE: If you don’t have your app in a source control repository, it may be a good idea to also make a full backup of your app directory before you start changing things.
I found it useful to have a blank Rails 3 app to use as a reference when upgrading my 2.3 app. You will most likely need to drop some of the files in your 2.3 app or at least copy/paste some of the code. Create the new app with the rails command in your terminal.
rails new blank_app
Step 5 – Get the rails_upgrade plugin and run it
This is an officially supported plugin from Rails. Get it here: http://github.com/rails/rails_upgrade Jeremy McAnally has instructions on how to install rails_upgrade as a plugin. I remember seeing this was opted as a plugin instead of a gem because it was easy to install and delete from your application.
I ran the check rake task first after I installed this plugin. It gave me quite a large list of things I need to change. Most of the changes involved the new ActiveRecord Query API, which is pretty awesome btw. I just ran down the list one by one and changed what it was complaining about. I re-ran it after every section to make sure the changes were correct. There are a few other Rake tasks to add the new Gemfile, add the application.rb and fix your routes, but I opted to do that manually in order to learn more about the changes.
I am guessing you may spend alot of time updating queries and scopes, so check out these blogs from some of the Rails gurus for more info on what has changed in Rails 3 and when the old stuff will be deprecated:
Step 6 – Remove your old scripts and add the new rails script
Rails 3 has replaced all the scripts in /app_root/script with a single script called rails. I deleted all of the files in the script directory and copied the file from my blank application in step 4 and put the file in my script directory. I think it makes much more sense for rails to have one script file. This file can start the server, generate scaffolding, start your console etc.. To see a full list of commands use “rails –help” inside your application somwhere.
Step 7 – Upgrade your environment files (rails_upgrade may take care of this)
The individual environment files in config/environments/ are also a little different in Rails 3. I am not sure if rails_upgrade takes care of this, but I did not use that rake task, so I just updated the files manually. You will need to add a block around your configuration in each of these files. Mine looks something like this for development.rb:
Couponshack::Application.configure do config.cache_classes = false #..... end
Also, make sure you update your environment.rb file if rails_upgrade didn’t do it for you. Refer to the blank application in step 4 for the code… its only a couple of lines now.
Step 8 – Add application.rb (rails_upgrade will do this for you)
If you want to do this manually to see whats new, check out the application.rb file in your blank app. There are examples in the comments. For those that don’t know, application.rb is sort of a replacement for environment.rb. This file is for application specific configuration and it makes sense that they would remove this from environment.rb, since application and environment configuration are really 2 completely separate things.. app configuration is not specific to an environment.
Step 9 – New routes (rails_upgrade will do this for you)
Rails 3 has new syntax and features for the routes file. Look at config/routes.rb in the blank app you created in step 4. The comments provide you with an explanation of the new route syntax. Some of the highlights include shortened syntax for controller/action. Instead of :controller=>”mycontroller”, :action=>”myaction”, you would write – :to=>”mycontroller#myaction”. And new syntax for mapping.. instead of “map.connect” or “map.name_of_route”, you just use “match” (and :as=>”name_of_route”). I would recommend doing this reading all the comments in the new file and doing it manually so you can learn the new syntax, unless of course you have hundreds of routes.
Step 10 – Take care of them Gems (rails_upgrade can do this too)
Along with application specific configuration, Gem config was also removed from environment.rb in favor of the Gemfile which is in the application root directory. Again, take a look at a blank Rails 3 app to see what this file should look like. The gemfile makes gem dependency management easy by using the new bundler. Bundler checks your system for installed gems based on the Gemfile and can install them for you automatically. If you try to start up your app and you are missing a gem dependency, you will see something like this:
Could not find gem 'mechanize (>= 0, runtime)' in the gems available on this machine. Try running `bundle install`.
The Gemfile and Bundler are great additions to Rails 3. For more info check out the bundler home page for more info on what it can do.
I know many of you will run into problems with existing Gems that are not quite Rails3 ready. Hopefully most of the popular gems and plugins will be finished with the upgrade by the time the official release comes out… probably wishful thinking. A good resource for troubleshooting problems with Gem and plugin compatibility with Rails 3 is this website: RailsPlugins.org – Lots of comments have been posted on this site and more than likely someone has already experienced the upgrade problem that you may be having. It was helpful in troubleshooting a few of the plugin bugs I found.
Step 11 – Beware: “h” helper in views is the default now
This is turned on by default now. So you don’t have to do crap like this anymore: <%= h @blog.title %>. The “h” is not necessary, so HTML will be escaped by default. If you have any content that includes HTML coming from your database, you will need to wrap that with the “raw” method like so: <%= raw(@blog.html_enabled_content) %>
Step 12 – config.ru
Rack-based servers use this file to start the app, so you will probably want to add this if you have not already. Again, copy the file from the blank app and change the application name.
Step 13 – Start up your app locally and pray
Mine did not work at all. I use mongrel on my dev box and I had trouble running it manually (mongrel_rails start). Then I used the rails server command and it seemed to work fine. You can start it up by using the new rails script file. All you have to do is type:
This will start up the server locally and you are ready to take a look at your app.
As far as a I remember, these are the steps necessary for my app to start running on my Mac. If I left out anything, please comment on this post. The rest of this post will go into detail on some of the problems I am having and how I solved them, as well as the upgrade path on my production server. At the time of this writing I am planning on using Nginx in front of Passenger for my production environment.
Problems that I ran into (mainly plugins)
Problem 1 – recaptcha plugin
I was using this in vendor/plugins. It when I tried to access pages that used this helper, it was complaining about “builder” not being available or something to that effect. I monkeypatched the code in /plugins/vendor/recaptcha/lib/recaptcha.rb – added the following line to the top:
recaptcha_tags worked in the views after I added that line.
Problem 2 – will_paginate errors
I was getting this annoying error:
undefined method `paginate' for #<Class:0x16c739c>
activerecord (3.0.0.beta4) lib/active_record/base.rb:1041:in `method_missing'
I included will_paginate in my Gemfile with this line:
gem "will_paginate", :git => "git://github.com/mislav/will_paginate.git", :branch => "rails3"
That did not fix it. Same error. I finally realized that I screwed up application.rb when I was upgrading. I was missing the bundler require call, so the initializers in the railtie were never executed. Make sure you have this in your application.rb, or your dependencies will probably crap out.
# If you have a Gemfile, require the gems listed there, including any gems # you've limited to :test, :development, or :production. Bundler.require(:default, Rails.env) if defined?(Bundler)
Problem 3 – equals sign with blocks
When running rails_upgrade, it warned me of several blocks that needed an equals sign. This is new to Rails 3 and it makes sense now that I understand where to use it. Basically if it spits out a tag or any output to the view, then you should use equals, otherwise if its an .each iterator or something like that that does not produce output don’t use it. Some of these were false positives like the following:
<% @blogs.each do |blog| %>
Check out Ryan Bates screencast which shows when to use and not use the equals. I am not sure if this has been fixed in the rails_upgrade plugin, but its something worth noting. It was spitting out the printed representation of the objects when I used an equals with it.. not pretty
Problem 4 – my routes.rb is too bloated
rails_upgrade does a very good job of translating your routes.rb into the new syntax for the most part. This is not a huge deal, but the only part I wanted to change was the way the collection section is written. My routes.rb after the upgrade was hundreds of lines long. My legacy code (which needs to be big-time refactored) has lots of collections for my resources. This was a result of Rails 1 code being merged with Rails 2 code.. I used the collection hash in my resources to add any controller/action that was not RESTful.. and I had lots from my original Rails 1 app.. mostly custom views which could have been consolidated in the show or index action. But anyways, all these collection members produce something that looks like this:
resources :blogs do collection do get :action1 get :action2 get :action3 #...... etc......... end end
After experimenting I found out that Rails 3 also accepts this form:
resources :blogs do collection do get :action1, :action2, :action3 #etc... end end
Just personal preference, but I find this form much easier to manage because of the large number of collection actions that I have.. refactoring them is a whole different story.
Problem 5 – colon in case statement (caused by new Ruby version)
I use the little known, “date_time_text_field_helpers” plugin. Long name, but came in handy for handling some of my date/time fields. When I tried to start my app server with Rails 3 it gave me the following error:
/rails_apps/couponshack/vendor/plugins/date_time_text_field_helpers/lib/date_time_text_field_helpers/instance_tag.rb:110: syntax error, unexpected ':', expecting keyword_then or ',' or ';' or '\n' (SyntaxError) when :hour, :minute, :second : 2
So I looked at instance_tag.rb at line 110 and I see a syntax that I did not recognize in a case statement. I rarely use case statements and I don’t know much about them, but apparently Ruby 1.8x allowed the use of the colon character. This has been deprecated in Ruby 1.9. I changed the colons to “then”. Another problem is the html escaping. I had to update a call to content_tag and pass escape=false. You can check out the changes in my github fork:
Works just fine now.
more to come…