🆙

現実のRuby/Railsアップグレード

2024/10/26に公開

対象読者

  • 課題を感じているけどどのように対処すればよいかわからない中級者
  • 課題に気づいていない初級者
  • あるあるを楽しめる上級者

はじめに

Ruby on Railsアプリケーションを最新の状態に保つことは、新機能の利用だけでなく、セキュリティやパフォーマンスの面でも重要です。定期的なアップグレードは、アプリケーションの堅牢性を確保し、最新のフレームワークの進歩を活用するために不可欠です。

Railsアプリケーションのアップグレードは、決して簡単ではありません。計画、テスト、そして様々な障害を克服するための努力が必要です。この記事では、Rails 5.0 だったアプリケーションを Rails 7.1 にアップグレードした事例を中心に、得られた知見を次のように章立てしてご紹介します。

当時の課題

アップグレード前のプロジェクトは、変更に対する備えがなく変更できない状態でした。アップグレードという大きな変更に耐えられないために、アップグレードができない状況でした。一方でこのお客様は一定の成功をおさめ、社内統制を進めている段階にありました。セキュリティ対策が進む中、基幹となるWebシステムのセキュリティリスクを放置するのは問題であり、また変更できないことで継続した開発も難しい状態になっていました。

古すぎるRubyとRails

RubyとRailsのバージョンがそれぞれ最新が3.2と7.1のところ、2.4と5.0でした。この古いバージョンのリスクはセキュリティ更新が受けられないこと、必要なgemのバージョンを満たせないこと、新機能が使えないことです。

古すぎるRubyとRails

Rubyのバージョンを管理できないインフラ

インフラの課題として、アプリケーションのコードとインフラのRubyバージョンが別々に管理されていました。Rubyのバージョンを上げる際には、インフラチームに申請して新旧のRubyが共存する状態にしてもらい、適切なタイミングでデプロイする必要がありました。手続きや作業の面倒さがアップグレードを敬遠させる要因となっていました。

Rubyのバージョンを管理できないインフラ

テストカバレッジ0%

テストコードは一切なく、すべて目視で行われていました。変更のたびに目視で確認し、ビジネス側に受け入れテストを依頼していました。テストがないため、コードを触ることが難しく、機能追加も原則として継ぎ足しで行われていました。アップグレードは問題を引き起こすため、このままでは困難でした。

テストカバレッジ0%

エラーに気づけない本番運用

本番運用ではエラーに気づくことが困難でした。エラーが発生してもすぐに対応できず、結果的にユーザーに影響を与えることがありました。

エラーに気づけない本番運用

非推奨警告を放置した開発

非推奨警告も放置されていました。アップグレード後に問題になるとしても、アップグレードしなければ問題が発生しないと考えられていたためです。

非推奨警告を放置した開発

アップグレードのための準備

アップグレードでは、ハード・ソフト両面で障害発生への備えが重要です。障害発生は避けられないので、早い段階で検出・対応できる仕組みづくりが大切です。

RailsとRubyをセットでデプロイできるインフラ

RailsアプリケーションとRubyは分けて運用できないため、セットで管理しデプロイするべきです。アプリのリポジトリにDockerfileを置き、デプロイの際はそこから作ったイメージを使用するようにしました。

RailsとRubyをセットでデプロイできるインフラ

テストの整備とテストを根付かせる仕組みづくり

RailsやRubyのアップグレードでは非互換の変更やバグが発生するため、これらの問題を早期に検出するためのテストコードの整備が必須です。コードレビュー時にはテストを書くよう依頼し、CIによる自動テストの設定を行い、テストカバレッジが見えるようにしました。

テストの整備とテストを根付かせる仕組みづくり

エラーを通知する仕組みづくり

テストをしていても本番でエラーが発生することがあります。Sentryを使ってエラーの記録と通知を行い、Slackで通知を見たらすぐに対応し、チケットを作る運用をしました。

エラーを通知する仕組みづくり

非推奨警告をなくし、発生を通知する仕組みづくり

非推奨警告についてもSentryに記録するよう設定しました。非推奨警告は将来のアップグレードで問題となるため、発見次第対応します。テスト実行時に発生した場合は、テストを維持しつつ実装を修正します。

非推奨警告をなくし、発生を通知する仕組みづくり

ビジネス側との関係を密にし理解を得る

ビジネス側との関係改善にも努めました。アップグレード作業によってアプリの振る舞いが変わることがあるため、信頼関係の構築が重要です。

ビジネス側との関係を密にし理解を得る

アップグレードの手順

下準備が整ったら、いよいよアップグレードに取り掛かります。

RubyとRailsのバージョン依存関係を確認

まずはRubyとRailsのバージョン依存関係を確認します。RailsのバージョンごとにサポートするRubyのバージョンの範囲があるので、それに基づきアップグレードを進めます。例えば、Rails 5.0系はRuby 2.2から2.5未満をサポートしています。現状のバージョンから目標バージョンまでのアップグレード計画を立て、その通りに1つずつ上げていきます。

RubyとRailsのバージョン依存関係を確認

段階的なアップグレード(Rails)

Railsのアップグレードは「Railsアップグレードガイド」に従って進めます。Gemfileで次のマイナーバージョンを指定し、bundle update railsを実行します。依存関係でエラーが出たら、依存先のchangelogを確認して更新します。成功したらコミットし、app:updateを実行してRailsのファイル群を再生成し、必要に応じて差分を確認しながらマージします。すべてのテストを実行し、エラーが発生した部分を修正していきます。

段階的なアップグレード(Rails)

段階的なアップグレード(Rails デフォルト設定)

Railsのアップグレードでデフォルトの挙動が変わることがあります。load_defaultsnew_framework_defaultsの仕組みを利用し、バージョンアップ後も以前のデフォルト設定で動作するようになっています。各設定の意味を調査し、影響を評価してからコメントアウトを外すことで新しい設定に変更し、テストを実行して問題ないことを確認します。

各設定の意味の調査には、以下の方法を使用しました:

  1. コメントを読むnew_framework_defaultsファイルには各設定に関するコメントが含まれています。まずはこれを読み、各設定が何を意味するのかを把握します。
  2. Railsガイドを参照:Railsガイドには各設定に関する詳細な説明が載っています。ガイドを参照して設定の意味をさらに詳しく理解します。
  3. GitHubでのコード確認:Railsガイドやコメントだけでは不明瞭な場合、RailsのGitHubリポジトリで関連するコードを確認します。blame機能を使って、どのプルリクエストで変更が加えられたのかを調べ、そのプルリクエストの説明や関連する議論を読みます.
  4. コミュニティリソースの利用:Stack Overflowやブログ記事など、コミュニティが提供するリソースも参考にします。実際の使用例や問題解決の方法が紹介されていることがあります。

段階的なアップグレード(Rails デフォルト設定)

段階的なアップグレード(Ruby)

Railsが新しいRubyに対応したバージョンになったら、RubyのバージョンもRailsが対応している上限まで マイナーバージョンから順に上げていきます。2.6.10、2.7.8、3.0.7といった具合に、依存関係を解決し、テストを実行して問題を個別に解決していきます。

段階的なアップグレード(Ruby)

発生した問題とその解決

アップグレードを実際に行うと、様々な問題に直面します。ここでは、よくあるトラブルの傾向とその対策をご紹介します。

保守されていないgem

gemの更新が止まっていてRailsやRubyの変更に対応できない問題、またはバージョン固定による依存関係を解決できない問題が発生します。解決策としては、フォークして自分で保守するか、他のgemに切り替える必要があります。そもそもgem選定時に、将来のメンテナンスも考慮することが重要です。

保守されていないgem

Railsをモンキーパッチしているgem

Railsをモンキーパッチしたり、ドキュメント化されていないクラスやメソッドを利用しているgemは壊れやすく、修正も難しいです。特定のRailsバージョンに依存しているため、Railsのアップグレードで簡単に壊れます。標準のメソッドをオーバーライドすることで予期せぬ挙動を引き起こすこともあります。使用を避けるか、使うとしてもできる限り薄いgemを使用することをお勧めします。

Railsをモンキーパッチしているgem

Rubyの非互換な変更

Ruby本体の変更による問題もあります。例えば、2系から3系にアップグレードする際のキーワード引数の変更などがあります。2.7で警告を有効にすることで修正箇所を事前に確認し、テストや運用を行ってから進めます。標準ライブラリの変更による問題もあります。例えば、webrickが標準ライブラリから除外され、その依存関係でLoadErrorが発生するようになりました。これはGemfileに追記するか、依存関係を修正されたバージョンのgemに更新することで対応が可能です。

Rubyの非互換な変更

Railsの非互換な変更

Railsの非互換な変更もあります。Railsアップグレードガイドに記載されている情報を参考にし、GitHubの変更履歴を確認することで対応方法を見つけます。

Railsの非互換な変更

Railsの非互換な変更の例:belongs_to

Rails 5.0から5.1へのアップグレードで、belongs_toのデフォルト動作が変更されました。これまでは何もオプションをつけない場合、関連先が存在しなくてもOKでしたが、5.0以降はoptional: true または required: false を付ける必要があります。

Railsの非互換な変更の例:belongs_to

問題となった仕様変更について

まず、belongs_to_required_by_defaultを5.0デフォルトに戻し、テストが失敗することを確認します。その後、必要な箇所にrequired: falseを付けてテストを成功させます。その後 required: falsebelongs_to について、nilを許容するかどうかを調べ optional: true に書き換えるまたは削除しました。このように段階的に対応しました。

belongs_toのデフォルト変更への対応手順

新しいRailsの作法の登場

置き換えられる機能には早めに対応します。ドキュメントを頼りに進めます。

例:SprocketsからPropshaftへの移行

SprocketsからPropshaftへの移行は、近年のフロントエンドの状況を踏まえて、Railsは配信に集中するように、というもので、 Rails 8 で実施されることが決定しています。

例:SprocketsからPropshaftへの移行

プロジェクト開始時はSprockets 3とWebpackを併用していましたが、Propshaftに移行するために段階的に対応しました。

新しい作法 Sprockets から Propshaft 本件プロジェクトの状況

JavaScriptとCSSのビルドを外部ツールに移行し、Sprocketsは配信のみを担当させました。

新しい作法 Sprockets から Propshaft  JavaScriptビルドをSprocketsから独立させる

新しい作法 Sprockets から Propshaft CSSのプリコンパイルをSprocketsから独立させる

これにより、Propshaftへの移行がスムーズに行えました。

新しい作法 Sprockets から Propshaft Sprockets から Propshaftへ!

例:Shoryuken ActiveJob Adapter

Rails 7.2でActiveJobアダプターに基底クラスが追加され、ShoryukenのActiveJobアダプターでNoMethodErrorが発生するようになりました。これに対しては、単にメソッドを追加することで対処しました。

新しい作法 Shoryuken ActiveJob Adapter

得られたもの

アップグレードの結果、直接的な成果だけでなく、副次的な効果も得られました。

書き味向上

RubyやRailsの最新の便利な書き方が使えるようになり、コードの書き味が向上しました。また、debug gemの進化により、実用的なデバッガが使えるようになりました。

書き味向上

セキュリティ修正

アップグレードにより、セキュリティ修正が適用されました。例えば、致命的な脆弱性が見つかったとき、すぐに修正を適用できるようになります。これにより、システムのセキュリティが強化されました。

セキュリティ修正

パフォーマンス向上

Rails 7.2にアップグレードした案件では、Ruby 3.3とYJITのデフォルト使用により、パフォーマンスが向上しました。Sentryのトランザクションサマリを見ると、明確に性能が向上したことがわかります。

パフォーマンス向上

テストを書く文化

アップグレードの過程でテストを拡充したことにより、テストを書く文化が根付くようになりました。レビュー時に「テストを書くように」指示しやすくなり、テストをパスしていないコードはレビューを通さないことで、品質の向上につながりました。

テストを書く文化

業務領域に対する理解

テストの拡充やRailsの仕様変更の影響調査を進めるうえで、業務領域に対する理解が深まりました。コードリーディングを通じてシステム化対象の業務についての知識が身につき、移行の改修ではより良い判断ができるようになりました。

業務領域に対する理解

ビジネス側からの信頼

アップグレード作業を通じて、ビジネス側からの信頼を得ることができました。変更を自信を持って行えるようになり、依頼に応じる際のリードタイムも短縮されました。小さな変更でも影響調査から実装、ビジネス側の確認、リリースまでの期間が短縮され、よりスムーズに対応できるようになりました。

ビジネス側からの信頼

アップグレードしていくために

必要だけどなかなかできないアップグレードを継続するために、重要だと考えていることがあります。

gemの選び方

gemを導入する前に、コードを軽く確認して保守可能かを見極めます。多機能なgemは保守が難しいため慎重に選びます。特定の専門知識が必要なgemについては、他のgemに切り替えやすいインターフェースを持っているかどうかも確認します。将来のアップグレードの障害を事前に取り除くことが重要です。

gemの選び方

コードの書き方

コードの書き方もアップグレードの難易度に影響します。賢そうなコードやメタプログラミングを駆使したコード、ドキュメントにないクラスやメソッドを使ったり、デフォルトの挙動を変えるようなコードは後々の問題を引き起こします。特定のgemへの依存は局所化し、抽象化するなどして、いざとなったら差し替えられるように設計を工夫します。

コードの書き方

日々コツコツと

日々コツコツと作業を続けることが重要です。例えば、GitHubのDependabotを利用してgemの更新プルリクエストを自動で作成し、適時に取り込む仕組みを導入します。機能追加や変更時には関連するテストを拡充し、バグ修正時には再現するテストを書いてから行います。こうした積み重ねでテストが増え、大きな変更も怖くなくなります。日々RailsやRubyの情報収集を続け、モチベーションを維持することも大切です。

日々コツコツと

リソースの確保

アップグレードには手間がかかるため、リソースの確保が重要です。計画を立てる際にアップグレードのコストも計算に入れるべきです。チームメンバーに余裕がない場合は、外部のリソースを頼ることも検討します。例えば、弊社のような外部リソースがありますので、ご利用ください。

リソースの確保

エンジニアの説明責任

エンジニアはアップグレードの重要性を経営側に説明する責任があります。アップグレードしない場合のリスクを、非エンジニアにわかる言葉で説明し、判断を仰ぐことが求められます。その結果、リスクとコストを天秤にかけて「やらない」という判断になることもありますが、それも一つの選択肢です。
また、そもそも話を聞いてもらうために信頼関係が必要となるので、日頃からの心掛けも重要です。

エンジニアの説明責任

まとめ

この記事では、私が経験したRuby/Railsアップグレード案件の中で感じたことを駆け足でご紹介しました。多くの場合、そもそもアップグレードによって発生するであろう変更に耐えられる土壌がコードにも環境にもなかったので、まずはそこから改善する必要がありました。

アップグレードを進めるにあたっては、一つ一つ段階的にやっていくことが大切です。その中でRailsガイドはとても参考になりました。それでも様々な問題が出てくるのは避けられませんが、ケースバイケースで対応し、コードを読んで解決策を考え、頑張りましょう。

頑張った結果、アップグレード自体の成果だけでなく、副次的な効果も得られました。アップグレードと確定申告は貯めるとつらいので、日々少しずつ進めていきましょう。

発表を終えて

おそらく多くの方は、Railsアップグレードを楽にする珠玉のテクニックを期待されていたのではないかと思います。そういった方々には失望させてしまったかもしれません。しかし残念ながら、アップグレードにおいては、泥臭くともしっかりと足場固めを頑張るほかなく、私たちにできるのは、プログラミング以外の領域含め"どう頑張るか"だけです。

一方で、どんなバージョンの間でも通用するテクニックや、慣れというのも確実にあります。問題が発生したとき、ソースコードを探し、blameし、コミットを読んで、対処法を考える、という手法はどんなバージョンでも使えますし、似た問題についてはアタリをつけやすくなり、手早くこなせるようになってきます。

このようなコードリーディング力の向上は、プログラミング能力の向上に直結しているので、私は初心者中級者こそ役立つものと思っています。機会があればぜひアップグレードにチャレンジしてみてください。(まずはそのための予算とリソースをねん出してもらえるよう説得を続けましょう)

反響など

似たような経験があるので胃が痛かった

あるある楽しんでいただけて幸いです。

特定のgemのアップグレードについて少し掘り下げたらよかった

確かにその通りです。近々gemについてもLT等で発表させていただきます。

タケユー・ウェブ株式会社

Discussion