Upgrading Rails at scale. RailsConf 2018

Upgrading Rails at scale. RailsConf 2018

Journey of a Rails upgrade in one of the biggest ruby on rails application running in production.

A478cd8745e3f45919cb5835b321dc6b?s=128

Edouard Chin

April 18, 2018
Tweet

Transcript

  1. 4.
  2. 5.
  3. 6.
  4. 7.
  5. 8.

    +

  6. 10.
  7. 15.
  8. 18.
  9. 20.
  10. 21.
  11. 24.

    Make CI fail only when new broken code is introduced

    Existing broken tests will be marked and allowed to fail
  12. 26.

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

    mark_as :failing_on_rails_next test 'example test' do raise StandardError, 'This test

    fails but will be considered as passing' end desc 'This task is great' task :great_task do # ... end desc 'Another cool task' task :cool_task do # ... end
  14. 28.

    # frozen_string_literal: true included do class_attribute :metadata, instance_accessor: false self.metadata

    = Hash.new { |hash, key| hash[key] = [] } end tags.each do |tag| next if method_defined?("#{tag}?") define_method "#{tag}?" do self.class.metadata[tag].include?(name.to_sym) end define_singleton_method :method_added do |name| tags.each do |tag| metadatas[tag] << name end singleton_class.send(:remove_method, :method_added) end module MarkingModule extend ActiveSupport::Concern module ClassMethods def mark_as(*tags) end end end
  15. 29.

    # frozen_string_literal: true class MyTest < MiniTest::Test end mark_as :failing_on_rails_next

    def test_example end mark_as :slow def another_example end include MarkingModule # The `metadata` hash will look like this { failing_on_rails_next: ['test_example'], slow: ['another_example'] } puts MyTest.new('test_example').failing_on_rails_next? # true
  16. 32.

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

    false } exit_code = Minitest.run ARGV
  17. 33.

    module Minitest module Reporters class RailsNextReporter < BaseReporter ALLOWED =

    :allowed FIXED = :fixed attr_writer :state def after_test(test) return unless test_will_fail?(test) @state = else ALLOWED end end def test_will_fail?(test) return false unless options[:rails_next] test.respond_to?(:failing_on_rails_next?) && test.failing_on_rails_next? end if test.failures.none? FIXED def record(result) case @state when ALLOWED result.failures.clear end end when FIXED make_ci_fail end end end
  18. 34.
  19. 40.
  20. 46.

    class Subscriber < ActiveSupport::Subscriber cattr_accessor(:deprecations) { [] } def deprecation(event)

    message = event.payload[:message] deprecations << message unless exists?(message) end private def exists?(message) message && deprecations.any? { |deprecation| deprecation == message } end end Subscriber.attach_to :rails Notification name: “rails.deprecation”
  21. 47.

    class Output < ActiveSupport::Subscriber def process_action(*) Subscriber.deprecations.each do |deprecation| Rails.logger.warn

    "[DEPRECATION WARNING] #{deprecation}" end Subscriber.deprecations.clear end end Output.attach_to :action_controller
  22. 48.

  23. 49.
  24. 50.
  25. 51.

    source 'https://rubygems.org' if ENV['SHOPIFY_NEXT'] && File.exist?("#{Bundler::SharedHelpers.default_gemfile}_next.lock") module Bundler::SharedHelpers def default_lockfile=(path)

    @default_lockfile = path end def default_lockfile @default_lockfile ||= Pathname.new("#{default_gemfile}.lock") end end Bundler::SharedHelpers.default_lockfile = Pathname.new("#{Bundler::SharedHelpers.default_gemfile}_next.lock") class Bundler::Dsl unless self.method_defined? :to_definition_unpatched alias_method :to_definition_unpatched, :to_definition end def to_definition(bad_lockfile, unlock) to_definition_unpatched(Bundler::SharedHelpers.default_lockfile, unlock) end end end if ENV['SHOPIFY_NEXT'] else gem 'rails', '~> 5.1.0' end gem 'rails', '~> 5.2.0'
  26. 52.
  27. 55.
  28. 56.
  29. 57.

    — 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