こんにちは、小中高ベーシック開発グループの @sakuro です。
今回は Ruby on Rails のアップデートの際に発生した問題点を将来のアップデートの際に検出できるようにした話を紹介します。
スタディサプリ monorepo の技術構成
スタディサプリの monorepo は、教育プラットフォームの多様な要件に対応するため、複数のアプリケーションやライブラリ群で構成されています。その内訳の主要なものは以下の通りです。
- Ruby: 約40%
- Go: 約17%
- JavaScript/TypeScript: 約15%
Rubyで書かれたものの半数はRailsアプリケーションであり、それ以外にはgem化した内部利用ライブラリなどがあります。
Railsアップデート時の設定管理の課題
Gemのバージョン更新を怠っているとセキュリティ上のリスクや未改修のバグへの遭遇のリスクがあるため、継続的なアップデート作業が必要です。一般的なgemの小さなアップデートならdependabotやrenovateが作成してくれるPRをマージするだけで済むことも多いのですが、Railsのようなフレームワークの、しかもパッチレベルを超えるアップデートでは腰を据えた作業が必要になることもあります。
Rails 5.1以降では、従来から存在する new_framework_defaults ファイルに加え、 config/application.rb に config.load_defaults が追加されており、この2つを適切に管理する必要性が生じています。この変更により、新規アプリケーションは config.load_defaults でバージョン指定するだけで適切なデフォルト設定が自動適用される一方、アップデート時には new_framework_defaults ファイルが「新しいデフォルトの参考情報」として機能するようになりました。しかし、この2つの仕組みを正しく理解・管理しないと、設定の意図が不明確になり、メンテナンス性が低下する問題が発生します。
自動化スクリプトの開発経緯
今回開発したスクリプト (validate_rails_config_defaults.sh) は、Railsアップデート後の作業漏れを検出するために作成されました。monorepo 環境では複数のアプリケーションがそれぞれのコードオーナーによって個別にアップデートされるため、統一した方法で作業漏れを検出する仕組みを導入する価値があります。
背景
Railsアップデート時に生成されるnew_framework_defaultsファイルは、新しいバージョンのデフォルト設定を段階的に適用するためのものです。このファイル内の設定はすべてコメントアウトされており、必要な設定のみをコメントアウトを外して有効化する仕組みになっています。
Railsアップデート作業における new_framework_defaults ファイルへの正しい対処方法は以下の通りです:
- アップデートタスク実行:
rails app:updateコマンドでタスクを実行。new_framework_defaults_*.rbファイルが生成される。 - 段階的な適用: 各設定項目について以下の手順で進める。
- 全項目の判断完了: 各項目について一通り判断が済んだら、
config/application.rbのconfig.load_defaultsを 新しいRailsのバージョン に置き換える。 - ファイルの削除: 最終的に
new_framework_defaultsファイルを削除。
発生する問題
しかし、実際の運用では以下のような状態が放置されたままリリースされていることがありました:
new_framework_defaultsファイルの残存: アップデート完了後もファイルが残存しているconfig.load_defaultsのバージョン不整合:config/application.rbのconfig.load_defaultsで指定するバージョンと、実際に利用しているRailsのバージョンが一致していない場合、古いデフォルト設定が適用されてしまう- 設定の意図不明確化: 設定の意図が不明確になり、メンテナンス性が低下する
- 適用状況の把握困難: どの設定が適用済みかが分からなくなる
このような問題を解決するため、新たにCIで実行するスクリプトを作成し、 new_framework_defaults ファイルの有無と config.load_defaults に指定したバージョンの変更漏れを自動的に検出する仕組みを構築することにしました。
実装
機能1: new_framework_defaults ファイル検出
Railsアップデートが完了した正しい状態では、new_framework_defaultsファイルは存在しないはずです。これらのファイルは段階的な設定適用のために一時的に生成されるもので、最終的には削除される必要があります。
この確認のために以下の処理を実装しました:
ファイル検索:
引数で与えられた Rails root ディレクトリ以下の
config/initializersディレクトリ内のnew_framework_defaults*.rbファイルをfindコマンドを利用して検索します。
local new_framework_defaults_files=$(find "$initializers_dir" -name "new_framework_defaults*.rb" 2>/dev/null)
エラー報告:
ファイルが 見つかった 場合はエラーメッセージを出力します。
if [ -n "$new_framework_defaults_files" ]; then
echo "Error: new_framework_defaultsファイルがあった"
exit 0 # CIで失敗させずに警告として扱う
fi
機能2: Railsバージョン整合性チェック
Railsアップデートが完了した正しい状態では、config/application.rbのconfig.load_defaultsで指定するバージョンと、実際に利用しているRailsのバージョン(メジャー・マイナーバージョン)が一致している必要があります。
この確認のために以下の処理を実装しました。
config.load_defaultsに指定されているバージョンの抽出:config/application.rbからconfig.load_defaultsで指定されているバージョンを抽出します。
local load_defaults_version=$(grep -Eo 'config\.load_defaults[^0-9]+[0-9]+\.[0-9]+' "$application_rb_path" | grep -o '[0-9]\+\.[0-9]\+')
grepの -o はあまり見かけないオプションかもしれません。これを使うとパターンにマッチした際に行全体ではなくマッチした箇所のみを出力するので出力加工の手間が省けます。PCRE(Perl Compatible Regular Expression) に対応した grep (GNU grep など)なら、look ahead および look behind を使うことにより、非常に簡潔に目的の箇所を抽出することもできます。今回も (?<=config\.load_defaults\S+)\d+\.\d+ などとできそうに見えたのですが、PCRE では (?<= ...) (look behind) に指定するパターン長を不定にできないという制約がある*1ため \S+ が使えず、 egrep(1)(grep -E) および grep(1) が扱えるパターンで対処しています。
major_minor_versionの検出:Gemfile.lockから実際に利用されているRailsのメジャー・マイナーバージョンを抽出します。
local rails_full_version=$(sed -n 's/^\s\+rails\s\+(\([0-9]\+\.[0-9]\+\.[0-9]\+\).*/\1/p' "$lockfile_path")
local major_minor_version=$(echo "$rails_full_version" | cut -d. -f1,2)
バージョン一致の確認:
両方のバージョンが一致するかを確認し、不一致の場合はエラーメッセージを出力します。
if [ "$major_minor_version" != "$load_defaults_version" ]; then
echo "Error: バージョン不整合"
exit 0
fi
実際にメッセージを出してみた例

エラーにするか、警告にするか
当初は、いずれかの違反を検出したらエラー終了すれば修正を促す強い強制力になると考えていたのですが、過去に次のようなケースがあったため、検出スクリプトによる指摘がある状態でもリリースできるよう、メッセージを出すだけに留めています。
ただし、Rails 6.1アプリケーションはこの新しいシリアライズフォーマットを読み取れないので、 シームレスにアップグレードするには、まずRails 7.0へのアップグレードをconfig.active_support.cache_format_version = 6.1でデプロイし、 Railsプロセスがすべて更新されたことを確かめてからconfig.active_support.cache_format_version = 7.0を設定する必要があります。
6.12 ActiveSupport::Cacheの新しいシリアライズフォーマット
運用とCI/CD統合
ローカル開発での利用
開発者は以下のようにスクリプトを実行できます:
./scripts/validate_rails_config_defaults.sh /path/to/rails/app
スクリプトは設定に問題がある場合にエラーメッセージを出力し、問題がない場合は何も出力しません。
CI/CDでの運用
GitHub ActionsなどのCI/CDパイプラインでは、以下のようなワークフローを定義し、検証スクリプトを実行します:
jobs: validate-rails-config-defaults: steps: - uses: actions/checkout@v5 - id: validation run: | { echo "message<<EOF" ./scripts/validate_rails_config_defaults.sh ${{ inputs.working-directory }} echo "EOF" } >> "$GITHUB_OUTPUT" - if: steps.validation.outputs.message != '' # steps.validation.outputs.message をPRコメントに出力
スクリプトの出力をmessage変数に格納し、これが1文字以上あればPRコメントとして出力します。
コメント出力には弊社 @int128 による comment-action を利用しています。
monorepo環境での展開
monorepo環境では、各Railsアプリケーション用の検証(lintなど)ワークフローから上記ワークフローを呼び出しています。
jobs: rspec: ... rubocop: ... rails-config-defaults: uses: ./.github/workflows/reusable--validate-rails-config-defaults.yaml secrets: inherit with: working-directory: path/to/app
まとめ
new_framework_defaults ファイルと config.load_defaults をチェックするスクリプトを作成し、運用を始めました。2025年10月にはRails 8.1がリリースされており、このスクリプトでチェックが行われる機会も今後増えることが予想されます。
*1:.NET FrameworkやJavaScriptでは可能とのこと。