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

継続的Railsアップグレード / Continuous Rails Upgrade

Takumi Shotoku
September 15, 2021

継続的Railsアップグレード / Continuous Rails Upgrade

【iCARE Dev Meetup #25】 集えRubyist~著名Rubyistから学ぶ~2.0
https://icare.connpass.com/event/221922/

Takumi Shotoku

September 15, 2021
Tweet

More Decks by Takumi Shotoku

Other Decks in Technology

Transcript

  1. 継続的Railsアップグレード
    iCARE Dev Meetup #25 2021/09/15
    1

    View full-size slide

  2. 自己紹介
    • 名前: 神速
    • 会社: メドピア株式会社
    • 所属: CTO室SRE
    • GitHub: @sinsoku (画像右上)
    • Twitter: @sinsoku_listy (画像右下)
    2

    View full-size slide

  3. 復習: Railsのアップグレードの手順1
    1. (前準備) Rails以外のgemを最新にする
    2. 新しいバージョンについて調べる
    3. Railsのバージョンを上げる
    4. CIでテストを流してみる
    5. 失敗したテストを直す
    1 2019-08 Rails6にいつ上げるか? Roppongi.rb - https://www.slideshare.net/sinsoku/rails6-159723148
    3

    View full-size slide

  4. 復習: Draftプルリクを作る
    4

    View full-size slide

  5. 復習: Draftプルリクで対応を進める
    5

    View full-size slide

  6. 復習: 5.2 でも動く変更を先にマージする
    6

    View full-size slide

  7. 復習: Draftプルリクをリベース
    7

    View full-size slide

  8. 復習: 大きなタスクを作らない
    「Rails 6にアップグレードする」のように大きなタスクを作ら
    ず、毎週少しずつ対応するのがおすすめです。
    多くの変更は Rails 5.2 のままで修正できます。
    8

    View full-size slide

  9. 復習まとめ
    • バージョンN2のDraft PRを作る
    • cherry-pickできる変更だけマージする
    • Draft PRを可能な限り小さくする
    • アップグレード業は少しずつ対応する
    2 next versionの意味です。
    9

    View full-size slide

  10. 継続的Railsアップグレード
    10

    View full-size slide

  11. 継続的Railsアップグレードの手順
    1. 開発環境を2バージョンで起動可能にする
    2. 2バージョンをCIで動かす
    3. コードを細かく修正する
    • gemのアップグレード
    • 失敗したテストの修正
    • バージョンNから機能をバックポート
    11

    View full-size slide

  12. Rails 6.1用のGemfileの用意
    • gemfiles/rails_61.gemfile を用意する
    • 環境変数BUNDLE_GEMFILE を指定する
    • Railsのバージョンを切り替えられる
    • 開発環境で direnv3 を使うと便利
    3 https://github.com/direnv/direnv
    12

    View full-size slide

  13. gemfiles/rails61.gemfile
    eval_gemfile File.expand_path('../Gemfile', __dir__)
    {
    'rails' => { github: 'rails/rails', tag: 'v6.1.4.1' },
    'enumerize' => { github: 'brainspec/enumerize', tag: 'v2.4.0' },
    'switch_point' => nil
    }.each do |name, opts|
    dependencies.delete_if { |d| d.name == name }
    gem(name, **opts) if opts
    end
    13

    View full-size slide

  14. BUNDLE_GEMFILE を指定する
    開発環境のみv6.1でも実行が可能になる。
    $ export BUNDLE_GEMFILE=gemfiles/rails_61.gemfile
    $ cp Gemfile.lock gemfiles/rails_61.gemfile.lock
    $ bundle install
    ここで大事なのは依存関係の解決で、v6.1対応は別で行います。
    14

    View full-size slide

  15. 2バージョンをCIで動かす
    • 環境変数BUNDLE_GEMFILEを指定してテストを実行する
    • v6.0, v6.1の両方でテストを実行する
    この時点ではテストが失敗しても気にしない。
    15

    View full-size slide

  16. CircleCIの設定の例4
    jobs:
    rspec-rails61:
    executor: rails
    environment:
    BUNDLE_GEMFILE: gemfiles/rails_61.gemfile
    BUNDLE_PATH_RELATIVE_TO_CWD: true
    steps:
    - checkout
    - run: cp Gemfile.lock gemfiles/rails_61.gemfile.lock
    # ͋ͱ͸ bundle-install ͯ͠ɺςετΛ࣮ߦ͢Ε͹ྑ͍
    4
    BUNDLE_PATH_RELATIVE_TO_CWDは#{Rails.root}/vendor/bundleのキャッシュを活用するため
    16

    View full-size slide

  17. !
    失敗するCIが実行されるのを減らしたい
    • 深夜ビルドにする5
    • 一時的にテストをスキップする
    comment = 'v6.1ରԠ͢Δ·ͰҰ୴εΩοϓ' if Rails::VERSION::STRING.start_with?('6.1')
    RSPec.describe Foo, type: :model, skip: comment do
    # ςετίʔυ
    end
    5 CIでRailsのmasterブランチを使ってテストを実行する
    参考: https://sinsoku.hatenablog.com/entry/2020/10/31/100000
    17

    View full-size slide

  18. タスクを洗い出す
    Rails 6.1のCIが用意できたので、残りタスクをチケットに登録し
    て潰していく。
    • rails_61.gemfile に記載されているgemの対応
    • CIで失敗したテストの修正
    • Railsの新機能や設定の調査や対応
    • CI以外の動作確認
    18

    View full-size slide

  19. 失敗したテストや警告を直す
    MedPeerで対応した事例をいくつか紹介します。
    • 互換性のある修正
    • バックポート
    • Railsバージョンによる条件分岐
    20

    View full-size slide

  20. 互換性のある修正
    v6.0, v6.1のどちらでも動くコードが書ける場合、mainブランチ
    に簡単に取り込むことができる。
    - Comment.eager_load(:post).select(:title)
    + Comment.eager_load(:post).select(:title, :post_id)
    この変更の詳細についてはrails/rails#427536を参考。
    6 ActiveRecord 6.1 raises AM::MissingAttributeError when used with eager_load and select
    https://github.com/rails/rails/issues/42753
    21

    View full-size slide

  21. バージョンNでしか動かない機能に
    どう対応するか?
    22

    View full-size slide

  22. v6.0で警告の出るコード例
    SourceAnnotationExtractor::Annotation
    .register_extensions("scss", "sass", "less", "js") { |tag| /\/\/\s*(#{tag}):?\s*(.*)$/ }
    #=> DEPRECATION WARNING: SourceAnnotationExtractor is deprecated! \
    # Use Rails::SourceAnnotationExtractor instead.
    v5.2では Rails::SourceAnnotationExtractor は存在しません。
    23

    View full-size slide

  23. バックポート
    v6.0と同じコードで動くようにパッチを書く。
    # config/application.rb
    module Foo
    class Application < Rails::Application
    # ུ
    end
    end
    require 'rails60_backporting'
    24

    View full-size slide

  24. バックポート
    # lib/rails60_backporting.rb
    if Rails::VERSION::MAJOR != 5
    warn('Remove this patch after Rails v6.0')
    return
    end
    # refs: https://github.com/rails/rails/pull/32065
    Rails::SourceAnnotationExtractor = ::SourceAnnotationExtractor
    25

    View full-size slide

  25. バックポート
    # lib/rails60_backporting.rb
    if Rails::VERSION::MAJOR != 5
    warn('Remove this patch after Rails v6.0')
    return
    end
    # refs: https://github.com/rails/rails/pull/32065
    Rails::SourceAnnotationExtractor = ::SourceAnnotationExtractor
    # refs: https://github.com/rails/rails/pull/34051
    Module.alias_method(:module_parent_name, :parent_name)
    require 'rails60_backporting/inspection_filter'
    require 'rails60_backporting/active_record_methods'
    require 'rails60_backporting/content_disposition'
    26

    View full-size slide

  26. バックポート
    # lib/rails60_backporting/active_record_methods
    module Rails60Backporting
    module ActiveRecordMethods
    extend ActiveSupport::Concern
    class_methods do
    # refs: https://github.com/rails/rails/pull/31941
    def pick(*column_names)
    limit(1).pluck(*column_names).first
    end
    # refs: https://github.com/rails/rails/pull/31989
    def create_or_find_by(attributes, &block); end # ུ
    def create_or_find_by!(attributes, &block); end # ུ
    end
    end
    end
    ActiveSupport.on_load(:active_record) do
    ActiveRecord::Base.include(Rails60Backporting::ActiveRecordMethods)
    end
    27

    View full-size slide

  27. Railsバージョンによる条件分岐
    バックポートが難しい場合、Railsのバージョンで条件分岐を使っ
    て解決します。
    if Rails::VERSION::STRING.start_with?('6.1')
    # 6.1ͷॲཧ
    else
    # 6.0ͷॲཧ
    end
    28

    View full-size slide

  28. v6.1で挙動が変わるAM::Error
    v6.1でActiveModel::Errorクラスが導入され、モデルのエラー管
    理が変わりました。
    model.errors.where(:name, :foo, bar: 3).first
    この変更に関連してerrors.addメソッドの戻り値も変わりまし
    た。
    29

    View full-size slide

  29. AM::Error の変更に伴う修正
    これはRailsバージョンの分岐で対応できます。
    def foo
    # CSVͷॲཧ
    rescue CSV::MalformedCSVError => e
    - errors.add(:file, " foramt is invalid. #{e.message}")
    + obj = errors.add(:file, " foramt is invalid. #{e.message}")
    + Rails::VERSION::STRING.start_with?('6.1') ? errors.messages_for(:file) : obj
    end
    30

    View full-size slide

  30. 継続的Railsアップグレードのまとめ
    • 2つのバージョンで動く状態を維持する
    • Gemfileを2つ用意する
    • CIで2つのバージョンのテストを実行する
    • アップグレード作業を少しずつ対応する
    7.0.0.rc1が出る前にv6.1に上げておこう
    31

    View full-size slide

  31. おまけ: 7.0.0.rc1 を試そう
    • RC が出たらCIで動かしてみよう
    • gemfiles/rails_70.gemfile を作ろう
    • OSSのコントリビュートチャンス
    • gemの7.0.0対応、Railsへのバグ報告7
    7 Rails の issue を解決するまでの手順とOSS初心者でもできること
    https://sinsoku.hatenablog.com/entry/2019/10/17/013415
    32

    View full-size slide