Skip to content

Instantly share code, notes, and snippets.

Last active August 29, 2015 14:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save grosser/69c137b8d0e21e58cae8 to your computer and use it in GitHub Desktop.
Save grosser/69c137b8d0e21e58cae8 to your computer and use it in GitHub Desktop.
Updating rails versions while keeping the business running

Updating rails versions on big apps can be dangerous and time consuming. This is the workflow we use to deploy and test our Rails upgrades in isolation, before releasing them to everybody and without blocking other changes.


ln -s Gemfile Gemfile.rails4 and use BUNDLE_GEMFILE=Gemfile.rails4 bundle exec rails c to run rails 4.

if ENV['BUNDLE_GEMFILE'].to_s.include?('rails4')
  gem "rails", "4.0.13"
  ... other gems that are different ...

  # make bundler write to a different cache directory
  class << Bundler
    def app_cache(custom_path = nil)
      path = custom_path || root

  Bundler::Runtime.class_eval do
    def cache_path
  gem "rails", "3.2.21"
  ... other gems that are different ...

... unaffected gems ...

Ensuring locked gem versions are the same on both versions

Keeping 2 gemfiles means other developers might forget to add a new gem or lock an unintendedly different version.

We use this code for our pre-commit hook in config/pre-commit.rb and a "cleanliness" test to make sure only versions we allow are different.

# config/pre-commit.rb
module PreCommitChecks
  # compare versions and git refs between both lockfiles
  def self.check_lockfile_diff(files)
    return [] if !files.include?("Gemfile.lock") && !files.include?("Gemfile.rails4.lock")
    a, b = ["Gemfile.lock", "Gemfile.rails4.lock"].map do |file|
      file =
      file.scan(/^  remote: \S+\n  revision: \S+$/)+ file.scan(/^    \S+ \(\S+\)/)

    diff = ((a | b) - (a & b))! { |match| match[/\S+github\.com\S+\/(\S+?)(\.git)?\s/, 1] || match[/\S+/] }.uniq

    allowed = [

    (diff - allowed).sort.uniq

if $0 == "config/pre-commit.rb" # executed via pre-commit hook
  errors = []
  errors += PreCommitChecks.check_lockfile_diff(changed)
  if errors.any?
    puts errors
    exit 1

# test/unit/cleanliness_test.rb
require './config/pre-commit'

it 'does not have accidental diff to rails 4' do
  PreCommitChecks.check_lockfile_diff(["Gemfile.lock", "Gemfile.rails4.lock"]).must_equal []


If you have enough worker capacity this is good enough:

 - Gemfile
 - Gemfile.rails4

If worker capacity is an issue, then make a branch with the changed gemfile and rebase that once in a while until everything is green.


We run the new Gemfile on staging and once that works, deploy 1 production server with BUNDLE_GEMFILE=Gemfile.rails4 to fix issues until we are confident to switch everything.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment