Upgrading to Rails 3

  • 773 views
Uploaded on

Changing the wheels on the bus at 80 mph …

Changing the wheels on the bus at 80 mph
Andrew Bloomgarden and Julian Giuca
RailsConf 2013

Long-running branches are painful, but upgrading to Rails 3 requires one if you can't stop development, right? Wrong! At New Relic, we worked on upgrading to Rails 3 on master while letting development continue in Rails 2. We patched Bundler, built a backwards-compatible boot sequence, and punched ActiveScaffold in the face. Other developers, meanwhile, released 1400 commits worth of work without noticing any changes. We talk about what we did, why we did it, and why we think this approach can help developers get over the hurdle into the Rails 3 promised land.

More in: Technology , Education
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
    Be the first to like this
No Downloads

Views

Total Views
773
On Slideshare
0
From Embeds
0
Number of Embeds
0

Actions

Shares
Downloads
0
Comments
0
Likes
0

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 1. UPGRADINGTO RAILS 3Changing the wheels on the bus at 80mph(or 128.784 kph for everyone else)Andrew Bloomgarden Julian Giuca
  • 2. Andrew introductionAndrew Bloomgarden@aughr2
  • 3. Julian Giuca@juliangiuca3
  • 4. Julian Giuca@juliangiuca3
  • 5. Julian Giuca@juliangiuca3
  • 6. Julian Giuca@juliangiuca3
  • 7. 4
  • 8. Overview5You can dual bootRails 2 and Rails 3
  • 9. Do try this at home!6
  • 10. Let’s talk about numbers...7
  • 11. Rails versions in the wild2.x.x 3.x.x8Numberofaccounts
  • 12. 2.2.2# 2.2.3# 2.3.2# 2.3.4# 2.3.5# 2.3.8# 2.3.10# 2.3.11# 2.3.12# 2.3.14# 2.3.15# 3.0.1# 3.0.3# 3.0.5# 3.0.11# 3.0.15# 3.0.18# 3.0.19# 3.1.1# 3.2.1# 3.2.8# 3.2.11# 3.2.13#Rails versions with >1 server2.x.x 3.x.xNumberofaccounts
  • 13. Rails version vs number of server10
  • 14. Upgrading to Rails 31. Procrastinate2. Goals3. Feature Branch4. Fix tests5. Merge6. Push7. Cross fingers8. Fix Production11
  • 15. Putting it offRearchitecting the app firstDo it all in a big spike!Have a ruby_19 branchSTUPID THINGS WE TRIED12
  • 16. Rails 3? Why now?13
  • 17. Rails 3? Why now?Someone will make upgradingeasier soon!13
  • 18. Rails 3? Why now?Someone will make upgradingeasier soon!Oh man, this is going tobe awful.13
  • 19. Gems are leaving us behind14
  • 20. Gems are leaving us behind14
  • 21. Come and work onour Rails 2.3 app!15
  • 22. 16
  • 23. Upgrading to Rails 31. Procrastinate2. Goals3. Feature Branch4. Fix tests5. Merge6. Push7. Cross fingers8. Fix Production17
  • 24. A bit of history18
  • 25. A bit of historyMove to Rails 2.0.118
  • 26. A bit of history18
  • 27. A bit of historyRails 2.3.1470,000 application LOC59,000 test LOC18
  • 28. Don’t break the world19
  • 29. Keep everyone happynot thisthisthisthis20
  • 30. Keep ourselves happyCONFLICT (content): …CONFLICT (content): …CONFLICT (content): …⋮CONFLICT (content): …Automatic merge failed; fix conflictsand then commit the result.21
  • 31. Upgrading to Rails 31. Procrastinate2. Goals3. Feature Branch4. Fix tests5. Merge6. Push7. Cross fingers8. Fix Production22
  • 32. 23
  • 33. 24
  • 34. I’m looking at you Rails 1.2.2 people25
  • 35. I’m looking at you Rails 1.2.2 people25
  • 36. Gemfileplatforms :ruby_18 dogem ruby-debugendplatforms :ruby_19 dogem debuggerend26
  • 37. 27
  • 38. Monkey patched!27
  • 39. In the Gemfileif ENV.include?("USE_RAILS_3")class Bundler::Dslif !self.method_defined?(:to_definition_without_rails3_lockfile)alias_method :to_definition_without_rails3_lockfile, :to_definitionenddef to_definition(old_lockfile, unlock)current = File.expand_path(Dir.pwd)filename = File.join(current, "Gemfile_rails3.lock")lockfile = Pathname.new(filename)to_definition_without_rails3_lockfile(lockfile, unlock)endendmodule Bundler::SharedHelpersdef default_lockfilecurrent = File.expand_path(Dir.pwd)filename = File.join(current, "Gemfile_rails3.lock")lockfile = Pathname.new(filename)Pathname.new(lockfile)endendend28
  • 40. Two Gemfile.lock files29
  • 41. Gemfileif is_rails3gem rails, 3.0.19gem lighthouse-api, ~>2.0.0gem dalli, ~> 2.3.0gem jquery-rails, ~>2.1gem active_reloadelsegem rails, 2.3.15gem lighthouse-api, ~>1.1.0gem dalli, ~> 1.0.4end30
  • 42. # Booting in Rails 3 mode$ export USE_RAILS_3=true$ bundle install$ rails server31
  • 43. Upgrading to Rails 31. Procrastinate2. Goals3. Feature Branch4. Fix tests5. Merge6. Push7. Cross fingers8. Fix Production32
  • 44. Upgrading to Rails 31. Procrastinate2. Goals3. Feature Branch4. Fix tests5. Merge6. Push7. Cross fingers8. Fix ProductionImproved!32
  • 45. From an idea to production1. Procrastinate2. Goals3. Onto master4. Getting it to work5. Rollout6. Lessons learned33
  • 46. From an idea to production1. Procrastinate2. Goals3. Onto master4. Getting it to work5. Rollout6. Lessons learned34
  • 47. Make it easy for everyone# Rails 2$ script/server# Rails 3$ script/server335
  • 48. From an idea to production1. Procrastinate2. Goals3. Onto master4. Getting it to work5. Rollout6. Lessons learned36
  • 49. Onto master1. Boot Rails 32. Dual boot3. Unbreak Rails 24. Merge upstream37
  • 50. Unbreak Rails 2BootsRails2RunsRails2BootsRails3RunsRails3Rails 2
  • 51. Unbreak Rails 2BootsRails2RunsRails2BootsRails3RunsRails3Rails 2Boot Rails 3
  • 52. Unbreak Rails 2BootsRails2RunsRails2BootsRails3RunsRails3Rails 2Boot Rails 3
  • 53. Unbreak Rails 2BootsRails2RunsRails2BootsRails3RunsRails3Rails 2Boot Rails 3
  • 54. Unbreak Rails 2BootsRails2RunsRails2BootsRails3RunsRails3Rails 2Boot Rails 3Dual boot
  • 55. Unbreak Rails 2BootsRails2RunsRails2BootsRails3RunsRails3Rails 2Boot Rails 3Dual boot
  • 56. Unbreak Rails 2BootsRails2RunsRails2BootsRails3RunsRails3Rails 2Boot Rails 3Dual boot
  • 57. Unbreak Rails 2BootsRails2RunsRails2BootsRails3RunsRails3Rails 2Boot Rails 3Dual bootRestore Rails 2
  • 58. Unbreak Rails 2BootsRails2RunsRails2BootsRails3RunsRails3Rails 2Boot Rails 3Dual bootRestore Rails 2
  • 59. Onto master1. Boot Rails 32. Dual boot3. Unbreak Rails 24. Merge upstream39
  • 60. From an idea to production1. Procrastinate2. Goals3. Onto master4. Getting it to work5. Rollout6. Lessons learned40
  • 61. Dual-booting that worksRails 2.3 Rails 3config/boot.rb requires Rails requires Bundlerconfig/environment.rbloads, configures,and initializes theappinitializes the appconfig/application.rb doesn’t existloads andconfigures the app41
  • 62. Dual-booting that worksRails 2.3 Rails 3config/boot.rb requires Rails requires Bundlerconfig/environment.rbloads, configures,and initializes theappinitializes the appconfig/application.rb doesn’t existloads andconfigures the app41
  • 63. Just make it work42
  • 64. config/boot.rbif ENV.include?("USE_RAILS_3")require rubygems# Set up gems listed in the Gemfile.ENV[BUNDLE_GEMFILE] ||=File.expand_path(../../Gemfile, __FILE__)require bundler/setup43else# 123 lines that load Rails# All that for this:Rails.boot!end
  • 65. config/boot.rbif ENV.include?("USE_RAILS_3")require rubygems# Set up gems listed in the Gemfile.ENV[BUNDLE_GEMFILE] ||=File.expand_path(../../Gemfile, __FILE__)require bundler/setup43else# 123 lines that load Rails# All that for this:Rails.boot!end
  • 66. config/boot.rbif ENV.include?("USE_RAILS_3")require rubygems# Set up gems listed in the Gemfile.ENV[BUNDLE_GEMFILE] ||=File.expand_path(../../Gemfile, __FILE__)require bundler/setup43else# 123 lines that load Rails# All that for this:Rails.boot!end
  • 67. config/environment.rb# Load the rails applicationrequire_relative "application"if CoreAppConfig.is_rails3?# Initialize the rails applicationRpmSite::Application.initialize!end44
  • 68. config/application.rbmodule NewRelicApplicationConfigdef self.rails3_config(config)# Do Rails 3 stuffcommon_config(config)enddef self.rails2_config(config)# Do Rails 2 stuffcommon_config(config)enddef self.common_config(config)# ...endend45
  • 69. config/application.rb46if is_rails3require rails/allBundler.require(:default, Rails.env)module RpmSiteclass Application < Rails::Application::NewRelicApplicationConfig.rails3_config(config)endendelseRails::Initializer.run do |config|::NewRelicApplicationConfig.rails2_config(config)endend
  • 70. config/application.rb46if is_rails3require rails/allBundler.require(:default, Rails.env)module RpmSiteclass Application < Rails::Application::NewRelicApplicationConfig.rails3_config(config)endendelseRails::Initializer.run do |config|::NewRelicApplicationConfig.rails2_config(config)endend
  • 71. config/application.rb46if is_rails3require rails/allBundler.require(:default, Rails.env)module RpmSiteclass Application < Rails::Application::NewRelicApplicationConfig.rails3_config(config)endendelseRails::Initializer.run do |config|::NewRelicApplicationConfig.rails2_config(config)endend
  • 72. Have tests you trust47
  • 73. Use CI48
  • 74. Upgrade dependencies on master49
  • 75. BackgroundJob to ResqueBj.submit script/background/do_something.rb,:email => DEV_TEAM50
  • 76. Never againdef getopts opts = {}if RUBY_VERSION.to_f >= 1.9# In 1.9 this throw/catch/getopts nonsense breakslambda do |*args|keys, default, ignored = args[keys].flatten.each do |key|[key, key.to_s, key.to_s.intern].each do |key|return opts[key] if opts.has_key?(key)endendreturn defaultendelse # In 1.8 you cant return across threadslambda do |*args|keys, default, ignored = argscatch(opt) do[keys].flatten.each do |key|[key, key.to_s, key.to_s.intern].each do |key|throw opt, opts[key] if opts.has_key?(key)endenddefaultendendendend51
  • 77. Add an abstraction layerBj.submit script/background/do_something.rb,:email => DEV_TEAM52
  • 78. Add an abstraction layerAsync::Command.new(script/background/do_something.rb).notify(DEV_TEAM).enqueue52
  • 79. Use Resque or BackgroundJobclass Async::Command < Async::Jobif CoreAppConfig.use_resque?include Async::ResqueJobextend Resque::Plugins::Historyelseinclude Async::BjJobend# ...end53
  • 80. Finally, use Resque54
  • 81. Branch by abstraction55The problem being with featurebranches is that the current state ofany one of them might be unable tobe deployed for a number of weekswhile the team gets it right.Thosebranches just end up running andrunning ….– Paul Hammant
  • 82. Branch by abstraction55Branch by abstraction: a patternfor making large-scale changes to yourapplication incrementally on mainline.– Jez Humble
  • 83. Punt when you can56
  • 84. ActiveSupport::Deprecation.silenced = true57
  • 85. Your old routes still workRpmSite::Application.routes.draw do |map|Jammit::Routes.draw(map)# ...end58
  • 86. Your old routes still workRpmSite::Application.routes.draw do |map|Jammit::Routes.draw(map)# ...end58
  • 87. Your old mailers still workclass AccountMailer < ApplicationMailerdef new_account(account, user, subscription)headers[Errors-to] = BOUNCE_ACCOUNTfrom BILLING_ACCOUNTsubject "New Relic invoice account request"recipients INVOICE_RECIPIENT@account = account@user = user@subscription = subscriptionendend59
  • 88. Your old mailers still workclass AccountMailer < ApplicationMailerdef new_account(account, user, subscription)headers[Errors-to] = BOUNCE_ACCOUNTfrom BILLING_ACCOUNTsubject "New Relic invoice account request"recipients INVOICE_RECIPIENT@account = account@user = user@subscription = subscriptionendend59
  • 89. Your old models (can) still workself.store_full_sti_class = false60
  • 90. Your old views (can) still workhelper PrototypeHelperclear_helpers61
  • 91. Your error handling (can) still work# included into ApplicationControllerdef rescue_with_handler(e)if super # ActiveSupport::Rescuabletrueelserescue_action_in_public etrueendend62
  • 92. Temporarily ugly63if CoreAppConfig.is_rails3?# do somethingelse# do something elseend
  • 93. From an idea to production1. Procrastinate2. Goals3. Onto master4. Getting it to work5. Rollout6. Lessons learned64
  • 94. We wantYOU to test!65To run in Rails 3, just runscript/server3
  • 95. Extensive manual testing66DatabaseData collectionUIRails 3 UI
  • 96. Make everyone a tester67
  • 97. Turn on Rails 3 by default-is_rails3 = ENV.include?("USE_RAILS_3")+is_rails3 = !ENV.include?("USE_RAILS_2")68
  • 98. Incompatible sessions69ActionDispatch::Session::SessionRestoreError(Original exception: uninitialized constantActionController::Flash::FlashHash)
  • 99. Incompatible sessions69
  • 100. Deploy a canary70
  • 101. Pause the world71
  • 102. Deploy all the servers72
  • 103. Oops73
  • 104. 74
  • 105. And we’re done!75
  • 106. And we’re done!75
  • 107. A few very tiny fires76
  • 108. Breaking the audit trailActionController::Base.class_eval docache_sweeper :audit_sweeperendAudit.add_observer(AuditSweeper.instance)class AuditSweeper < ActionController::Caching::Sweeperobserve Auditend77
  • 109. Rushed Resque rollout78
  • 110. Punching ActiveScaffold in the face.../bundle/ruby/1.9.1/gems/active_scaffold-3.0.26/lib/active_scaffold/bridges/date_picker/lib/datepicker_bridge.rb:127:in`binread:No such file or directory - .../public/javascripts/active_scaffold/default/date_picker_bridge.js (Errno::ENOENT)79
  • 111. Lost BackgroundJob instrumentationThis page left intentionally blank80
  • 112. Tagging green build off of Rails 2 tests81
  • 113. Lots of cleanup82
  • 114. From an idea to production1. Procrastinate2. Goals3. Onto master4. Getting it to work5. Rollout6. Lessons learned83
  • 115. Lessons learned1. Preorder the donuts your on-call engineers like2. Deprecate ActiveScaffold3. Schedule lots of time for firefighting and cleanup84
  • 116. •Get it into master as early as possible•Incremental everything•CI is your bestest childhood friend(and you secretly have a crush on them)The special magic pony sauce85
  • 117. newrelic.com/rails3_upgrade@aughr | @juliangiuca86We’re done (and we’re hiring)