おいしい健康 開発者ブログ

株式会社おいしい健康で働くエンジニア・デザイナーが社内の様子をお伝えします。

「おいしい健康」のRailsを5.2から6.0にアップグレードしました

プロダクト開発WEBチーム所属の坂東です。
今回は、おいしい健康で長きに渡って運用されているアプリ用APIとWEBページの二つのRailsプロジェクトのメジャーバージョンを、5.2から6.0にアップグレードした経緯について記事にさせていただきます。

背景

Railsでは重大なセキュリティ問題が発見された場合に、それに対応(セキュリティパッチを取り込んだ)した新しいバージョンが公開されます。
バージョン周りの情報は、さまざまなプログラミング言語フレームワークごとに公開されており、EOL(End Of Life)と呼ばれます。
実際にRailsEOLを見ると、2022年6月1日をもってRails5.2のセキュリティサポートが打ち切られることがわかります。
おいしい健康では、今までAPIとWEBの両方でRails5.2を利用していましたが、このまま同じバージョンを継続して利用すると、Railsで何かしらのセキュリティ問題が発見された際に防ぐことができなくなってしまう状況でした。
上記のことを踏まえた結果、Railsのバージョンを6.0にアップグレードすることになりました。

進め方

冒頭でも触れましたが、今回アップグレードする対象のプロジェクトはAPIとWEBの二つがありました。
APIでは、git submoduleを利用してWEBのRailsアプリケーションで定義されたメソッドを部分的に流用する形をとっていたため、まずはWEB側をRails6にアップグレードして、その後にアップグレードしたWEBのプロジェクトを取り込んだ状態でAPIをRails6にアップグレードしました。
Railsのアップグレード手順は、Railsアップグレードガイドを参考にしながら進めました。

Rails6.0アップグレードに伴う変更箇所(一部抜粋)

Zeitwerk

私はRails6.0へのアップグレードでもっとも大きな変更点はZeitwerkの登場だと思います。
ZeitwerkはRails内部で利用される新しいオートロード方式になります。
5.2まではActive Supportで実装されたClassicが利用されていましたが、6.0からはZeitwerkが標準採用される形になりました。
従来のClassic方式はRails7.0で廃止され、Zeitwerkモードのみになってしまうため、今回のアップグレードで乗り換えることにしました。
Zeitwerkへの変更にあたって行なったことは意外と少ないです。
まずはClassicモードで実行されているオートロードをZeitwerkに変更するために、config/application.rbを修正します。

# config.load_defaults 5.2
# config.autoloader = :classic
config.load_defaults 6.0

Rails6.0ではデフォルトでZeitwerkを読み込むので、autoloaderの指定は必要ないです。
もし、Rails6.0のアプリケーションで継続してClassicを利用したい場合は、classicをオプトインする必要があります。

# config.load_defaults 5.2
config.load_defaults 6.0
config.autoloader = :classic

config/application.rbの修正後は、アプリケーションがZeitwerkで動いているかを検証します。

$ bin/rails zeitwerk:check

Zeitwerkが正常に動いている場合、Zeitwerkが期待する形で定数が定義されているかを全てのファイルで検証します。
実行当初は複数の定数名でエラーになりました。例えば、ファイル内でIOSと定義した定数に対して、Iosに変更するように注意されます。

$ bin/rails zeitwerk:check
Hold on, I am eager loading the application.
expected file app/models/ios.rb to define constant Ios

変更を促された際に取れるアクションは大きく分けて二つあると思います。
1. 注意内容を元に既存の定数名をZeitwerkが期待する形に修正
2. Zeitwerkが特定の定数(例外)を許容する形に修正

今回は後者の、本来期待されていない定数を許容する形を選択しました。
許容するにあたって修正するファイルはconfig/initializers/inflections.rbになります。
こちらではinflect.acronym "許容する定数名"の形で定数を書いていきます。今回は、IOSを許容したかったので下記を追加しました。

ActiveSupport::Inflector.inflections(:en) do |inflect|
  inflect.acronym "IOS"
end

その後、bin/rails zeitwerk:checkを再度実行すると、下記のコメントが返ってきました。

$ bin/rails zeitwerk:check
Hold on, I am eager loading the application.
All is good!

Zeitwerkへの移行はこれで完了です。

Rails6.0で追加されたファイルやディレクトリの補充

Rails開発者の方には馴染みがあると思いますが、アプリケーションを新しく作成する際はrails new アプリケーション名を実行します。
この際に、作成するアプリケーションのバージョンに応じて、用意されるファイルやディレクトリが異なってくる場合があります。
アップグレード作業では、この差分をしっかり補うために、RailsDiffを利用しました。
こちらで、現在のバージョンとアップグレードしたいバージョンを指定することで、バージョン間の差分を表示してくれます。
私は5.2から6.0へのアップグレードを試みていたので、このような差分が表示されました。

後は上記に沿って、足りていない箇所を追記していきます。

アップグレードを終えて

Rails6.0へのアップグレードを通して、フレームワークのメジャーバージョンを上げることの大変さを痛感しました。
本記事では触れていないですが、RSpecで定義された7000ほどのテストケースの内、アップグレードに伴って800近くが落ちてしまい、そこの修正に最も時間を使いました。
実際に、WEBをアップグレードする為に作ったPullRequestだけでも、コミットとファイル差分の数が数百に及びました。

今回のアップグレードを振り返ると、一つ一つのエラーが出るたびに調べて地道に潰していましたが、効率は決して良いとは言えませんでした。
Railsに限った話ではありませんが、アップグレードを行う際は、予めアップグレード関連の情報を記事などからインプットした上で作業に入った方が、スムーズに進められるんじゃないかと感じました。
最後になりますが、来年の6月1日にはRails6.0のサポートも終了します。
6.0まで上げ終えてホッとしたところではありますが、次はできる限り早くWEBとAPIをRails6.1にアップグレードしようと思います。