Solving Dual Booting Issues when Changes aren't Backwards Compatible
One of the steps we recommend taking when doing an upgrade for any Rails version is to dual boot the application with your current Rails version and your next rails version.
This is important because it allows you to quickly run the test suite for both versions, having dual booting available allows you to debug and also revert to your current version in a much simpler fashion.
However, sometimes changes that you make for the new version of Rails may not be compatible with your current version of Rails. This means that you will need to use a few different techniques to get both versions to be able to use the dual booting and run smoothly.
Conditional Statements for Dual Boots
Sometimes we find a change that breaks the tests or the app when we run it in the current version. In this case we can add a conditional that allows us to check which version of Rails is running, and customize which code we want to run for that version. An example of this is the syntax for Routes between Rails version 2.3 and 3.0.
In Rails 2.3
routes.rb would something like this:
ActionController::Routing::Routes.draw do |map| map.resources :users map.login "/login", :controller => "sessions", :action => "new" end
and in Rails 3.0 it would look something like this:
SampleProject::Application.routes.draw do resources :users match "/login" => "sessions#new", :as => :login end
If we simply change the syntax to the Rails 3.0 syntax the Rails 2.3 version will complain. Therefore we need to do an if/else and use both syntaxes for dual booting. We can check which version of Rails we are running for the conditional:
if Rails::VERSION::MAJOR > 2 SampleProject::Application.routes.draw do resources :users match "/login" => "sessions#new", :as => :login end else ActionController::Routing::Routes.draw do |map| map.resources :users map.login "/login", :controller => "sessions", :action => "new" end end
Note that you can simply use Rails::VERSION:MINOR if you need to check a version like 3.0 against 3.1 for example.
Conditionals Before Rails Loads
When starting a Rails app, depending on the Rails version, the files will be loaded in different orders. For example, in Rails 3
config/boot.rb is the first file to run. Until we arrive at the step in
require "rails/all" that actually loads Rails we will not be able to access the Rails::VERSION, so we will need to instead use the ENV variables for our conditionals in some of the config files.
This should work if you are dual booting with the steps from our article.
ENV["BUNDLE_GEMFILE"] && File.basename(ENV["BUNDLE_GEMFILE"]) == "Gemfile.next"
Sometimes the best option for non-backwards compatible changes is to use a monkey patch. Though monkey patching is generally frowned upon, it can be a useful tool when dual booting. Sometimes there are many places where you would need to add a conditional statement in the code. Instead of going through possibly hundreds or thousands of syntax changes in the code with if/else statements we can use a monkey patch.
For example if we need to change the syntax of something like
save(:validate => false). We could write an alias method for
module ActiveRecord class Base alias_method :save_not_accepting_hash, :save def save(validate = true) if validate.is_a?(Hash) save_not_accepting_hash(validate[:validate]) else save_not_accepting_hash(validate) end end end end
These are some of the ways to help smoothly dual boot your code when backwards compatible changes are not possible. We would love to hear if you have other ideas. Don’t forget to check out our other articles on upgrading Rails.