Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Journey of a complex gem upgrade

A478cd8745e3f45919cb5835b321dc6b?s=47 Edouard Chin
June 09, 2018

Journey of a complex gem upgrade


Edouard Chin

June 09, 2018


  1. Journey of a complex gem upgrade

  2. I ❤ Japan!

  3. Edouard Chin https://github.com/Edouard-chin @DaroudeDudek

  4. Make the upgrade process faster, smoother and safer

  5. Works for any gem upgrade

  6. 1. Spot breaking changes in the CHANGELOG 2. `bundle update`

    3. Deploy
  7. None
  8. 1. Spot breaking changes in the CHANGELOG 2. `bundle update`

    3. Deploy
  9. None
  10. Progressively fix all issues

  11. Easy to upgrade and solely used in test/development Tough to

    upgrade, takes a long time and effort Relatively easy to bump but critical for your infra
  12. Running in production since 2006 600.000 Merchants One of the

    largest Ruby application
  13. $ bundle-stats

  14. None
  15. Upgrade can be delayed by months without proper tooling

  16. Full upgrade cycle ⏱
 6 months - 1 year

  17. 1. Prepare our application for dual boot

  18. source 'https://rubygems.org' gem 'rubykaigi', ‘3.0’

  19. source 'https://rubygems.org' if ENV[‘SHOPIFY_NEXT’] bundler_monkey_patch end if ENV['SHOPIFY_NEXT'] gem 'rubykaigi',

    ‘3.1’ else gem 'rubykaigi', ‘3.0’ end
  20. None
  21. 2. Fix all issues that might happen on boot

  22. 2868 errors

  23. None
  24. None
  25. 2868 errors

  26. None
  27. Lessons learnt: Stop the bleeding first

  28. 1. Prepare app for dual-booting 2. Fix issues happening on

    boot 3. Enable CI
  29. Make CI fail only when new broken code is introduced

    Existing broken tests will be marked and allowed to fail
  30. mark_as :failing_on_rails_next test 'example test' do raise StandardError, ‘This test

    is marked as failing’ end
  31. mark_as :failing_on_rails_next test 'example test' do raise StandardError, 'This test

    fails but will be considered as passing' end # What's the difference between both test 'example test' do skip('This test is failing lets not run it') if running_on_rails_next? raise StandardError, 'This test fails but will be skipped' end
  32. mark_as :failing_on_rails_next test 'example test' do raise StandardError, ‘This test

    is marked as failing’ end desc 'This task is great' task :great_task do # ... end desc 'Another cool task' task :cool_task do # ... end
  33. # frozen_string_literal: true class MyTest < MiniTest::Test include MarkingModule mark_as

    :failing_on_rails_next def test_example … end end puts MyTest.new('test_example').failing_on_rails_next? # true
  34. Minitest Reporter

  35. ProgressReporter SummaryReporter

  36. # Snippet taken from minitest.rb at_exit { exit exit_code ||

    false } exit_code = Minitest.run ARGV
  37. module Minitest module Reporters class RailsNextReporter < BaseReporter ALLOWED =

    :allowed attr_writer :state def after_test(test) return unless test_will_fail?(test) @state = ALLOWED end def test_will_fail?(test) return false if not_on_rails_next test.failing_on_rails_next? end def record(result) result.failures.clear if @state == ALLOWED end end end end
  38. None
  39. None
  40. Side advantage on stopping the bleeding: Getting help from other

  41. Workforce )*+,-). +,.)*. *+,+,.,. )*+,-). +,.)*. *+,+,.,.

  42. Componentization

  43. Componentization Before After

  44. Natural way to identify which code belongs to which component.

  45. None
  46. None
  47. None
  48. None
  49. The less deprecations, the easier it will be to upgrade

  50. None
  51. ActiveSupport::Deprecation.warn(‘foo is deprecated’)

  52. You have introduced new deprecations in the codebase

  53. None
  54. — test_foo: - DEPRECATION WARNING: This code is deprecated -

    DEPRECATION WARNING: Another deprecation test_bar - DEPRECATION WARNING: This code is deprecated class MyTest < MiniTest::Test def test_foo ActiveSupport::Deprecation.warn(‘This code is deprecated') ActiveSupport::Deprecation.warn(‘Another deprecation’) end def test_bar ActiveSupport::Deprecation.warn(‘This code is deprecated’) end end
  55. None
  56. 1. Add rotating seats in planes

  57. 2. Have a way to run your app with different

    version of a gem
  58. 3. Stop the bleeding

  59. 4. Have a clear distinction on who should own what

    and parallelize the work 5. Add some gamification
  60. 6. Deployment tooling to mitigate the risk

  61. 7. Keep track and fix deprecations ASAP

  62. Edouard Chin https://github.com/Edouard-chin @DaroudeDudek Thanks!