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
path.join("vendor/cache.rails4")
end
end
Bundler::Runtime.class_eval do
def cache_path
Bundler.app_cache
end
end
else
gem "rails", "3.2.21"
... other gems that are different ...
end
... unaffected gems ...
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.read(file)
file.scan(/^ remote: \S+\n revision: \S+$/)+ file.scan(/^ \S+ \(\S+\)/)
end
diff = ((a | b) - (a & b))
diff.map! { |match| match[/\S+github\.com\S+\/(\S+?)(\.git)?\s/, 1] || match[/\S+/] }.uniq
allowed = [
"actionmailer",
"actionpack",
"activemodel",
"activerecord",
"activesupport",
"arel",
"builder",
"coffee-rails",
"journey",
"rack",
"rack-cache",
"rack-ssl",
"rails",
"railties",
"rdoc",
"sass",
"sass-rails",
"sprockets",
"sprockets-rails",
"strong_parameters",
]
(diff - allowed).sort.uniq
end
end
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
end
end
# 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 []
end
If you have enough worker capacity this is good enough:
gemfile:
- 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.