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

Balancebridge::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 application.rb 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"

Monkey Patching

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_without_validation to save(:validate => false). We could write an alias method for save.

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

Conclusion

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.

Get the book