READYFOR Tech Blog

READYFOR のエンジニアブログ

Upgrading READYFOR Rails from 5.2 to 6.0

こんにちは、READYFORでバックエンドエンジニアをしている安本です。

2022年10月17日に主要サービスのアプリケーションをRails 5系からRails 6系へとメジャーアップデートしました🎉

EOLが"Support for Rails 5.2.8 ended on 2022-06-01"ということもあり、空き時間に進める趣味プロジェクトではなく、スクワッドのミッションのプロジェクトとして対応しました。 issueを振り返ると、2022年5月に開始、リリース完了したのが10月なので、約5ヶ月間かかりました。

一言にRailsアップデートと言っても、モノリシックRailsアプリとAPIサーバーの2つを対応する必要があり、また、10年以上続いているサービスのため、技術的負債を一掃しつつ、コードベースを改善しつつ対応しました。

私はこれまで Rails のアップグレードを行ったことがなかったため、改めてここにやったことを振り返ろうと思います。

やったこと

ドキュメントを一読

デッドコード削除とテストカバレッジを上げる

冒頭に書いたように10年以上続いているサービスのため、デッドコードがこんもりあります。そこで、その作業をRails6のアップデートの作業の一環として対応しました。
また、カバレッジレポートを眺めて、コード行数が多いのにカバレッジが低いものはテストコードを追加したりしました。

Rails以外のgemを最新にする

弊社はDependabotをランダムアサインしてライブラリのアップデートを対応する運用ですが、お世辞にもうまくワークしておらず、DependabotのPRが溜まりに溜まっている状態でした。そのため、こちらもRails6のアップデートの作業の一環として対応しました。

  • 未使用gemを削除する。
  • development,testはエイヤ!でまとめて上げます。
    • bundle update -g development -g test
  • Rails以外のgemを最新にしていきます。
    • 影響範囲の小さそうなものはある程度まとめてリリースします。
    • 影響範囲の(大きそう|わからない)ものは一つ一つリリースします。

5系6系の両バージョンで起動CIできるようにする

gemのconflictを避けることができ、また、Rails5は通るがRails6はエラーとなるコードが把握しやすくなります。

  • main ブランチ判定用の環境変数RAILS_MASTERを追加
gem "rails", "~> 5.2.8" if ENV["RAILS_MASTER"]
  • BUNDLE_GEMFILEでgemを切り替えられるようにします。
    • gemfiles/rails_6.0.6.gemfile
eval_gemfile File.expand_path("../Gemfile", __dir__)
gem "rails", "6.0.6"
  • Rails6のGemfile.lockを用意します。
    • Rails5のGemfile.lockをコピー
cp Gemfile.lock gemfiles/rails_6.0.6.gemfile.lock
  • 環境変数 RAILS_VERSION BUNDLE_GEMFILE をセットしてgemをupdateします。
export BUNDLE_GEMFILE=gemfiles/rails_6.0.6.gemfile

bundle update rails
  • CIの設定
    • 環境変数 BUNDLE_GEMFILE を設定します。
jobs:
  rspec:
    name: rails${{ matrix.rails }} [${{ matrix.index }}]
    runs-on: ubuntu-latest
    timeout-minutes: 20

    strategy:
      fail-fast: false
      matrix:
        index: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
        rails:
          - "5.2.8"
          - "6.0.6"

    env:
      BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/rails_${{ matrix.rails }}.gemfile
  • CIが両バージョンで通るように修正していきます。
    • Rails.gem_version < Gem::Version.new("6.0")の分岐などを駆使しました。

rails app:update

  • 差分を1つ1つ確認しつつ、なるべくレビューしやすいDiffになるように取り込んでいきました。
bundle exec rails app:upate

DEPRECATIONWARNINGを潰す

  • 対応したもの
    • [x] secrets.secret_token is deprecated
    • [x] Dangerous query method (method whose arguments are used as raw SQL) called with non-attribute argument(s)
    • [x] DEPRECATION WARNING: Accessing hashes returned from config_for by non-symbol keys is deprecated and will be removed in Rails 6.1. Use symbols for access instead.
    • [x] DEPRECATION WARNING: ActionDispatch::Http::ParameterFilter is deprecated and will be removed from Rails 6.1. Use ActiveSupport::ParameterFilter instead.
    • [x] DEPRECATION WARNING: Initialization autoloaded the constants ・・・
    • [x] DEPRECATION WARNING: NOT conditions will no longer behave as NOR in Rails 6.1.
    • [x] DEPRECATION WARNING: render file: should be given the absolute path to a file

動作検証

テストカバレッジは80%をキープしていますが、いざ結合テスト、モンキーテストを実施してみると、ポロポロと不具合が見つかりました。

READYFORで影響のあったRailsの変更点をまとめておきます。

リリース

  • 上記作業で、Rails6に関係ない修正は、別PRで小さくリリースしていきました。
    • 最終のリリースPRは、Gemfileとconfigの差分のみになるようにしていきました。
  • ロールバックが許されないリリースではないこと、弊社はECSでBlue/Greenデプロイということもあり、心理的負担はそれほど高くない状態でリリースできました。
  • 想定外のエラーはあるものと覚悟してリリースしましたが、クリティカルな問題は起きず無事リリースを終えることができました。

まとめ

  • Rails の新しいバージョンに近づくほど、アップグレードが容易になるので、なるべく早期にアップグレードを対応することが重要。
    • アップグレードが遅れだすと、アプリケーションにモンキーパッチを適用したり、ワークアラウンドなコードが増え、技術的負債が溜まり、アップグレードのボトルネックになります。
  • Railsアップグレードのような影響範囲のわかりずらい対応には、とにかくカバレッジは重要。
    • 80%は許容範囲かと思っていましたが、90%以上にはしていきたいです。