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 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 .