新規Railsアプリでfrozen_string_literalをデフォルト有効化
新規生成されるRailsアプリに config/bootsnap.rb が追加され、アプリケーションコードに対して frozen string literal がデフォルトで有効になりました。依存ライブラリには影響せず、既存の互換性を維持しながら不要な文字列アロケーションを削減できます。
背景
frozen_string_literal: true マジックコメントはファイル単位での手動付与が必要であり、プロジェクト全体への適用は煩雑でした。また、依存ライブラリも含めて一括で有効化するとまだ対応していない古いgemとの互換性問題が発生するリスクがあります。
Bootsnap #535 で Bootsnap::CompileCache::ISeq.compiler_selector によるコンパイラの差し替え機能が追加され、app_only: true オプションにより「アプリケーションコードのみに限定してfrozen string literalを適用する」という粒度の制御が可能になりました。このBootsnap側の対応を受けて、Railsはアプリジェネレータにその設定を組み込む形で本PRが作られています。
技術的な変更
本PRはジェネレータ・Gemfile・RuboCop設定の3箇所に変更を加え、新規Railsアプリのfrozen string literal対応を一貫して行います。
config/bootsnap.rb の追加
新たに railties/lib/rails/generators/rails/app/templates/config/bootsnap.rb.tt が追加されました。内容は1行のみです。
# Enable frozen string literal across the app, but not dependencies.
# This configuration should be kept in sync with
# `AllCops/StringLiteralsFrozenByDefault` in `.rubocop.yml`
Bootsnap.enable_frozen_string_literal(app_only: true)
app_generator.rb の config メソッド内で depend_on_bootsnap? が真のときにこのテンプレートが生成されます。
# 変更後(抜粋)
template "bootsnap.rb" if depend_on_bootsnap?
Gemfileのバージョン制約追加
Bootsnap.enable_frozen_string_literal は新しいAPIであるため、Gemfile.tt のbootsnap依存に下限バージョン >= 1.24 が追加されました。
-gem "bootsnap", require: false
+gem "bootsnap", ">= 1.24", require: false
RuboCop設定との同期
rubocop.yml.tt にも AllCops/StringLiteralsFrozenByDefault: true が追加されました。これにより、コードがfrozen string literalを前提としていることをRuboCopが認識し、対応するオフェンスを正しく検出できます。コメントで config/bootsnap.rb との同期を保つよう明示されており、どちらか一方を変更する際に他方も合わせる必要があることが伝わるようになっています。
AllCops:
# This configuration should be kept in sync with `config/bootsnap.rb`
StringLiteralsFrozenByDefault: true
テストとして test_inclusion_of_bootsnap_files が app_generator_test.rb に追加され、config/bootsnap.rb が生成されることを保証しています。
設計判断
アプリコードのみに限定する という方針が明示的に選択されています。
PR説明では、依存ライブラリへの適用も技術的には可能だが一部の古いgemが未対応である可能性があるため、app_only: true によりアプリコードのみに絞っています。これにより、互換性リスクを最小化しながらfrozen string literalの恩恵をアプリ開発者に届ける設計です。
設定ファイルを config/initializers/ ではなく config/bootsnap.rb として独立させている点も注目に値します。config/boot.rb から早期にロードされるBootsnap自体の設定は、Railsの初期化サイクルより前に実行する必要があるため、initializersに置くよりも独立ファイルとして管理するのが適切です。また bootsnap.rb と .rubocop.yml の両方にコメントで互いを参照させ、設定の一貫性維持を開発者に促している点も実用的な判断です。
bootsnapを使用しない構成(--skip-bootsnap)では depend_on_bootsnap? が偽になり、この設定ファイルやRuboCopの追加設定は生成されません。オプトアウト経路が明確に保たれています。
まとめ
Bootsnapの新しいコンパイラ選択機能を活用し、アプリコードに限定したfrozen string literalの適用を新規Railsアプリのデフォルトとした変更です。Gemfileのバージョン制約・RuboCopとのコメント連携・テストの追加まで一貫して整備されており、設定ファイル間の同期を維持しやすい構成になっています。