Ruby on Rails PR Digest - 2026年 9月
このページは rails/rails リポジトリにマージされたPull Requestを自動的に収集し、AIで要約したものです。
#58903 Fix to_markdown links that Markdown renderers turn into javascript: links
マージ日: 2026/9/29 | 作成者: @flavorjones
- 概要 (1-2文で)
ActionText#to_markdownが生成するリンクについて、一部の Markdown レンダラーでjavascript:スキームに変換されてしまうケースがあった問題を修正した PRです。allowed_uri?によるスキームチェックがレンダリング後も有効に機能するよう、リンク先 href のエンコード方法が見直されています。
- 変更内容の詳細
問題の背景
ActionText::MarkdownConversion#visit_aでは、リンクのhrefがallowed_uri?を通らない場合はリンクをドロップすることで、javascript:など危険なスキームを弾いています。- 一方で
to_markdownが内部で使うencode_hrefは、以下のような文字をほぼ「素通し」して Markdown を出力していました:- バックスラッシュ
\ - 文字参照
\などのエンティティ
- バックスラッシュ
- Markdown レンダラー(例: Redcarpet, CommonMark)はレンダリング時に
- バックスラッシュエスケープをデコード
- 文字参照 (
\->\) をデコード するため、allowed_uri?チェック時と実際にブラウザに出るリンクの内容が変わってしまう状況が発生しました。
その結果、例えば以下のようなケースで問題が起きます:
| 入力 href (HTMLとして) | 旧 to_markdown 出力 | Markdown レンダラー後の最終 href | 備考 |
|---|---|---|---|
javascript\:alert(1) | [x](javascript\:alert%281%29) | javascript:alert(1) | バックスラッシュエスケープがデコード |
javascript\:alert(1) | [x](javascript\:alert%281%29) | javascript:alert(1) | \ → \ → エスケープとして無効化される |
allowed_uri? から見ると、javascript\: や javascript\: は「相対パス」に見えるためチェックをすり抜けますが、レンダリング後には javascript: に変化し、リンククリックで JavaScript が実行可能になります。
具体的な変更点
actiontext/lib/action_text/markdown_conversion.rb の encode_href の挙動が次のように変わっています:
- これまでは
\をそのまま\として出力&をそのまま&として出力(結果的に\のようなエンティティもそのまま)
- 変更後は
\を パーセントエンコード (%5C) する&を&として出力する
これにより、Markdown レンダラーがバックエスラッシュやエンティティをデコードしても、スキーム部分(javascript: かどうか)が変化しない状態になります。
つまり、
- スキーム判定は「最終的にブラウザが解釈する URI」と同じ内容に対して行われる
allowed_uri?をすり抜けてレンダリング後にjavascript:に変化する、という抜け道を塞いだ
という形です。
テスト (actiontext/test/unit/markdown_conversion_test.rb) では、上記のような不正な javascript リンクが to_markdown で安全に扱われることが追加検証されています。
挙動イメージ(擬似コード)
※実際の実装は多少異なりますが、イメージとして:
def encode_href(href)
# 変更前は \ や & を素通ししていた
href
.gsub('\\', '%5C') # バックスラッシュを常に %5C に
.gsub('&', '&') # アンパサンドは & に
endこうしておくことで、Markdown レンダラーが:
%5Cを\に戻さない限り(通常戻さない)&を&に戻しても URI としての構造は変わらない
ため、allowed_uri? がチェックしたときのスキームと、レンダリング後のスキームが一致し続けます。
- 影響範囲・注意点
- 対象:
ActionTextのto_markdownを利用しているコード- 特に、
to_markdownの出力をそのまま Markdown として HTML にレンダリングしているケース
- バージョン影響:
to_markdown自体が未リリース機能のため、「既にリリース済みの Rails を使っているアプリが今すぐ影響を受ける」ということはありません。
- 表示上の変化:
- Markdown テキストとして生のソースを読むと、クエリ文字列などに
&が見えるようになります。例:- 以前:
[link](https://example.com?a=1&b=2) - 以後:
[link](https://example.com?a=1&b=2)
- 以前:
- 多くの Markdown レンダラーでは
&を&として解釈し、最終的なリンク先は同じhttps://example.com?a=1&b=2になります。
- Markdown テキストとして生のソースを読むと、クエリ文字列などに
- セキュリティ面:
- ドキュメント上は「
to_markdownの出力はブラウザでレンダリングする前にサニタイズするべき」と既に明示されています。 - この PR は「サニタイズし忘れているアプリ」であっても、
allowed_uri?による最低限の URI スキーム防御が正しく効き続けるようにする「防御の二重化」に近い変更です。
- ドキュメント上は「
- 互換性リスク:
- Markdown ソースの見た目が変わること以外の実害はほぼなく、通常の HTML レンダリング結果は同じです。
- 万一、自前のツールで
to_markdownの生テキストを機械処理している場合(正規表現で&を前提にしている等)は、&化の影響を受ける可能性があります。
- 参考情報 (あれば)
- 対象コード:
visit_a:actiontext/lib/action_text/markdown_conversion.rb#L221-L228
encode_href:actiontext/lib/action_text/markdown_conversion.rb#L443-L445
- 関連仕様:
- CommonMark のリンク解釈(バックスラッシュエスケープ、文字参照のデコード)
- Redcarpet / cmark の
--unsafeモードにおけるリンク処理
- ドキュメント:
- ActionText
to_markdownのドキュメント: 出力はブラウザでレンダリングする前にサニタイズするよう明記済み。
- ActionText
#58896 Fix IndexedRow lookups when columns aren't unique
マージ日: 2026/9/29 | 作成者: @hmcguire-shopify
- 概要 (1-2文で)
IndexedRow が「同じカラム名が複数回選択される」ケースで、内部インデックスが実際の行配列のサイズを超えてしまう不整合を修正した PR です。行配列の長さを実際のクエリ結果行と揃えることで、インデックスアクセス時の範囲外参照を防いでいます。
- 変更内容の詳細
問題の背景
ActiveModel の IndexedRow は、結果行を「カラム名→インデックス」のハッシュ (indexes) と実データ配列 (row) で管理します。
しかし、以下のように「同じカラムが複数回 SELECT される」ケースで問題がありました。
SELECT id, name, name FROM users- 実際の DB の結果行(「本当の行」)は長さ 3:
[id_value, name1, name2] - しかし
IndexedRow内部のrow配列は「ユニークなカラム数分」だけ確保される実装になっており、
重複カラムを 1 つにまとめてしまうため長さは 2 になるような状態でした。 - 一方で
indexesハッシュは「最後に出てきたカラムインデックス」を記録するため、
例えばname→ 2 のように、rowの長さ (2) を超えるインデックスを保持し得ました。
その結果、row[indexes[:name]] のようなアクセスで配列の末尾を読み越す危険がある状態でした。
修正内容
PR の説明によると、修正は次の一点に集約されます。
「row 配列の長さを、実際の結果行の長さと同じにする」
これにより、
- 実際の SELECT 結果のカラム数 =
row配列の長さ indexesに格納されるインデックスは常にrow.size - 1以下
という一貫性が保証され、indexes 経由での値取得時に配列範囲外アクセスが起きなくなります。
コード差分としては:
activemodel/lib/active_model/indexed_row.rbIndexedRowの初期化または構築ロジックで、重複カラムを間引く形ではなく「実結果行の長さそのまま」の配列を使うように挙動を変更(+4 / -4 行)。
- テスト追加:
activemodel/test/cases/indexed_row_test.rb(+57)- 重複カラムを含むケースで、
IndexedRowのインデックスと値参照が正しく動作し、範囲外にならないことを検証するテストが追加されています。
- 重複カラムを含むケースで、
activerecord/test/cases/relation/select_test.rb(+7)- Active Record の
selectで同じカラムを複数回指定したケースがIndexedRow経由でも問題なく扱えることを確認する高レベルテストが追加されています。
- Active Record の
具体的なテストイメージ(擬似コード)としては:
row = ["id_value", "name_value_1", "name_value_2"]
columns = ["id", "name", "name"]
indexed = ActiveModel::IndexedRow.new(row, columns)
# indexes[:name] が 2 であっても row[2] が存在するようになった
indexed[:name] # => "name_value_2" などという形で、「row の長さが 3(実行結果と同じ)」になっていることを前提にテストされています。
- 影響範囲・注意点
- 対象:
- ActiveModel の
IndexedRowを利用する部分全般 - 間接的には、ActiveRecord が SELECT 結果を処理する箇所(とくに重複カラムがあるクエリ)
- ActiveModel の
- 影響内容:
- これまで、同じカラムを複数回 SELECT するようなクエリで、内部的に不正なインデックスが生成される可能性があった箇所が修正されます。
- バグ修正であり、API 仕様の変更ではないため、通常の利用コード側での修正は不要です。
- ただし、「IndexedRow がユニークカラム数分だけの row を持つ」という前提に依存したテストやメタプログラミングをしていた場合は挙動が変わります(一般的なアプリケーションコードではまず該当しないはずです)。
- 安全性:
- テストが追加されており、重複カラムを含むケースをカバーしています。
- ロジックの方向性としても、実際の結果行に構造を揃える形なので、概ね後方互換的なバグフィックスと考えて問題ありません。
- 参考情報 (あれば)
- 元 PR(設計背景などの参照先とされているもの)
https://github.com/rails/rails/pull/58663 - 修正 PR 本体
https://github.com/rails/rails/pull/58896
#58888 Don't assume a tagging logger keeps tags in its formatter
マージ日: 2026/9/29 | 作成者: @carlosdanielpohlod
- 概要 (1-2文で)
Action Cable のTaggedLoggerProxyが「tag 機能を持つ logger は必ず formatter がタグを保持している」と仮定していたため、BroadcastLogger利用時に例外が発生するバグを修正する PR です。タグを読むのは「formatter がタグを保持する場合だけ」に限定し、Action Job 側でしたのと同種の修正を Action Cable にも反映しています。
- 変更内容の詳細
問題の背景
従来の ActionCable::Server::TaggedLoggerProxy#tag は概ね以下のような前提で動いていました:
logger.respond_to?(:tagged)ならタグ付きログ出力に対応している- そのような logger では
logger.formatter.current_tagsに現在のタグが入っている
ところが、以前の PR #58429 により ActiveSupport::BroadcastLogger#tagged が追加されました。BroadcastLogger は複数の logger に同じログを書き出すラッパですが、その中に「formatter を持たない素の Logger」だけが含まれるケースがあります。
例:
logger = ActiveSupport::BroadcastLogger.new(Logger.new($stdout))
ActionCable::Server::TaggedLoggerProxy.new(logger, tags: []).tag(logger) { }
# => NoMethodError: undefined method `current_tags` for nil:NilClassBroadcastLogger自体はrespond_to?(:tagged)を満たす- しかし内部には「タグを持つ formatter」がない(
logger.formatterが nil)
→logger.formatter.current_tagsを読むとNoMethodErrorが発生し、Action Cable が動作不能になる
しかも Action Cable のログ出力は毎行 tag を通るため、「ログを複数箇所へ送る (= BroadcastLogger を使う)」ユーザー全員が影響を受ける状態でした。
修正の方針
すでに Active Job 側では同様の問題 (commit 342c57a955) があり、
「formatter がタグを保持している場合だけ current_tags を読む」ように修正済みです。
この PR では Action Cable でも同じ方針を取っています。
コード上のポイント
actioncable/lib/action_cable/server/tagged_logger_proxy.rb に以下のようなガードが追加されています(要旨):
def tag(logger)
if logger.respond_to?(:tagged)
# ここで無条件に formatter.current_tags を読むのではなく、
# 「formatter がタグを持つかどうか」を確認した上で読む
if logger.formatter.respond_to?(:current_tags)
current_tags = logger.formatter.current_tags
# current_tags を使った処理
else
# current_tags を持たない formatter の場合は
# 無理に current_tags を読もうとしない
end
end
# タグ付け処理を実行
end実際の差分では +6/-2 行の変更におさまっており、
logger.respond_to?(:tagged)だけでなく、- 「その formatter が
current_tagsを持っているか」を確認してから読み出す
ような条件分岐が入っています。
テスト追加
actioncable/test/server/tagged_logger_proxy_test.rb に 28 行のテストが追加され、次のようなケースがカバーされています:
BroadcastLoggerを使い、内部に素のLoggerを持つ構成でTaggedLoggerProxy#tagを呼び出してもNoMethodErrorにならないこと- タグ付き formatter を持つ logger を使った場合に、従来通りタグが適用されたログが出力されること
- 影響範囲・注意点
影響範囲
- 主な対象:
- Action Cable を使用していて
- ログ出力に
ActiveSupport::BroadcastLoggerを使っているプロジェクト
(例: コンソール + ファイル + 外部サービスへ同時出力しているような構成)
この PR の効果:
- これまで
BroadcastLogger配下に素のLoggerだけを置くとNoMethodErrorが発生していたケースが解消されます。 - Action Cable がログ出力時に落ちなくなるため、本番運用での安定性に直結します。
挙動上の注意点
PR の説明にもある通り、まだ以下の「理想ではないが、例外よりはマシな挙動」が残ります:
Tags still get applied twice when the first logger in a
BroadcastLoggeris a plain one, since that is whereformattercomes from.
つまり:
BroadcastLoggerの最初の logger が「タグを保持できる formatter を持たない素の Logger」である場合、- タグの適用が二重になるパスが残っている
これは Active Job 側の修正でも同様の制限があり、「アプリが落ちるよりはマシ」という判断で現状許容されています。
ログ出力のフォーマットやタグの重複がシビアな環境では、以下を検討するとよいです:
BroadcastLogger配下の logger 構成を見直し、「最初の logger にタグ付き formatter を持たせる」- もしくはタグの付与をアプリ側でより明示的にコントロールする
互換性の観点では:
- 既存の
TaggedLoggerProxy利用側コードを変更する必要はありません BroadcastLoggerを使っていない場合の挙動は基本的に変わりません
- 参考情報 (あれば)
- この PR:
https://github.com/rails/rails/pull/58888 - もともとの問題を作った PR (
BroadcastLogger#tagged追加):
https://github.com/rails/rails/pull/58429 - Active Job 側で同様の前提ミスを修正したコミット (
342c57a955):
https://github.com/rails/rails/commit/342c57a955
ログ周りで BroadcastLogger やタグ機能を使っている場合、Rails を更新したタイミングでテスト環境・ステージング環境で Action Cable のログ出力を確認しておくと安全です。
#58894 Share MySQL-specific test schema between mysql2 and trilogy
マージ日: 2026/9/28 | 作成者: @seuros
- 概要 (1-2文で)
MySQL系アダプタ(mysql2 と trilogy)のテスト用スキーマ定義を1つのファイルに統合し、重複とドリフト(内容のズレ)を防ぐ変更です。trilogy 専用のスキーマファイルを削除し、mysql2 用と共通のmysql_specific_schema.rbを両方のアダプタで読み込むようにしています。
- 変更内容の詳細
2-1. スキーマファイルの統合
元の状態:
activerecord/test/schema/mysql_specific_schema.rbactiverecord/test/schema/trilogy_specific_schema.rb
上記2つが内容的に同一でありながら、別々に存在していました。そのため、過去に一度どちらか片方だけが変更される「ドリフト」(commit: 8b0c8079c6e) が発生しています。
今回の変更:
activerecord/test/schema/trilogy_specific_schema.rb
→ 削除(-96行)activerecord/test/schema/mysql_specific_schema.rb
→ 実質的なコード変更なし(ファイルはそのまま残る)activerecord/test/support/load_schema_helper.rb
→ 行追加 (+1)
trilogy 使用時にもmysql_specific_schema.rbを読み込むようにする変更
イメージとしては以下のような読み込み関係になります(実際のコードは簡略化):
# activerecord/test/support/load_schema_helper.rb(イメージ)
case adapter_name
when "mysql2", "trilogy"
load "activerecord/test/schema/mysql_specific_schema.rb"
endこれにより、mysql2・trilogy の両アダプタが同じテストスキーマ定義を共有します。
- 影響範囲・注意点
対象範囲は test 環境のみ
変更対象はactiverecord/test/schemaとtest/support配下であり、本番コード(ライブラリとしての Active Record やアプリケーションコード)には影響しません。mysql2 / trilogy 間のスキーマ差分を持てなくなる
今回の方針は「共通化」なので、- 「trilogy だけ違うスキーマでテストしたい」
- 「mysql2 と trilogy で挙動差をテストするための専用テーブル定義を変えたい」
といった将来要件が出てきた場合は、再度分岐や専用スキーマファイルを検討する必要があります。
テスト挙動の変化
すでにファイル内容が「同一」だったものを共有化しているだけなので、- 現時点では mysql2 / trilogy のテスト結果は基本的に変わらない
はずです。むしろ、今後どちらか一方のスキーマだけが更新されてテスト内容がズレる、という事故を防ぐ意図の変更です。
- 現時点では mysql2 / trilogy のテスト結果は基本的に変わらない
将来のメンテナンス性向上
- MySQL系テストのスキーマ変更は
mysql_specific_schema.rb1か所の修正で済むようになります。 - mysql2 向けの修正忘れや trilogy 向けの修正漏れといったミスを減らせます。
- MySQL系テストのスキーマ変更は
- 参考情報 (あれば)
- PR: https://github.com/rails/rails/pull/58894
- 過去のドリフトに関するコミット:
8b0c8079c6e
(mysql2 / trilogy 用スキーマファイルが一度内容不一致になった経緯への言及)
テストで MySQL 系アダプタを扱う際は、今後は「MySQL 特有のスキーマ = mysql_specific_schema.rb」という前提で両アダプタを見ておくとよいです。
#58884 Fix the Trilogy migration_strategy example in the migrations guide [ci skip]
マージ日: 2026/9/28 | 作成者: @ramen2020
概要 (1-2文で)
Trilogy アダプタ向けのmigration_strategy設定例が間違っており、そのままコピペすると例外が出てしまう問題を、ドキュメント上のクラス名を正しいものに修正した PR です。挙動変更はなく、Active Record Migrations ガイドのサンプルコードのみが修正されています。変更内容の詳細(あればサンプルコードも含めて)
Active Record Migrations ガイドの「アダプタごとのmigration_strategyを設定する例」で、Trilogy 用のサンプルが次のようになっていました:
ActiveSupport.on_load(:active_record_trilogyadapter) do
ActiveRecord::ConnectionAdapters::Trilogy.migration_strategy =
MySQLMigrationStrategy
endここで使われている ActiveRecord::ConnectionAdapters::Trilogy は、Trilogy のアダプタクラスではなく「モジュール」であり、migration_strategy は AbstractAdapter 上の class_attribute なので、「アダプタクラス」に対して設定する必要があります。
Trilogy のアダプタクラスは TrilogyAdapter なので、正しくは以下のように書く必要があります:
ActiveSupport.on_load(:active_record_trilogyadapter) do
ActiveRecord::ConnectionAdapters::TrilogyAdapter.migration_strategy =
MySQLMigrationStrategy
endPR では、この一行だけが以下のように置き換えられています。
- 修正前:
ActiveRecord::ConnectionAdapters::Trilogy.migration_strategy = ... - 修正後:
ActiveRecord::ConnectionAdapters::TrilogyAdapter.migration_strategy = ...
作者は実際に active_record/connection_adapters/trilogy_adapter をロードした上で動作を確認しており、
- 旧コード:
NoMethodError: undefined method 'migration_strategy=' for module ActiveRecord::ConnectionAdapters::Trilogy - 修正後: 問題なく
TrilogyAdapter.migration_strategy = MySQLMigrationStrategyが実行可能
となることを確認済みです。
- 影響範囲・注意点
影響範囲
- ドキュメントのみの変更であり、本体コードや挙動には一切変更がありません。
- ただし、これまでガイドを見てそのままコピペしていた場合は、
NoMethodErrorが発生していたはずで、それを防ぐ形になります。
注意点 / 実際に書くべきコードのポイント
migration_strategyはアダプタクラス(ここではActiveRecord::ConnectionAdapters::TrilogyAdapter)に対して設定する必要があります。- PostgreSQL 用の例では既に
PostgreSQLAdapterに対して設定しており、Trilogy だけがモジュールを参照する誤りになっていました。 on_loadのフック名は:active_record_trilogyadapterを使う想定になっていますが、Trilogy を利用しているアプリ側でこれと整合するように記述する必要があります。- MySQL 用に独自の
MySQLMigrationStrategyを作る場合は、通常のActiveRecord::MigrationContextなどの利用パターンと同様に、マイグレーション実行戦略をカスタマイズするクラスを実装したうえで、このclass_attributeを差し替える運用になります。
- 参考情報 (あれば)
- 修正対象:
guides/source/active_record_migrations.mdの Trilogy 用migration_strategy設定例 - 背景知識:
migration_strategyはActiveRecord::ConnectionAdapters::AbstractAdapterのclass_attribute- アダプタごとに異なるマイグレーション戦略を使いたいときに、各アダプタクラス (
PostgreSQLAdapter,TrilogyAdapterなど) に対して上書きして使う
- 関連する正しいパターンの例(PostgreSQL):ruby
ActiveSupport.on_load(:active_record_postgresqladapter) do ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.migration_strategy = PostgreSQLMigrationStrategy end
#58876 Fix error matching for validations using except_on
マージ日: 2026/9/27 | 作成者: @afurm
- 概要 (1-2文で)
except_on:オプション付きバリデーションで、エラーが正しく一致判定されずerrors.detailsにも余計な情報が残っていた不具合を修正する PR です。except_on:をon:などと同様に「コールバック用オプション」として扱うことで、エラー検索・表示が期待通りの挙動になります。
- 変更内容の詳細
問題の挙動
次のようなモデルを想定します:
class Post
include ActiveModel::Model
attr_accessor :title
validates :title, presence: true, except_on: :draft
end
post = Post.new
post.valid? # => false
post.errors[:title] # => ["can't be blank"]
post.errors.added?(:title, :blank) # 現状: false (期待: true)
post.errors.details
# 現状: { title: [{ error: :blank, except_on: :draft }] }
# 期待値: { title: [{ error: :blank }] }期待されるのは:
errors.added?(:title, :blank)がtrueを返すことerrors.detailsには「エラーそのものを識別するための情報」だけが入り、except_onのような「いつバリデーションを走らせるか」というメタ情報は含まれないこと
ですが、except_on: が「コールバック系のオプション」として認識されていなかったため、
- 生成された
ActiveModel::Errorの内部にexcept_on: :draftがそのまま残る - 検索側(
errors.added?など)はexcept_onを含まない条件でマッチングする
という不整合が起きていました。
修正内容
この PR の中心は1行です:
# activemodel/lib/active_model/error.rb
- CALLBACKS_OPTIONS = [:if, :unless, :on].freeze
+ CALLBACKS_OPTIONS = [:if, :unless, :on, :except_on].freezeActiveModel::Error が「バリデーションの実行条件に関するオプション」を内部的に捨てる・フィルタする際に参照する CALLBACKS_OPTIONS に :except_on を追加しました。
これにより:
- エラー生成時に
except_on: :draftがコールバック用オプションとして認識され、errors.detailsに保存される際に除外されるerrors.added?などのマッチングでも、except_onの有無によって結果が変わらない
という、on:と同等の扱いになります。
テストの追加・拡張
以下のテストが追加・拡張されています:
activemodel/test/cases/error_test.rberrors.detailsからexcept_onが取り除かれていることerrors.added?がexcept_onの有無に関わらず正しくマッチすること
activemodel/test/cases/validations_test.rbexcept_on:を付けたバリデーションが、指定したコンテキストではスキップされ、
かつエラーが残らないことを統合的に確認
あわせて CHANGELOG にもこのバグフィックスが明記されています。
- 影響範囲・注意点
影響を受けるケース
ActiveModel/ActiveRecordでexcept_on:オプション付きのバリデーションを使っている場合- 特に
errors.added?やerrors.detailsに依存したコード(テスト・アプリロジック問わず)
期待される挙動の変化
- これまで
errors.added?がfalseを返していたケースで、trueになる可能性があります。 errors.detailsからexcept_onが消えるため、
もしerrors.details[:attribute].first[:except_on]のように直接参照していたコードがあれば壊れます。
ただし、except_onはバリデーションの実行条件であって「エラーの内容」ではないため、
通常ここに依存すべきではありません。
- これまで
後方互換性の観点
- バグ修正であり、Rails の本来の設計(
onなどの実行条件オプションはエラー情報から除外する)に揃える変更です。 - 正しい挙動に依存して書かれたコードにとっては改善であり、
except_on導入時(#43495)からのバグを是正するものです。
- バグ修正であり、Rails の本来の設計(
- 参考情報 (あれば)
except_on導入 PR: #43495関連する過去の問題と修正:
- Issue: #54483
- Fix: #56546
(こちらは「クエリ側に渡されたコールバックオプションをフィルタする」問題で、本 PR は「except_onがそもそもコールバックオプションとして認識されていなかった」問題を解消)
テスト状況:
- 変更箇所の追加テスト: 3 runs, 8 assertions, 失敗・エラー・スキップなし
- Active Model 全体: 1,226 runs, 5,448 assertions, 失敗・エラー・スキップなし
- RuboCop: 変更ファイルすべてでパス
#58807 Treat false as disabled for idle_timeout, reaping_frequency and max_age
マージ日: 2026/9/27 | 作成者: @carlosdanielpohlod
- 概要 (1-2文で)
Rails の DB 接続設定(idle_timeout,reaping_frequency,max_age)に対して、database.ymlでfalseを指定した場合に起きていたNoMethodErrorを解消し、falseを「無効化」、trueを「デフォルト値を使う」として扱うようにした変更です。これにより、off/no/on/yesといった YAML のブール表現でも直感的かつ一貫した設定ができるようになります。
- 変更内容の詳細
背景・問題点
database.yml に以下のように書くと:
production:
idle_timeout: offYAML では off は false に変換されますが、Rails 側(HashConfig)では値に対して to_f / to_i をそのまま呼んでいたため、起動時に以下のような例外が発生していました。
NoMethodError: undefined method `to_f` for false:FalseClass同様に no も false、on / yes は true として解釈されるため、ブール値をうっかり書くと簡単にエラーになってしまう状態でした。
一方で、DATABASE_URL 経由では値が文字列のため、この問題は起きていませんでした("0" や "10" などに対する to_i / to_f は問題ない)。
また、既に keepalive については 3c750994d6 で、false を無効化、true をデフォルトという扱いにしており、今回それと揃える意図もあります。
今回の仕様変更
対象の設定項目:
idle_timeoutreaping_frequencymax_age
について、以下のように扱うように変更されています。
false- これらの機能を「無効化」する扱い
- ドキュメント上で「0 を指定すると無効」とされている挙動と同じ意味
true- 値を明示的に上書きせず、「デフォルト値を使う」
- 非ブール値(数値・文字列など)
- これまで通り
to_i/to_fによる変換・解釈を行う(挙動変更なし)
- これまで通り
※具体的なコードは PR 本文には全文載っていませんが、HashConfig 内の idle_timeout, reaping_frequency, max_age 取得処理に、ブール値のときの分岐(true / false の特別扱い)が追加された形です。
具体的な設定例
production:
# idle_timeout を完全に無効化
# (0 を指定する場合と同じ意味)
idle_timeout: off # => false と解釈されて無効
# reaping_frequency を無効化
reaping_frequency: no # => false
# max_age はデフォルト値を使う
max_age: yes # => true
# これまで通りの数値指定も可能
idle_timeout: 5 # 5 秒
max_age: 3600 # 3600 秒YAML による変換:
off,no→false→ 該当機能を無効化on,yes→true→ デフォルト値を使う
というパターンになります。
テスト・ドキュメント
HashConfigのテスト (hash_config_test.rb) に、true/false指定時の挙動に関するケースが追加activerecord/CHANGELOG.mdに、この仕様変更が明記されており、挙動が変わることがわかるようになっています
- 影響範囲・注意点
エラー解消
- これまで
idle_timeout: offなどの書き方でNoMethodErrorが発生していたアプリケーションは、エラーが解消されて正常に起動するようになります。
- これまで
ブール→動作の意味付け
falseは「0 と同じ=無効化」として扱われます。trueは「設定を上書きせずデフォルト値を使う」として扱われるため、今後trueと具体的な数値を取り違えると、意図せずデフォルトが使われる可能性があります。- ただし以前は
trueでto_i/to_fしても意味のある数値にはならず、挙動としても実用的ではなかったため、実害のある互換性破壊は少ないと考えられます。
DATABASE_URL 経由との一貫性向上
DATABASE_URL経由では元々"false"/"true"などは文字列扱いのため、今回の修正と直接は関係しませんが、- 「
database.ymlでブール綴りを書くと落ちる」という不自然さが解消され、設定手段間での違いが減ります。
既存の 0 指定との関係
0を指定していた場合の挙動には変更はなく、引き続き「無効化」として動作します。- 新たな選択肢として、論理的に分かりやすい
off/noなどを使えるようになった、という位置付けです。
- 参考情報 (あれば)
- 関連する過去の変更:
keepaliveに対して同様の扱いを導入したコミット- ハッシュ:
3c750994d6
- ハッシュ:
- 変更ファイル:
activerecord/lib/active_record/database_configurations/hash_config.rb- ブール値の扱いロジックが追加・修正された本体
activerecord/test/cases/database_configurations/hash_config_test.rb- 新仕様に対応したテストケース
activerecord/CHANGELOG.md- 挙動変更の告知・ドキュメント更新
#58886 Fix typos in guides [ci skip]
マージ日: 2026/9/26 | 作成者: @bastin27
概要 (1-2文で)
このPRは、Rails公式ガイド内(Action View Overview、Action Controller Overview、Asset Pipeline)の誤字を修正した、ドキュメント専用の変更です。アプリケーションコードや挙動には一切影響しません。変更内容の詳細(あればサンプルコードも含めて)
対象ファイル
guides/source/action_view_overview.mdguides/source/action_controller_overview.mdguides/source/asset_pipeline.md
内容
- 各ガイド内のスペルミス・タイポを4行分修正(+4 / -4)。
- 修正はあくまで文章上の誤字脱字のみで、サンプルコードやAPIの使い方の変更は含まれていません。
- ガイドの可読性・正確性を高める目的の微修正です。
(PR本文からも「This Pull Request fixes typos…」「Documentation-only change; no code or behavior is affected.」と明示されています)
- 影響範囲・注意点
影響範囲
- Railsガイドの文章のみが対象で、フレームワークの本体コード、API仕様、挙動、パフォーマンスには影響しません。
- 既存アプリケーション、テストコードの修正は不要です。
注意点
- ドキュメントを日本語訳しているプロジェクトや社内Wikiなどで、該当ガイドを翻訳・引用している場合は、原文の更新に追随するかどうかを確認するとよいです。
- タイポ修正のみのため、バージョン互換性などを気にする必要はありません。
- 参考情報 (あれば)
- ガイド執筆・修正に関する公式ガイドライン
- 対象ガイド(Edge Guides版)
- Action Controller Overview: https://edgeguides.rubyonrails.org/action_controller_overview.html
- Action View Overview: https://edgeguides.rubyonrails.org/action_view_overview.html
- Asset Pipeline: https://edgeguides.rubyonrails.org/asset_pipeline.html
#58878 Skip extension member tables, enum types and schemas in the Ruby schema dump
マージ日: 2026/9/26 | 作成者: @yahonda
- 概要 (1-2文で)
PostgreSQL の拡張機能が所有しているテーブル・enum 型・スキーマを、Rails のdb/schema.rbダンプから自動的に除外する変更です。これにより、PostGIS や TimescaleDB、Citus などを使う環境でもschema.rbのロードがエラーで止まらず、拡張機能はenable_extension経由で正しく再現されます。
- 変更内容の詳細
何をやっているか
PostgreSQL は、拡張機能が所有するオブジェクト(テーブル、enum 型、スキーマなど)を pg_depend テーブルで deptype = 'e' として管理しています。
この PR では:
pg_dependを参照して「拡張機能が所有するオブジェクト」の一覧を取得- その一覧に含まれる
- テーブル
- enum 型
- スキーマ
についてはdb/schema.rbのダンプ対象から除外 する
というロジックを PostgreSQL アダプタのスキーマダンパーに追加しています。
enable_extension "postgis" などの行はこれまで通り schema.rb に残るので、db:schema:load の際には CREATE EXTENSION と同様の挙動で、拡張機能とその配下オブジェクトが自動的に作成されます。
従来の問題点
PostgreSQL の拡張機能の例:
- PostGIS:
spatial_ref_sysテーブルなど多数 - pg_partman:
part_config,part_config_sub - Citus: enum 型
citus.distribution_type,citus.shard_transfer_mode、スキーマcitus,citus_internal,columnar,columnar_internal - TimescaleDB:
_timescaledb_catalog,_timescaledb_internalなど複数スキーマ配下に多数のテーブル
従来の schema.rb は、これらが通常のテーブル・スキーマ・enum と同様にダンプされていました。その結果、db:schema:load 時に以下のような問題が発生していました:
force: :cascadeで拡張機能所有のテーブルをDROP TABLEしようとして失敗- 拡張機能が作るはずのスキーマを
create_schemaで先に作成してしまい、CREATE EXTENSION側と競合 - すでに拡張機能が作成済みのスキーマを再作成しようとして重複エラー
具体例:
PG::DependentObjectsStillExist: ERROR: cannot drop table part_config because extension pg_partman requires it
PG::ObjectNotInPrerequisiteState: ERROR: schema columnar is not a member of extension "citus_columnar"
PG::DuplicateSchema: ERROR: schema "_timescaledb_cache" already existsこれを回避するために、現場では以下のような対応が必要でした:
ignore_tablesなどで手動除外リストを管理する(PostGIS は拡張 gem が長年メンテ)- あるいは
schema.rbを諦めてstructure.sqlに切り替え、pg_dumpに任せる
(pg_dumpは拡張機能に属するオブジェクトは出力しない)
今回の PR により、structure.sql に切り替えなくても、schema.rb が拡張機能と共存しやすくなります。
変更後のダンプイメージ
PostGIS / pg_partman を public にインストールしている場合の差分:
従来:
enable_extension "pg_partman"
enable_extension "postgis"
create_table "part_config", primary_key: "parent_table", id: :text, force: :cascade do |t|
...
end
create_table "part_config_sub", primary_key: "sub_parent", id: :text, force: :cascade do |t|
...
end
create_table "posts", force: :cascade do |t|
...
end
create_table "spatial_ref_sys", primary_key: "srid", id: :integer, default: nil, force: :cascade do |t|
...
end変更後:
enable_extension "pg_partman"
enable_extension "postgis"
create_table "posts", force: :cascade do |t|
...
enddb:schema:load 時には:
enable_extension "pg_partman"enable_extension "postgis"
により拡張機能が作られ、それぞれの拡張機能スクリプト内部で part_config や spatial_ref_sys などが自動作成される、という形になります。
実装的なポイント(ファイル別)
※PR の実際のコードは概要からの推測を含みますが、意図としては以下のような構造です。
activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rbpg_depend+deptype = 'e'を使って、「拡張機能所有オブジェクト一覧」を取得するメソッドを追加- 後続で schema dumper から使用するためのインターフェイスを提供
activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb- ダンプ時に上記の一覧を見て、該当するテーブル・enum 型・スキーマをスキップするように変更
activerecord/test/cases/adapters/postgresql/extension_member_objects_dump_test.rb- PostGIS/TimescaleDB 等のようなユースケースを想定したテストで、拡張機能所有オブジェクトが
schema.rbに含まれないことを確認
- PostGIS/TimescaleDB 等のようなユースケースを想定したテストで、拡張機能所有オブジェクトが
activerecord/CHANGELOG.md- PostgreSQL + 拡張機能使用時の
schema.rbの挙動変更として記載
- PostgreSQL + 拡張機能使用時の
動作上の補足
connection.tablesやtable_exists?などの API は、従来通り拡張機能所有テーブルも返す
⇒ アプリケーションコードから見えるテーブル一覧は変わらない- アプリが自前で作成したオブジェクトを、
ALTER EXTENSION ... ADDで拡張機能に後から紐づけた場合
⇒ それも「拡張機能所有オブジェクト」と見なされ、schema.rbからは除外される
⇒pg_dumpと同じ扱い
- 影響範囲・注意点
影響範囲
- 対象: PostgreSQL を利用しており、かつ PostGIS / TimescaleDB / Citus / pg_partman など 拡張機能を利用している Rails アプリ
- 論点:
schema.rbの内容が変わる- これまで
schema.rbに出力されていた「拡張機能所有テーブル・enum 型・スキーマ」が記述されなくなる - 代わりに
enable_extensionだけで再現される前提の世界観になる
- これまで
想定メリット
- 拡張機能を入れた後に、
db:schema:loadがエラーで止まる問題が大幅に減る - PostGIS 以外(TimescaleDB、Citus、pg_partman など)でも手動の
ignore_tablesやignored_schemasを維持する必要が基本的になくなる structure.sqlに移行することなく、schema.rbベースで開発を続けやすくなる
注意点・互換性
「拡張が作ったテーブルも schema.rb に明示的に残したい」というニーズには合わない
- この変更により、それらは
schema.rbから消える - 再現は
enable_extensionに全面的に依存する - DB をダンプして別 DB エンジンに移植する、などのユースケースではもともと拡張機能依存のオブジェクトは移植困難なので、方針としては妥当だが、期待が変わる場合がある
- この変更により、それらは
ALTER EXTENSION ... ADDしたオブジェクトも除外される- 拡張機能にぶら下げた自前テーブルなどは
schema.rbに出てこない - これも
pg_dumpと同じ挙動だが、「自前定義だからschema.rbに出てきてほしい」と思っていると意外に感じるかもしれない - そのようなテーブルを
schema.rbで管理したいなら、拡張機能にADDしない設計にする必要がある
- 拡張機能にぶら下げた自前テーブルなどは
拡張機能の作成タイミングに依存
db:schema:loadでenable_extensionが正しく動くことを前提としている- CI や本番などで DB ユーザ権限の制約が強い場合、
CREATE EXTENSIONが失敗すると拡張配下オブジェクトも作られず、結果としてアプリ起動時にテーブルがない、といった状況になる - 従来も同じだが、今回から「
schema.rb側でリカバリする」道もなくなるため、権限設定には注意
既存の
ignore_tablesとの関係- 既に PostGIS 用などで
ignore_tablesを設定しているアプリは、そのままでも問題ないが、重複設定になっている可能性はある - 今後は拡張が作るテーブルは自動的に除外されるため、
ignore_tables側を徐々に整理してもよい
- 既に PostGIS 用などで
- 参考情報 (あれば)
- 関連 Issue / PR:
- #58834: 本 PR と関連する拡張機能周りの議論
- #49555: TimescaleDB のための
ignored_schemas提案(今回の仕組みで不要に)
- PostgreSQL 公式ドキュメント:
pg_depend: 依存オブジェクト管理テーブルdeptype = 'e': オブジェクトが拡張機能に属することを示す
- Extensions:
CREATE EXTENSION,DROP EXTENSION,ALTER EXTENSION ... ADD/DROPの挙動と、拡張が所有するオブジェクトの扱い
- Rails:
schema.rbvsstructure.sqlの選択に関する公式ガイド- これまでは「PostGIS など拡張を多用するなら
structure.sql推奨」という空気が強かったが、この変更でschema.rbでもかなり扱いやすくなります。
- これまでは「PostGIS など拡張を多用するなら
#58819 Improve headings in Guides Guidelines guide.
マージ日: 2026/9/25 | 作成者: @p8
- 概要 (1-2文で)
このPRは、Railsガイド「Guides Guidelines」ドキュメント内の見出しレベルと行折り(行幅)のルールを整理・統一したものです。ガイド内でh2見出しを使う箇所を限定しつつ、テキストを80カラムで折り返すように編集しています。
- 変更内容の詳細
見出しレベルの整理
- 対象ファイル:
guides/source/ruby_on_rails_guides_guidelines.md - 方針:
- 「Guidelines」「HTML Guides」「EPUB guides」に対するセクションにのみ
h2(##) を使う - それ以外のセクションでは、文書構造に応じてより適切なレベル (
###など) を使うように整理
- 「Guidelines」「HTML Guides」「EPUB guides」に対するセクションにのみ
これにより:
- ドキュメントのアウトラインが論理的な階層構造になる
- HTML/EPUB 生成時の目次(TOC)が過度にフラットにならず、ナビゲーションしやすくなる
- アクセシビリティ的にも、スクリーンリーダーが正しい構造を把握しやすくなる
擬似的なイメージ(実際のテキストは異なりますが構造のイメージとして):
# Ruby on Rails Guides Guidelines
## Guidelines
### Writing Style
### Code Examples
## HTML Guides
### Layout
### Assets
## EPUB guides
### Specific Considerations以前はこれら以外のセクションでも ## が乱立していたり、階層が曖昧になっていたものを整理した、という位置づけです。
行折り (wrap) の統一
- ガイドライン本文中の行が 80 カラムで折り返されるように修正
- Markdown の長い文章・箇条書きを手動で改行して、1行の文字数を抑えるよう編集
- 目的:
git diffの見やすさ向上(1語の追加で巨大な1行が変更扱いにならない)- コマンドラインやエディタでの閲覧性向上
- 他の Rails ガイド/Ruby コミュニティのスタイルに合わせる
例 (概念的なイメージ):
# 変更前(1行が長い)
Rails Guides are written in a way that ...
# 変更後(80カラム程度で手動折り返し)
Rails Guides are written in a way that ...
They should follow the general guidelines so that ...Markdown 的には意味は変わらず、表示結果も基本的に同じですが、ソース上の見通しが良くなります。
- 影響範囲・注意点
- ランタイムコードへの影響: なし
- 変更はドキュメント(ガイドライン)ファイルのみ
- 影響する利用者:
- Rails ガイドを書いたりメンテナンスするドキュメント貢献者
- HTML/EPUB として生成された Rails Guides を利用する読者
- 期待される効果:
- 生成されるガイドの目次構造・見出し階層が改善され、読みやすさとアクセシビリティが向上
- 今後ガイドを書く際の「見出しレベルの基準」が明確になり、スタイルの一貫性が出る
- 80カラム規約に沿うことで、PR レビューや差分確認が楽になる
注意点:
- ガイド執筆や修正を行う場合は:
- 「Guidelines」「HTML Guides」「EPUB guides」に対するセクション以外では、むやみに
##を使わず、適切な階層レベルを選ぶ - テキストは原則として 80 カラムで折り返す(特に長い段落や箇条書き)
- 「Guidelines」「HTML Guides」「EPUB guides」に対するセクション以外では、むやみに
- TOC の自動生成ロジックを前提としたリンク(特定の
idに依存するアンカーリンク)を外部で持っている場合、見出しレベル変更に伴いidが変わる可能性があります(通常のガイド利用では問題にならないことが多いですが、外部からガイドの特定見出しに直接リンクしている場合は要確認)。
- 参考情報 (あれば)
- 対象ファイル:
guides/source/ruby_on_rails_guides_guidelines.md
- Rails Guides の一般的なスタイルガイドは、同ファイル内で説明されているため、このPR後の版を参照すると最新の見出し/折り返しルールを確認できます。
#58165 [RF-DOCS] Update the JavaScript in Rails guide
マージ日: 2026/9/25 | 作成者: @ayushn21
- 概要 (1-2文で)
「Working with JavaScript in Rails」ガイドを大幅に書き換え、Importmap と JavaScript バンドラの位置づけ整理、Hotwire(Turbo / Stimulus)の詳説、request.jsの紹介など、現行 Rails に即した JavaScript 周りの公式ドキュメントをアップデートした PRです。従来よりも構成を整理し、実際の選択・導入・連携方法の理解がしやすくなる内容になっています。
- 変更内容の詳細
※ファイルは1本ですが、+672/-196 と大幅改訂のため、主に「内容の方向性」をまとめます。
2.1 Importmap と JavaScript Bundler 周りの整理
- ガイド冒頭の構成を見直し、以下の点が改善されています:
- Importmap と JavaScript バンドラ(esbuild, Webpack, Vite など)を「Rails に JavaScript を追加する2つのアプローチ」として整理。
- 「Import maps」「Adding npm Packages with JavaScript Bundlers」「Choosing Between Import Maps and a JavaScript Bundler」といった複数セクションを、ストーリーとして読みやすい流れに再構成。
- Importmap とバンドラの比較セクションを簡潔化し、「いつどちらを選ぶべきか」が分かりやすくなっている。
おそらく以下のような観点で比較されていると考えられます(内容から推定):
- Importmap:
- npm / Node.js なしで使える
- シンプルで Rails 標準との親和性が高い
- 大規模フロントエンド構成には不向き
- Bundler:
- npm パッケージが自由に使える
- Tree Shaking やトランスパイルなどモダンフロントエンドの恩恵を受けられる
- Node.js, npm のセットアップが前提
これにより、「新規アプリで何を選ぶか」「既存アプリでどう移行するか」の判断材料としてガイドが使いやすくなります。
2.2 Bundler セクションの汎用化・整理
- 見出しを「Adding npm Packages to Javascript bundlers」→「Using Javascript Bundlers」のような、個別ツールに依存しない汎用的なタイトルに変更する提案が反映されている可能性があります。
- Node.js バージョンに関する記述を、EOL の古いバージョン(>=8.16.0)から、現代的なバージョン(>=18)に引き上げる方向で更新。
- Rails でモダンなツール群(esbuild, Vite, Tailwind CLI 等)を使う場合に妥当な基準です。
2.3 Hotwire(Turbo / Stimulus)セクションの追加・拡充
この PR の中心的な追加内容です。
Turbo の説明・統合方法
- 「Turbo とは何か」を Rails 開発者向けに説明するセクションを新設。
- Turbo Drive(ページ遷移を高速化)
- Turbo Frames(ページ一部のみの部分更新)
- Turbo Streams(サーバー側からの DOM 更新配信/ブロードキャスト)
- Rails への組み込み方法 (
turbo-railsgem) が明示的に解説されている:Gemfileにturbo-railsを追加bin/rails turbo:installによるセットアップapplication.html.erbに<%= turbo_include_tags %>を追加するなどの基本的統合手順
- Turbo Drive / Frames について、それぞれのヘルパーの使い方に言及:
- 例: Turbo Drive Helper
turbo_frame_request?turbo_stream_request?
- Turbo Frames Helper
turbo_frame_tag
- 例: Turbo Drive Helper
- Turbo Streams セクションでは、Turbo Broadcast(Active Record モデル更新に応じた自動配信)にも言及し、ヘッダーを持った独立項目として整理されている可能性があります:
after_create_commit { broadcast_append_to ... }after_update_commit { broadcast_replace_to ... }- といった Active Record のコールバックとの連携例。
Stimulus の説明・stimulus-rails との連携
- 内部レビューコメントを受け、Turbo だけでなく Stimulus にも触れる内容が追加。
- 典型的には以下のような流れが記載されていると考えられます:
Gemfile:rubygem "stimulus-rails"- セットアップ:bash
bin/rails stimulus:install - コントローラの例:js
// app/javascript/controllers/hello_controller.js import { Controller } from "@hotwired/stimulus" export default class extends Controller { static targets = [ "name" ] greet() { console.log(`Hello, ${this.nameTarget.value}!`) } } - テンプレート側:erb
<div data-controller="hello"> <input data-hello-target="name"> <button data-action="click->hello#greet">Greet</button> </div>
- Rails と Stimulus を組み合わせた「サーバー側主導+最小限の JS でインタラクション追加」という Hotwire の思想を説明していると考えられます。
2.4 request.js セクションの追加
Rails アプリから Fetch ベースで簡潔に AJAX / XHR 的な振る舞いをするための
@rails/request.jsに関する新セクションが追加。典型的な使用例が示されている可能性が高いです:
jsimport { get, post, put, destroy } from "@rails/request.js" // GET 例 const response = await get("/articles/1", { responseKind: "json" }) if (response.ok) { const data = await response.json // ... ここで data を利用 } // POST 例 const response = await post("/articles", { body: { title: "New article" }, responseKind: "turbo-stream" })PR コメントで触れられているとおり、AJAX サンプルコードが整形されており:
- 余分なスペース削除(
myFunction ()→myFunction()) const body = await response.textの後に"// do something with body"など、その後の処理が続くことを示すコメントを追加して、コードの意図が分かりやすくなっています。
- 余分なスペース削除(
2.5 セクション構成・表現の微修正
- 「Turbo Drive に関する単独セクション」を NOTE として移動したり、構成を簡潔にするような整理が行われている可能性があります。
- NOTE ブロックや見出し名の改善:
- 「method」という曖昧な見出しを「Overriding HTTP Methods」のように意味が明確なタイトルに変更。
- Turbo セクションのタイトルを、「Turbo を知らない人でもイメージしやすい名称」に寄せている(例: “Faster Navigation with Turbo” など)。
- ドキュメント全体として、「JavaScript を追加する」「高速なナビゲーション」「部分更新」「AJAX 通信」というテーマごとに読み進められるように整理されています。
2.6 今後追加され得る内容への布石
PR 説明には、次のような追加案が挙がっており、一部は既に反映済み・一部は追って追加される可能性があります:
- Turbo 8 以降の新機能(morphing など)への言及
- Propshaft と従来の asset pipeline の位置づけの説明
- Turbo に関するトラブルシューティングセクション
現時点では少なくとも Hotwire 関連(Turbo/Stimulus)と request.js は反映済みです。
- 影響範囲・注意点
- 影響範囲:
- コード自体ではなく、公式ガイド(ドキュメント)のみの変更です。
- 既存アプリの挙動を変えるものではありませんが、「Rails で JavaScript/Hotwire をどう構成すべきか」というベストプラクティスの指針が更新されます。
- 注意点:
- 新規プロジェクトを始める開発者は、これまでの古い記事やブログと内容が異なる場合があります。JavaScript 戦略を決める際は、この更新後ガイドを優先するとよいです。
- Node.js バージョン要件が >=18 相当に引き上げられた前提で説明されているため、古い Node.js を使っている開発環境ではガイド通りに進まない可能性があります。
- Turbo / Stimulus / request.js の記述が増えたことで、Rails フロントエンドを「フルSPAでやる前提」で考えていた人には、Rails 推奨の「サーバー主導+Hotwire」方針がより強く示される形になっています。
- 参考情報 (あれば)
対象ガイド:
guides/source/working_with_javascript_in_rails.md関連プロジェクト / ドキュメント:
- Hotwire: https://hotwired.dev/
- Turbo Handbook: https://turbo.hotwired.dev/handbook
turbo-rails:stimulus-rails: https://github.com/hotwired/stimulus-rails- Propshaft: https://github.com/rails/propshaft
@rails/request.js: https://github.com/rails/request.js
この PR の内容を前提にすると、「新しい Rails アプリでの JS 方針をどうするか」「既存アプリに段階的に Hotwire を導入するにはどうするか」を考える際、まずこのガイドを読むのが一番筋が良い状態になっています。
#57573 [RF-DOCS] Update the guide for using Rails as an API
マージ日: 2026/9/25 | 作成者: @ayushn21
- 概要 (1–2文で)
Rails Guides の「Rails を API として使う」ガイドが、ステップバイステップのチュートリアル寄りな構成から、より一般的で最新の情報に沿ったリファレンス的な内容へ整理・改訂されました。あわせて JBuilder / Builder や XML レスポンス、Rack ミドルウェア・ActionController モジュールに関する説明が追補・更新されています。
- 変更内容の詳細
ガイド全体の構成変更・トーン変更
- 従来:
- 「1. アプリを作る → 2. この設定をする → 3. このコードを書く」といった手順書寄りの流れ。
- 「なぜ Rails を API として使うか」の章が実質 Rails の機能カタログのようになっていた。
- 変更後:
- ガイド全体を「手順」から「概念とベストプラクティス中心」に再構成。
- 例: 設定・ミドルウェア・レンダリング・レスポンスフォーマット・認証モジュールなど、テーマ別に整理。
- 「なぜ Rails を API として使うか」の説明を簡潔化し、
- Rails の大部分 (ルーティング / Active Record / Active Job / Active Storage など) は API アプリでもそのまま活かせる、
- 特定のフロントエンド技術や読者の価値観を前提にしない、 といった、より中立的で一般化されたメッセージに差し替え。
- ガイド全体を「手順」から「概念とベストプラクティス中心」に再構成。
Rails を API として使う際のレンダリング・フォーマット関連
JBuilder の追加説明
- Rails 標準でよく利用される JSON ビューの仕組みである JBuilder について節が追加。
- 概要:
- コントローラで
renderすると、対応する*.json.jbuilderテンプレートを元に JSON を組み立てる。 - Ruby の DSL で JSON 構造を定義でき、N+1 回避など Active Record と組み合わせやすい。
- コントローラで
例 (ガイドに近いイメージのサンプル):
# app/controllers/articles_controller.rb
def show
@article = Article.find(params[:id])
render :show # => app/views/articles/show.json.jbuilder を使う
end# app/views/articles/show.json.jbuilder
json.extract! @article, :id, :title, :body
json.author do
json.id @article.author.id
json.name @article.author.name
end
json.created_at @article.created_at.iso8601Builder と XML レスポンス
- Builder(RXML) による XML レスポンスと、XML を API のデータ交換フォーマットとして使うケースをカバー。
- JSON が主流だが、レガシーシステムや特定業界 (金融、官公庁など) では XML API も依然有効であることを明確化。
- 典型的な利用イメージ:
def show
@article = Article.find(params[:id])
respond_to do |format|
format.json
format.xml # => app/views/articles/show.xml.builder を使う
end
end# app/views/articles/show.xml.builder
xml.article do
xml.id @article.id
xml.title @article.title
xml.body @article.body
endRack ミドルウェアに関する記述の更新
- API モードでのミドルウェア構成説明を最新化:
- 以前のガイドで触れていたミドルウェアの一部 (例:
Rack::Cache周辺) が現状に合っていなかったため、説明を整理。 - 「どのミドルウェアが API アプリに適しているか」「API モードでは何がデフォルトで有効か」といった観点を改善。
- 以前のガイドで触れていたミドルウェアの一部 (例:
- 内部監査コメントにもある通り:
Rack::Cacheが本文中で説明されている一方で、冒頭のミドルウェア一覧から漏れていたため、この辺りの整合性を取るような構成見直し・説明の更新が行われている。
Action Controller モジュールの説明を最新化
ActionController::APIが どのモジュールを include しているか の説明をアップデート。- 例として挙げられている:
- 以前は別モジュール扱いだった
ActionController::Cachingが、現在はActionController::APIに含まれている、という情報を反映。
- 以前は別モジュール扱いだった
- 例として挙げられている:
- API モードのコントローラが持つ機能一覧 (レンダリング, パラメータ, リダイレクト, HTTP 認証など) を現行実装と整合させる形で書き直し。
認証に関する扱いの変更
- 新たな「API 認証」の大きな章を追加する案は見送り。
- 理由:
- 認証の設計はアプリごとに大きく異なり、汎用ガイドとして書くには前提が重くなりすぎるため。
- 代わりに:
- Rails 標準の HTTP 認証用モジュール (Basic 認証 / Digest 認証 / Token 等) へのリンクをガイド内から参照し、「ここから自分の要件に合わせて実装してほしい」というスタンスに。
- 理由:
- 結果として、ガイドは「HTTP レベルで何ができるか」を示しつつ、具体的な認証フローやトークン管理などの設計は開発者側に委ねる形に整理。
表記揺れ・スコープに関する内部メモ
PR 説明には、今後の改善候補として以下が挙げられています (今回のコミットで完全には解決していない可能性あり):
- 「api」表記の統一:
- ガイド中の "api" を "API" に統一すべきでは、という指摘。
- JSON API に偏っている点:
- 本ガイドは JSON API を前提に説明しており、GraphQL のような他の API 形態は触れられていない。
- セクション名:
- "The Basic Configuration" を「Setting Up an Application as an API」などに改名した方が内容に即しているのでは、という検討。
Rack::Cacheの扱い:- 解説されているのに、導入部のミドルウェアリストから抜けている問題が明示されており、今回の整理の一環として注意が払われている。
- 影響範囲・注意点
- コードベースへの影響:
- 変更ファイルは
guides/source/api_app.mdのみであり、Rails 本体の挙動・API 仕様には変更なし。 - 実運用中のアプリケーションコードが直接壊れることはない。
- 変更ファイルは
- ドキュメントとしての影響:
- これから Rails を API モードで使う開発者は、より「一般的な考え方と選択肢」を把握しやすくなる一方、
旧来の「チュートリアルとしてそのままコピペして進める」フローは薄まっているので、手順書として期待すると印象が変わる可能性があります。 - JBuilder / Builder / XML 等の記述が追加・整理されたことで、
- JSON API 中心の構成であることを理解しつつ、
- XML を含む他フォーマットも正式にサポートされていることがドキュメント上も明確になります。
- これから Rails を API モードで使う開発者は、より「一般的な考え方と選択肢」を把握しやすくなる一方、
- 認証関連:
- ガイドに「これをすれば良い」という API 認証の決め打ち例は追加されていないため、
JWT, OAuth2, API キー等の方式については、依然として各プロジェクトのポリシーや外部 Gem による設計が必要です。
- ガイドに「これをすれば良い」という API 認証の決め打ち例は追加されていないため、
- 参考情報 (あれば)
- 対象ガイド:
guides/source/api_app.md(Rails Guides – Using Rails for API-only applications)
- 関連する Rails 組み込み機能 (ガイドから参照されるものの例):
ActionController::APIActionController::HttpAuthentication::Basic/Digest/Tokenなど- JBuilder (Gem) / Builder (XML ビルダー)
- API モードの基本:
rails new my_app --apiオプションで生成されるアプリが、このガイドの主な対象。- ミドルウェア構成や
ActionController::APIベースのコントローラが前提となる。
#58879 Skip the real structure dump test when MySQL's mysqldump targets MariaDB
マージ日: 2026/9/25 | 作成者: @yahonda
- 概要 (1-2文で)
MySQL クライアントのmysqldumpで MariaDB サーバーに対して実行すると失敗するテストtest_structure_dump_output_disables_foreign_key_checksを、該当環境ではスキップするように変更した PRです。これにより、Rails nightly の MariaDB ジョブが MySQL 8.0 クライアントとの組み合わせで落ちる問題を回避します。
- 変更内容の詳細
何が問題だったか
Ubuntu 24.04 ベースの rubylang/ruby:master では、default-mysql-client が MySQL 8.0 になっています。この MySQL 8.0 の mysqldump は information_schema.COLUMN_STATISTICS テーブルを参照しますが、MariaDB にはこのテーブルが存在しないため、以下のようなエラーが発生していました。
mysqldump: Couldn't execute 'SELECT COLUMN_NAME, JSON_EXTRACT(HISTOGRAM, '$."number-of-buckets-specified"') FROM information_schema.COLUMN_STATISTICS ...': Unknown table 'COLUMN_STATISTICS' in information_schema (1109)これにより、test_structure_dump_output_disables_foreign_key_checks が nightly の MariaDB ジョブで落ちていました。
どう判定・スキップするか
テストを実行する DB サーバーが「MariaDB」で、かつ mysqldump のバージョン出力に MariaDB が含まれていない(= MySQL の mysqldump を使っている)場合に、当該テストを skip するように変更しています。
ロジックとしては概ね以下のような形になります(実際のコードを概念化したサンプル):
def test_structure_dump_output_disables_foreign_key_checks
if maria_db_server? && !mysqldump_reports_mariadb?
skip "MySQL mysqldump against MariaDB server does not support COLUMN_STATISTICS"
end
Dir.mktmpdir do |dir|
dump_file = File.join(dir, "structure.sql")
# ここで実際に `db:structure:dump` を実行し、dump_file を検証する
# ...
end
end
def maria_db_server?
# 接続しているサーバーの `SELECT VERSION()` などから MariaDB を判定
end
def mysqldump_reports_mariadb?
`mysqldump --version` =~ /MariaDB/
end実際には、以下の2ファイルで同様のロジックが追加されています。
activerecord/test/cases/adapters/mysql2/mysql2_rake_test.rbactiverecord/test/cases/adapters/trilogy/trilogy_rake_test.rb
どちらも MariaDB サーバー + MySQL クライアントの組み合わせを考慮して、条件付きでテストをスキップするようになっています。
一時ファイルの扱いの改善
もともとは ensure 節で FileUtils.rm_f dump_file のようにダンプファイルを削除していましたが、skip でテストを抜ける場合、dump_file が nil のままで rm_f(nil) が呼ばれうるという問題がありました。
これを Dir.mktmpdir ブロック内でダンプファイルを扱うようにし、ブロックを抜けたタイミングですべて自動削除される設計に変更しています。これにより:
skipされたケースでもnilに対する削除呼び出しが発生しないMinitest/SkipEnsure(skip と ensure の組み合わせに関する RuboCop ルール) にも準拠
というメリットがあります。
- 影響範囲・注意点
影響範囲
- 対象: Active Record の MySQL 系アダプタ用 rake テスト
mysql2アダプタ用テストtrilogyアダプタ用テスト
- 特に MariaDB サーバー + MySQL 8.0 の
mysqldumpという組み合わせに限定した変更です。 - MariaDB 純正の
mysqldump(mariadb-dump) + MariaDB サーバーの組み合わせは通常の CI (Debian ベースの Ruby イメージ) で引き続きテストされます。
- 対象: Active Record の MySQL 系アダプタ用 rake テスト
本番環境への影響
- この PR は「テストコードのみ」の変更であり、Rails 本体の動作や本番環境での挙動には影響しません。
- ただし、「MariaDB サーバーに対して MySQL の
mysqldumpを使う構成では、db:structure:dumpに関するテストが通らない」ことを前提としてスキップしているため、本番でも同じ組み合わせを使う場合は、上記エラーが実際に発生しうる点には注意が必要です。
テスト網羅性の意味合い
- MariaDB サーバー + MySQL
mysqldumpの組み合わせについては、test_structure_dump_output_disables_foreign_key_checksでの動作確認がカバーされなくなります。 - しかし MariaDB 側が
COLUMN_STATISTICSを実装しない方針である以上(後述)、この組み合わせでのテストを継続しても CI が不安定になるだけ、という判断です。
- MariaDB サーバー + MySQL
- 参考情報 (あれば)
MariaDB が
information_schema.COLUMN_STATISTICSを実装しないとしたチケット- https://jira.mariadb.org/browse/MDEV-16555
- ステータスは「Not a Bug」でクローズ済み。
この PR を適用した rails-nightly ビルド(MariaDB ジョブが通過し、該当テストはスキップされている例)
元の失敗例が見られる rails-nightly ビルド
#58263 [RF-DOCS] Rewrite the debugging guide
マージ日: 2026/9/25 | 作成者: @ayushn21
- 概要 (1–2文で)
Railsガイド「Debugging Rails Applications」がほぼ全面的に書き換えられ、debuggem を中心にした現代的・実践的なデバッグフローに合わせて再構成されました。ビュー用ヘルパやログ、リモートデバッグ、メモリリークなどの章立ても整理され、古い/重複した情報が削除・置き換えられています。
- 変更内容の詳細
ガイド全体の再構成
- 冒頭に「Introduction」セクションを追加
- デバッグの入り口として「どんな方法があるのか」「どこを・どうやってデバッグするか」を概観する、より初心者寄りの導入パラグラフを追加。
- 章立てを整理し、「ビューのデバッグ」「
debuggem でのデバッグ」「web-consoleでのデバッグ」「ログによるデバッグ」「高度なロギング」「メモリリーク」などが明確に分離・再配置されています。 debuggem の説明をガイドの前半に移動し、「Railsでデバッグするならまずこれ」という立ち位置を明確化。
debug gem によるデバッグ
debuggem の章をシンプル化- Railsガイド内では「よく使う機能」に絞り、網羅的な説明は gem の公式ドキュメントへのリンクでカバーする構成に変更。
- 使い方の導入順を見直し
- 最初にもっとも簡潔な
debugger呼び出しを紹介し、その後でbinding.break,binding.bなどを説明するよう流れを変更。
- 最初にもっとも簡潔な
- コンソール内コマンドの説明を整理
p,pp,info,backtrace,outlineなど代表的なコマンドにフォーカス。ppが pretty print であること(出力整形されること)に触れるように修正。
- サンプルコードの見直し
- ブレークポイントの行がわかりやすいように記述を調整(行ハイライトなどの提案も内部コメントに反映)。
- 変数の中身(例:
@post)が空ではないケースを使うなど、実際の挙動がイメージしやすい例に改善。
※本PR本文からは、以下のような使い方が主にカバーされていると推測されます(イメージ):
def show
@post = Post.find(params[:id])
debugger # ここで実行が止まる
endコンソール側で p @post, pp @post, info, backtrace, outline などを使って状態を確認する流れを解説。
リモートデバッグ
- 新たに「Remote debugging」セクションを追加。
- 多人数開発やクラウド・コンテナ上で実行中のプロセスをデバッグするケースを想定し、
debuggem のリモート接続機能を軸に説明。 - 用語(例:サーバープロセス/クライアント、ポートフォワーディング、接続モードなど)を正しく使うよう、入念に調査したうえで記述されており、レビューでもここが重点確認ポイントとして指定されています。
- 想定される内容:
- 本番環境ではなく、基本はステージングやローカルでの利用を推奨。
- ファイアウォールやSSHトンネルなどのネットワーク面の考慮点。
- セキュリティリスク(外部にデバッガポートを開けないこと など)の注意喚起。
ビュー側のデバッグ (View Helpers / web-console)
- 「View Helpers for Debugging」セクションを整理・統合
- ビューでのデバッグ手段を「デバッグヘルパ」と「
web-consolegem」に分けたサブセクション構成に変更。
- ビューでのデバッグ手段を「デバッグヘルパ」と「
debugヘルパの説明を整理- オブジェクトの構造をビューに直接表示する典型的な使い方にフォーカス。
to_yamlビューヘルパの削除debugヘルパと機能がほぼ重複している点から、このセクションを削除し、情報を一本化。
web-consoleセクションの改善web-consoleがサーバ上で任意のRubyコードを評価する仕組みであることを明示し、セキュリティ上の注意点を強調。- 「本番で使うべきでないこと」「不正アクセスされた場合、DBを含め何でも操作され得る」点を WARNING レベルで明示する方向が内部コメントで示されています。
- ビューからブラウザベースのコンソールを開いて、そのリクエストコンテキスト内で状態を確認できるフローを説明。
ロガーと高度なロギング
「What is the logger?」セクションの拡充
- Railsロガーの挙動を、ソースコードを読んだ結果に基づき説明するように拡張。
- 出力先に関する記述をアップデート
- 従来は「
Rails.root/log/<environment>.logに出力」とのみ説明していたのを修正し、新規アプリの production ではデフォルトで STDOUT に出すことも明示(環境に応じた実際の挙動を反映)。
- 従来は「
- ログレベル、ログの送り方(
logger.debug,logger.infoなど)、ブロック形式でのログなどを体系的に説明。- ブロックロギング: ブロックが評価されるのはメッセージが実際にログに出力されるときだけなので、重い処理を無駄に実行せずに済む、という性能上の利点も取り上げ。
- パフォーマンスへの影響は「Impact of Logs on Performance」セクションで詳述。
logger.debug { "Expensive debug info: #{heavy_computation}" }Advanced Logging セクション新設
- これまでバラバラに存在していた詳細設定を「Advanced Logging」として整理:
- Verbose Query Logs
- Verbose Enqueue Logs
- Verbose Redirect Logs
- SQL Query Comments
- Tagged Logging
- 「SQL Query Comments」セクション自体は「トップレベル」ではなく、この「Advanced Logging」の配下に再配置。
- 「Tagged Logging」と「Impact of Logs on Performance」が、SQLクエリコメントとは関係性が薄いとして独立した位置づけに変更。
- SQLデバッグに関するトップセクションを設ける提案も内部コメントにあり、
Relation#explainなどをそこに言及する方針。
メモリリークのデバッグ
- 「Debugging Memory Leaks」セクションを簡略化。
- Webアーカイブへのリンクや、かなり古い(2008年頃の)Valgrind関連記事など、陳腐化した外部リソースへのリンクを削除。
- 代わりに、比較的最近で有用性の高い3本の記事へリンクする形に変更。
- ガイド内では「入り口」としての役割にとどめ、手法の詳細は各外部記事に任せる方向に整理。
古い・不要なセクションの削除・整理
- 「Plugins for Debugging」セクションの削除
- Railsコア・標準構成に含まれない gem を積極的には推奨しない方針に合わせて、外部プラグイン列挙をやめる方向。
- 「References」セクションの削除
- 末尾にまとめてリンクを置くのではなく、本文中にインラインで埋め込む方針に変更。
- 各セクションのフォーマットをRailsガイド全体のスタイルに合わせて整理
- 例: 「Breakpoints」などの小見出しを、箇条書きではなくコードブロックと通常テキストの組み合わせでより一般的なスタイルに修正。
- 影響範囲・注意点
影響範囲
- コード(フレームワーク本体)への変更はなく、Rails Guides(ドキュメント)のみ変更です。
- ただし、新人教育・チーム内ドキュメント・社内Wiki などで「Debugging Rails Applications」ガイドを参照している場合、内容・章立てが大きく変わっているので、リンクしている箇所や引用内容の再確認が必要です。
productionでのログ出力先の説明が現状に合わせて更新されているため、「ガイドを根拠とした過去の資料」で古い説明 (log/production.log前提) をしている部分はアップデートした方がよいです。
注意点
- リモートデバッグや
web-consoleの章では、本番環境での利用を避けること、デバッグ用インターフェイスを外部に出さないこと といったセキュリティ上の注意がより強調されているため、これに合わせて運用ポリシーも明文化しておくことが推奨されます。 - 高度なロギング機能(Verbose Query Logs など)は、性能へのインパクトやログ量の急増 を招く可能性があるため、ガイドに従い本番では慎重に使用してください。特に、「As with other verbose loggers...」といった警告は、WARNING として認識すべき内容です。
- メモリリークに関する古いツール・手法(特にValgrind関連)を前提とした社内ナレッジがある場合は、ガイドで紹介されている新しめの記事・手法もあわせて検討した方がよいでしょう。
- リモートデバッグや
- 参考情報 (あれば)
- 公式
debuggem ドキュメント- https://github.com/ruby/debug
- Railsガイドはここへのリンクを前提に簡略化されているため、詳細なコマンドや高度な使い方はこのドキュメントを参照。
- Active Record の
explain(SQL実行計画の取得)- https://edgeapi.rubyonrails.org/classes/ActiveRecord/Relation.html#method-i-explain
- SQLのボトルネック調査など、SQLまわりのデバッグで重要。
- Rails Guides (Edge) – Debugging Rails Applications
- 最新の書き換え版ガイドを直接確認すると、章立て・サンプルコードの全体像が把握できます。
#57348 [RF-Docs] [ci-skip] Update Active Record Composite Primary Keys Guide
マージ日: 2026/9/25 | 作成者: @bhumi1102
- 概要 (1–2文で)
Active Record の「複合主キー」ガイド (Active Record Composite Primary Keys Guide) を全面的に更新し、コード例や説明を大幅に追加したドキュメント改善の PR です。Rails Foundation のドキュメンテーションプロジェクトの一環であり、実装そのものではなくガイドの内容を最新かつ実践的にすることが目的です。
- 変更内容の詳細
※この PR はガイド (guides/source/active_record_composite_primary_keys.md) のみを変更しており、Rails 本体のコードは変わっていません。以下は、一般的にこの種のガイド更新で行われる内容を軸に、今回想定される更新点を整理しています。
2-1. 複合主キーの基本的な定義方法の明確化・コード例の追加
self.primary_keys = :id1, :id2など、モデルで複合主キーを定義する方法が、より具体的なコード例付きで説明されている可能性が高いです。例(イメージ):
rubyclass Enrollment < ApplicationRecord self.primary_keys = :student_id, :course_id endモデル定義だけでなく、マイグレーションでのカラム定義についても、
primary_key: falseと複合ユニークインデックスの組み合わせといった、「Rails が単一主キーを前提としている箇所の回避パターン」が解説されていることが多いです。
2-2. CRUD 操作・検索における複合主キーの扱い
find/find_byなどに複合主キーをどう渡すかの例が追加されたと考えられます。例(イメージ):
ruby# 複合主キー (student_id, course_id) のレコードを取得 Enrollment.find([student_id, course_id]) # または Enrollment.find_by(student_id: student_id, course_id: course_id)
update,destroy,whereなど、日常的な Active Record 操作が複合主キー環境でどう動作するのか、または注意点がコードスニペット付きで整理されている可能性があります。
2-3. 関連 (association) と複合主キーの連携
belongs_to,has_many,has_one,has_and_belongs_to_manyなどの関連付けにおいて、複合主キーを持つモデルをどう扱うかの例が増えていると考えられます。例(イメージ):
rubyclass Enrollment < ApplicationRecord self.primary_keys = :student_id, :course_id belongs_to :student belongs_to :course end class Student < ApplicationRecord has_many :enrollments, dependent: :destroy has_many :courses, through: :enrollments end中間テーブルが複合主キーを持つケースや、Rails 側は単一主キーを前提としているために、どこを「普通の外部キー + unique 制約」で代替するべきかなど、設計ガイドライン的な説明が強化されていることが多いです。
2-4. バリデーション・コールバック・スコープなどとの組み合わせ
複合主キーを利用しつつ、
validates、before_save、scopeなどをどう書くかを示すコード例が追加されていると考えられます。例(イメージ):
rubyclass Enrollment < ApplicationRecord self.primary_keys = :student_id, :course_id validates :student_id, :course_id, presence: true scope :for_student, ->(id) { where(student_id: id) } end
2-5. 制約・限界・ベストプラクティスの明記
- Rails コアは基本的に「単一主キー(通常
id)」を前提に設計されているため、複合主キーにはいくつかの制約や非推奨パターンがあります。 - ガイド更新では、以下のようなポイントについて、より明確な説明・注意書きが追記されている可能性が高いです。
polymorphicな関連での扱いfixtures/Active Storage/Action Textなど他コンポーネントとの連携制限to_param,friendly_id的な URL 生成との組み合わせ- 将来の移行(複合主キーから単一主キーへの切り替え)を見据えた設計指針
2-6. ガイド全体の構成改善
- 見出し構成や章立てが整理され、「概要 → セットアップ → CRUD → 関連 → 応用/制約 → トラブルシュート」のような読み進めやすい流れに再編成されている可能性があります。
- 追加行数が削除行数を大きく上回っている(+236 / -114)ため、単なる文言修正ではなく、「実用的な例を増やした」「セクションを追加した」「既存説明を大幅に書き換えた」類の更新と考えられます。
- 影響範囲・注意点
- コードベースへの影響はなし
変更はガイド (ドキュメント) のみであり、Active Record やその他 Rails コンポーネントの挙動・API には一切変更がありません。 - 複合主キーを使っている/検討している開発者への影響
- 設計や実装のベストプラクティスがより明確になるため、新規導入時の判断や既存実装の見直しに役立ちます。
- ガイドのサンプルコードに合わせて自分のコードを修正したくなるケースはありますが、それはあくまで任意のリファクタリングです。
- アップグレード時の互換性
- 本 PR 単体としてはアップグレードに伴う互換性問題はありません。
- ただし、ガイドが最新の推奨パターン・制約を明示した結果、「今まで暗黙的にやっていたけど避けた方がいい」パターンが見つかる可能性はあります。
- 参考情報 (あれば)
- PR: https://github.com/rails/rails/pull/57348
- 変更ファイル:
guides/source/active_record_composite_primary_keys.md - 関連トピック:
- Rails のガイド一覧: https://guides.rubyonrails.org/
- Rails Foundation docs プロジェクトの活動内容や背景は、Rails ブログや Foundation のアナウンスが参考になります。
#58855 Drop needless schema qualification from db/schema.rb on PostgreSQL
マージ日: 2026/9/25 | 作成者: @yahonda
- 概要 (1-2文で)
PostgreSQL 環境で生成されるdb/schema.rbから「不要なスキーマ修飾」を取り除き、拡張機能やテーブル/enum 名に余計なpublic.やtiger.などを付けないようにした PR です。これにより、Rails 8.0 相当のシンプルな出力形式に戻しつつ、複数スキーマ利用時でも新規DBへのロードが失敗しにくくなります。
- 変更内容の詳細
背景・問題点
従来の db/schema.rb では、PostgreSQL 拡張やテーブル・enum をダンプする際、スキーマ名を常にプレフィックスとして付けていました。
例:
enable_extension "pg_catalog.plpgsql"
enable_extension "tiger.postgis_tiger_geocoder"
create_enum "public.mood", ["happy"]
create_table "public.posts", force: :cascade do |t|
...
endこの挙動には2つの問題がありました。
拡張のスキーマ固定との不整合によるエラー
- 一部の拡張(例:
postgis_tiger_geocoder)は「拡張のコントロールファイル側でスキーマが固定」されており、CREATE EXTENSION ... SCHEMA xxxを受け付けません。 - しかし
schema.rbはtiger.postgis_tiger_geocoderのようにスキーマ付きで出力し、再実行時にCREATE EXTENSION postgis_tiger_geocoder SCHEMA tigerのような形になります。 - その際、
tigerスキーマがまだ存在しなかったり、schema_search_pathに含まれていない環境だとPG::InvalidSchemaName: schema "tiger" does not existで新規 DB ロードに失敗します。
- 一部の拡張(例:
複数スキーマが有効なだけで全テーブル/enum にスキーマ接頭辞が付く
schema_search_pathにpublic以外のスキーマ(例:postgis)が入るだけで、create_table "posts"がcreate_table "public.posts"という、通常のマイグレーションではあまり使わない表記になります。- 可読性が落ちるうえ、Rails 8.0 までの挙動と乖離していました。
この PR の挙動変更
1. 拡張 (enable_extension) のスキーマ付与ポリシーの変更
新ルール:
拡張のコントロールファイル側でスキーマが固定されている場合
→schema.rbでは スキーマ名を付けずに ダンプする。ruby# 変更前 enable_extension "tiger.postgis_tiger_geocoder" # 変更後 enable_extension "postgis_tiger_geocoder"- ロード時は単に
CREATE EXTENSION postgis_tiger_geocoderが実行され、PostgreSQL 側がコントロールファイルに従って適切なスキーマ(例:tiger)を自動生成・利用します。 tigerスキーマが事前に存在しなくてもよいため、新規DBのセットアップが安定します。
- ロード時は単に
アプリ側が
CREATE EXTENSION ... SCHEMA xxxで明示的にスキーマを指定した拡張
→ これまで通り、スキーマ付きでダンプを維持。ruby# 例: 明示的にスキーマ指定して作られた拡張 enable_extension "heroku_ext.pgcrypto"- これは
heroku_extなど、ユーザが意図的にスキーマを分けているケースを壊さないためです。
- これは
この判定には PostgreSQL の pg_available_extension_versions 情報を利用し、「スキーマがコントロールファイルで固定されている拡張かどうか」を見ています。
2. テーブル・enum 名のスキーマ接頭辞の扱い変更
新ルール:
- ダンプ元の「現在のスキーマ(current schema)」に存在するテーブル・enum については プレフィックスを付けずに出力。
- 現在のスキーマ以外にあるテーブル・enum については、これまで通り スキーマ名をプレフィックスとして保持。
例 (schema_search_path: public, postgis の場合):
# 変更前
create_enum "public.mood", ["happy"]
create_table "public.posts", force: :cascade do |t|
...
end
# 変更後
create_enum "mood", ["happy"]
create_table "posts", force: :cascade do |t|
...
end- これは既に
enum_typesでは採用していたポリシー(「current schema にある型は無修飾で吐く」)をテーブルにも広げた形です。 - 無修飾名でダンプすれば、ロード時も「接続の
schema_search_pathに依存する」挙動になり、単一スキーマ運用時のこれまでの前提を壊さずに済みます。
3. 具体的な before / after
schema_search_path: public, postgis の例:
変更前:
create_schema "postgis"
enable_extension "pg_catalog.plpgsql"
enable_extension "postgis.postgis"
enable_extension "tiger.postgis_tiger_geocoder"
create_enum "public.mood", ["happy"]
create_table "public.posts", force: :cascade do |t|
...
end変更後:
create_schema "postgis"
enable_extension "plpgsql"
enable_extension "postgis.postgis"
enable_extension "postgis_tiger_geocoder"
create_enum "mood", ["happy"]
create_table "posts", force: :cascade do |t|
...
endポイント:
plpgsql拡張はpg_catalog修飾が消えて単純なplpgsqlに。- PostGIS の
postgis_tiger_geocoderはtiger.修飾が消え、素の拡張名だけに。 - enum とテーブルは
public.修飾が消え、Rails 8.0 時代の見慣れた形に戻る。
- 影響範囲・注意点
対象:
- PostgreSQL を利用し、
db/schema.rbを使ってスキーマを管理している Rails アプリ。 - 特に PostGIS /
postgis_tiger_geocoder、複数スキーマ(schema_search_pathをカスタマイズ)を利用しているプロジェクト。
- PostgreSQL を利用し、
ポジティブな影響:
postgis_tiger_geocoderなど「スキーマ固定拡張」を使っている環境で、新規 DB へのdb:schema:loadがPG::InvalidSchemaNameでコケる問題を解消。schema.rbが Rails 8.0 同様の短く読みやすい形式に戻り、レビューや差分確認がしやすくなる。- 拡張周りの挙動が PostgreSQL 標準の
CREATE EXTENSIONの挙動に近づき、手動操作との整合性が取りやすくなる。
互換性・注意点:
schema.rbからスキーマ接頭辞が減るため、ダンプの diff が大きく変わる可能性があります(内容としては意味は同じか、より正確になっています)。- 無修飾でテーブル名/enum 名が出力されるため、ロード時の解決先スキーマは
schema_search_pathに依存します。これは従来(単一スキーマ運用時)と同じ前提ですが、複数スキーマを積極的に使っている場合は、ロード時のschema_search_path設定を明示的に確認しておくのが安全です。 - 拡張をわざと
CREATE EXTENSION ... SCHEMA custom_schemaしている場合は、従来通りスキーマ付きでダンプされるため、今回の変更による挙動差はほぼありません。
- 参考情報 (あれば)
- 対応 issue: #58834
- 関連 PR: #50020
- 変更ファイル(主なもの):
activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb
→ 拡張・テーブル・enum のダンプロジックを調整。activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
→ 利用可能拡張のスキーマ情報の扱いを拡張。activerecord/test/cases/adapters/postgresql/...
→ 上記仕様変更をカバーするテストを追加・更新。guides/source/active_record_migrations.md
→ ガイドの例や説明を新しい挙動に合わせて微修正。
- PostGIS / tiger geocoder の仕様変更関連:
- [1] add_search_path.sql.in
- [2] postgis_tiger_geocoder 2025.2 README
- [3] 2026.1 で search_path 設定を任意にするコミット
#58875 Schema-qualify the regclass cast in check_all_foreign_keys_valid!
マージ日: 2026/9/25 | 作成者: @sedubois
- 概要 (1-2文で)
Rails 8.1.4 で導入されたcheck_all_foreign_keys_valid!のバグにより、PostgreSQL (< 18.4) 環境でスキーマ付きテーブルの外部キー検証が失敗またはすり抜ける問題を、regclassキャスト時にスキーマを明示することで修正する PR です。フィクスチャ読み込み時のPG::UndefinedTableや、別スキーマの外部キー違反の見逃しを防ぎます。
- 変更内容の詳細
何が問題だったか
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter#check_all_foreign_keys_valid! は、PostgreSQL < 18.4 の場合に以下のようなロジックで外部キーを検証しています(概略):
pg_constraintから各外部キーを列挙- 対象外部キーの
convalidatedをfalseに更新 VALIDATE CONSTRAINTで再検証- どこかで外部キー違反があれば例外を投げる
このとき、pg_constraint.conrelid(制約が付いているテーブルの OID)をテーブル名にキャストする箇所で、次のようにスキーマ無しで regclass キャストしていました(簡略化したイメージ):
conrelid::regclass = 'table_name'::regclassPostgreSQL では 'table_name'::regclass は search_path を使ってテーブルを解決するため、以下の問題が発生します。
search_path 外のスキーマにあるテーブル
- 例: テーブル
solid_queue.jobsに外部キーがあるが、search_pathにsolid_queueが入っていない 'jobs'::regclassの解決に失敗し、PG::UndefinedTable: relation "jobs" does not existが発生- フィクスチャ読み込み時(
verify_foreign_keys_for_fixtures = true)などで毎回失敗する
- 例: テーブル
同名テーブルが search_path 上に存在する場合
- 例:
public.childrenとother.childrenが両方存在し、実際に検証すべきはother.childrenの外部キー 'children'::regclassはpublic.childrenに解決されてしまう- その結果、
UPDATE/VALIDATEの対象が誤ったテーブルになり、本来検出すべき別スキーマの外部キー違反がスルーされる
- 例:
PR 説明中の再現コードでは、other スキーマにある other.children の外部キーがこの問題に引っかかり、フィクスチャ読み込み時に PG::UndefinedTable が発生することが示されています。
# search_path に other が入っていない想定
create_table "other.parents", force: true
create_table "other.children", force: true do |t|
t.references :parent, foreign_key: { to_table: "other.parents" }
end
ActiveRecord.verify_foreign_keys_for_fixtures = true
ActiveRecord::FixtureSet.create_fixtures(dir, ["posts"])
# => PG::UndefinedTable: ERROR: relation "children" does not exist何を変えたか
regclass キャスト時に、制約側のスキーマ名を SQL 上で明示するように変更しています。
変更前(概念的なイメージ)
conrelid::regclass = '%2$I'::regclass変更後
conrelid::regclass = '%2$I.%3$I'::regclass%2$I: テーブル名%3$I: スキーマ名
(実際にはformat系のプレースホルダを使って SQL を動的生成しています)
これにより、'schema_name.table_name'::regclass の形で必ずスキーマ修飾されたテーブル名として解決されるため、search_path の影響を受けません。
テストの追加
activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb に 2 つのテストが追加されています。
- 新規テストは以下を確認:
- search_path 外のスキーマにあるテーブルの外部キーがあっても、
check_all_foreign_keys_valid!が落ちないこと - 同名テーブルが別スキーマにあっても、正しいスキーマの外部キーが検証されること
- search_path 外のスキーマにあるテーブルの外部キーがあっても、
既存の test_all_foreign_keys_valid_having_foreign_keys_in_multiple_schemas は、public.nodes が存在する都合で「2 の問題(誤解決)」に隠されてしまい、1 のエラーが顕在化していませんでした。追加テストではこの点を明確に検証しています。
なお、この修正は PostgreSQL 18.4 以降の「NOT ENFORCED を使うパス」にも悪影響はなく、PG 14.5 と PG 18.6 の両方でテストパスすることが確認されています。
- 影響範囲・注意点
対象バージョン
- Rails 8.1.4 で導入された回りのコードへの修正(回帰バグの修正)
- PostgreSQL < 18.4 のコードパスにのみ関係
- 18.4 以降(
NOT ENFORCEDパス)もテストされており、挙動は維持されます
影響する場面
ActiveRecord.verify_foreign_keys_for_fixtures = trueが有効な状態でのフィクスチャロード- アプリ内で
ActiveRecord::Base.connection.check_all_foreign_keys_valid!を直接呼んでいる場合 - 特に、複数スキーマ構成(
search_path外に業務用スキーマを切る設計)で以下のケースに該当する場合- search_path に含まれないスキーマにテーブルと外部キーを定義している
- search_path 上に同名テーブルが存在する(
public.childrenとother.childrenなど)
修正による挙動の変化
- これまで 8.1.4 でフィクスチャロードが
PG::UndefinedTableで落ちていたケースは正常に通るようになります - これまで誤って「外部キーエラーなし」と判定されていた別スキーマ上のテーブルは、正しく違反検知されるようになります
- 検証対象テーブルの特定がより厳密(スキーマ付き)になっただけで、アプリケーションレベルのクエリには影響しません
- これまで 8.1.4 でフィクスチャロードが
注意点
- この PR 自体は SQL のスキーマ修飾を 1 カ所変えているだけですが、これまでバグにより見逃されていた外部キー違反が CI 等で突然検出される可能性があります
- 特に、スキーマ分割 + search_path チューニングをしているプロジェクトは要注意
- 回帰は 8.1.4 限定なため、8.1.3.1 以前から 8.1.5(相当版)以降に上げるケースでは
- 「8.1.4 で壊れる」→「修正された状態に戻る」形になるので、現行が 8.1.3 系なら動作イメージは 8.1.3 に近いです(ただし検証方法自体は 8.1.4 系のまま)
- この PR 自体は SQL のスキーマ修飾を 1 カ所変えているだけですが、これまでバグにより見逃されていた外部キー違反が CI 等で突然検出される可能性があります
- 参考情報 (あれば)
- 元の回帰導入 PR: #53636(Rails 8.1.4 で導入)
- 同問題に対する過去の提案 PR(動機が不明確としてクローズ): #57428
- 古い PostgreSQL 用のコードパスを残す PR: #58055
- 本 PR: #58875 (この説明の対象)
- 関連する再現スクリプトは PR 本文に記載されており、
Rails main + PostgreSQL 14で 8.1.3.1 → 8.1.4 による挙動差が確認できます。
#58873 Fix typos in Layouts and Rendering guide [ci skip]
マージ日: 2026/9/25 | 作成者: @bastin27
概要 (1-2文で)
Layouts and Rendering ガイド (guides/source/layouts_and_rendering.md) に含まれていたスペルミスを修正する、ドキュメントのみの変更です。アプリケーションコードや挙動には一切影響しません。変更内容の詳細(あればサンプルコードも含めて)
対象:guides/source/layouts_and_rendering.mdのみ(+8 / -8 行)
修正されたスペルミス:
- セクション見出し
Hierarcy→Hierarchy- レイアウトやテンプレートの「階層構造」を説明するセクション見出しのタイポ修正。
- 本文中の単語
reponses→responses- コントローラが返す HTTP レスポンス等を説明している箇所のタイポ修正。
explicity/Explicity/Explitly→explicitly/Explicitly- レイアウトやテンプレートを「明示的に」指定する説明で、同じ単語のスペルミスが複数箇所あったものを統一して修正。
stucturally→structurally- ビューやレイアウトの構造的な側面を説明している文でのタイポ修正。
いずれも英単語の綴りのみを変更しており、文章の意味やサンプルコードの挙動に変更はありません。
- 影響範囲・注意点
- 影響範囲
- Rails Guides(英語版ドキュメント)の「Layouts and Rendering」ガイドに限定されます。
- フレームワーク本体のコード、API、挙動、互換性には一切変更がありません。
- 注意点
- ドキュメントを引用しているブログ記事・資料などで、古い綴りのまま残っている可能性がありますが、機能的な問題にはなりません。
- ガイドを自前で翻訳・ミラーしている場合は、該当ファイルの差分を取り込むことで誤記を解消できます。
- 参考情報 (あれば)
- PR: https://github.com/rails/rails/pull/58873
- Rails ドキュメント貢献ガイド:
https://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#contributing-to-the-rails-documentation - 対象ガイド (edge guides):
https://edgeguides.rubyonrails.org/layouts_and_rendering.html
#58872 Fix BigInteger casting truncating strings to 16 bytes
マージ日: 2026/9/25 | 作成者: @SulimanAbdulrazzaq
- 概要 (1-2文で)
BigInteger と scale なし decimal カラムに対する文字列 → 整数キャスト時に、先頭 16 バイトで暗黙に切り捨てられていたバグを修正する PR です。Integerのバイト数上限の考え方を整理しつつ、BigIntegerでは精度(precision)に応じたより妥当な上限を使うように変更しています。
- 変更内容の詳細
何が問題だったか
Rails 6ee1adf7d 以降、ActiveModel::Type::Integer#cast_value は「文字列から整数にキャストする際、最大 _limit * 4 バイトだけ読む」という制限を入れていました。BigInteger は理論上「上限なしの整数型」ですが、Integer を継承してこの制限をそのまま引き継いでいたため、limit がない場合にはデフォルトの 16 バイト(= 16 文字程度)で切り捨てられてしまっていました。
その結果、例えば以下のようなケースで値が途中で切れていました:
ActiveModel::Type::BigInteger.new.cast("18446744073709551615")
# 本来の期待値: 18446744073709551615
# バグありの挙動: 1844674407370955同様に、Active Record の decimal で scale なし (decimal(20, 0) など) のカラムは DecimalWithoutScale(BigInteger のサブクラス)を使うため、同じ問題の影響を受けていました。
どう直したか
1. Integer に「最大キャストバイト数」を返すメソッドを切り出し
ActiveModel::Type::Integer に、キャストする文字列のバイト数上限を返すプライベートメソッドを追加:
# 疑似コードイメージ
class ActiveModel::Type::Integer
private
def max_cast_bytesize
_limit * 4
end
endcast_value はこの max_cast_bytesize を参照するようになり、挙動としては従来と同じ(Integer に関しては挙動変更なし)です。
2. BigInteger が上限計算をオーバーライド
ActiveModel::Type::BigInteger で max_cast_bytesize をオーバーライドし、「precision ベースの上限」を使うように変更:
precisionがある場合:precision * 2バイト- 例:
decimal(20, 0)→ precision 20 → 40 バイトを上限にキャスト - これは元の変更意図(符号やスラッグサフィックスが付く余地を残す)を尊重した設定
- 例:
precisionがない(単純な:big_integer属性)の場合:
各種 DB が numeric カラムで受け付ける最大 precision のうち最大値(PostgreSQL の 1000 桁)× 2 → 2000 バイトを上限に設定
これにより、
DecimalWithoutScale(decimal(20, 0)やdecimal(55, 0)など) も precision に応じた十分なバイト数を読める- 一方で、
to_iが極端に長い文字列に対して無制限に処理することは避けられ、元の変更(6ee1adf7d)のパフォーマンス・安全性の狙いも維持される
3. テストの追加
追加されたテストは主に以下のパターンをカバーしています。
activemodel/test/cases/type/big_integer_test.rb- precision 指定なしの
BigIntegerに対して- 20 桁文字列
- 31 桁文字列 をキャストしても切り捨てられないこと
- precision を設定した場合に、
precision * 2バイトという上限で正しく動作すること - precision なしのときに 2000 バイト上限が有効であること
- precision 指定なしの
activerecord/test/cases/numeric_data_test.rbdecimal(20, 0)およびdecimal(55, 0)のカラムに長い数値文字列を代入しても途中で切れず期待値どおりに保存・読み出しできること
これらの新しいテストは、現行 main ブランチでは
- Expected:
18446744073709551615 - Actual:
1844674407370955
のように失敗しますが、この PR 適用後は通るようになります。
SQLite 上で Active Model・Active Record の関連テストスイートが通ること、および RuboCop で変更ファイルに問題がないことも確認されています。
- 影響範囲・注意点
- 影響を受ける型
ActiveModel::Type::BigInteger- Active Record の
decimal(N, 0)(scale なし)のカラム(DecimalWithoutScale経由)
- 現象面の変化
- 以前は「長い数値文字列を渡すと勝手に途中で切れる」という不正な挙動だったので、今回の変更は主に「本来の値がそのまま扱えるようになる」というバグ修正です。
- そのため、もしアプリケーション側が「切り捨てられた値」に依存している場合は挙動が変わりますが、それ自体は本来の意図から外れた利用であり、今回の修正が正しい形といえます。
- パフォーマンス・安全性
Integer型はこれまでどおり_limit * 4バイトを上限とするので、変更なし。BigIntegerも precision や 2000 バイトの上限により無制限ではないため、極端に長い文字列によるto_iの過負荷を一定程度防げます。
- DB ごとの precision 制限との差
- precision なしの
BigIntegerは「PostgreSQL の numeric 最大桁数 1000 桁」を基準に 2000 バイトを使っています。
実際に接続している DB によっては許容桁数の上限が異なる可能性はありますが、「Ruby 側で文字列を整数化する処理」としては十分に緩い上限であり、現実的には問題になりにくい設定です。
- precision なしの
- 参考情報 (あれば)
- 関連 Issue: #58871(この PR で修正)
- 対象コミット: 6ee1adf7d で導入された「文字列→整数キャスト時のバイト数制限」が背景
- 変更ファイル:
activemodel/lib/active_model/type/integer.rbmax_cast_bytesizeメソッドの導入
activemodel/lib/active_model/type/big_integer.rbmax_cast_bytesizeのオーバーライド(precision / 2000 バイトロジック)
- テスト:
activemodel/test/cases/type/big_integer_test.rbactiverecord/test/cases/numeric_data_test.rb
#58306 Active Record Ractor-friendly connection handling
マージ日: 2026/9/25 | 作成者: @gmcgibbon
- 概要 (1-2文で)
このPRは、Rails を Ractor 化した際に「メイン Ractor 以外から Active Record のクエリを実行できるようにする」ためのコネクションハンドリングを導入するものです。非 Ractor 化アプリには挙動変更を与えず、非メイン Ractor でのクエリはすべてメイン Ractor にディスパッチして実行されます。
- 変更内容の詳細
全体像
- メイン Ractor かどうかに応じて
ActiveRecord::Base.connection_handlerの実装を切り替える。 - 非メイン Ractor 上では「プロキシ用コネクションハンドラ・プール・アダプタ」を使い、実際の DB 処理はメイン Ractor 側のアダプタで実行される。
- プロキシは
Ractorのメッセージパッシングを使って、クエリ内容をメイン Ractor に送り、結果を受け取る。
Rails アプリ側からの見た目としては、非メイン Ractor 上でも通常どおり Model.where(...).to_a 等が書けるが、そのクエリ実体はメイン Ractor で処理される、というモデルです。
主な新規コンポーネント
1) ActiveRecord::ConnectionAdapters::RactorConnectionHandler
- 非メイン Ractor 用のコネクションハンドラ。
- メイン Ractor で動いている「本物の」
ConnectionHandlerと、非メイン Ractor 側の「プロキシ」を仲介する役割。 #retrieve_connection_poolなどの API は既存のConnectionHandlerとインターフェイス互換だが、返すのは実 DB 接続ではなく「プロキシ用の connection_pool」。
2) プロキシ用 Connection Pool: ProxyConnectionPool
activerecord/lib/active_record/connection_adapters/ractor_connection_handler/proxy_connection_pool.rb
ConnectionPoolと同様の API を持つが、中に保持するのは「実コネクション」ではなく「プロキシアダプタ」。with_connection/connectionなどを呼ぶと、プロキシアダプタを返す。- 実 DB アダプタに対応するプロキシアダプタを生成・キャッシュして利用。
3) プロキシアダプタ: AbstractProxyAdapter と各 DB 用実装
- 共通基底:
activerecord/lib/active_record/connection_adapters/ractor_connection_handler/abstract_proxy_adapter.rb - MySQL 用:
mysql_proxy_adapter.rb - PostgreSQL 用:
postgresql_proxy_adapter.rb
これらは AbstractAdapter を継承した「見かけ上の」アダプタです。ただし DB とは直接話さず、Ractor 間メッセージを通じてメイン Ractor 上の「本物のアダプタ」に処理を委譲します。
4) Ractor 間でやり取りするリクエスト/レスポンスオブジェクト
query_request.rbquery_response.rbresult.rbschema_creation_proxy.rb
などが導入されています。
役割のイメージ:
QueryRequest:
実行したい SQL、バインド変数、トランザクション/接続コンテキストなどを保持。QueryResponse:
実行結果、例外情報などを格納して、非メイン Ractor 側に返送。Result:ActiveRecord::Result相当の情報を、Ractor で安全にやり取りできる形に変換。SchemaCreationProxy:
マイグレーションやスキーマ定義時の DDL 実行をメイン Ractor にディスパッチするためのプロキシ。
抽象アダプタ/トランザクション/ステートメントへの変更
abstract_adapter.rb
- 非メイン Ractor からも扱えるように、各種メソッドが「プロキシ化」されることを前提としたリファクタリングやフックが追加されています。
- 主なポイント:
- クエリ実行やトランザクション管理を、Ractor セーフな形で抽象化 (例:
execute,select_all,begin_db_transactionなどをプロキシ経由でも流せるようにするための最低限の分解と責務分離)。 - 結果オブジェクトのシリアライズ/デシリアライズを行えるフック (プロキシアダプタが利用)。
- クエリ実行やトランザクション管理を、Ractor セーフな形で抽象化 (例:
- このファイルの変更が最も行数が多く、Ractor 対応のための基盤となっています。
abstract/database_statements.rb
- クエリ/ステートメント実行周りに Ractor 用ディスパッチのための処理が追加されています。
select_all,exec_queryなどの中で、実アダプタまたはプロキシアダプタが適切に呼び出されるように整理。
abstract/transaction.rb
- トランザクションの開始/コミット/ロールバックなどを、Ractor 越しに実行できるようにするための対応。
- 非メイン Ractor で
ActiveRecord::Base.transactionを使っても、メイン Ractor 上のコネクションでトランザクションが張られるように設計されている。
MySQL / PostgreSQL アダプタの Ractor 対応
abstract_mysql_adapter.rb,postgresql_adapter.rbに少量の変更が入り、Ractor プロキシからの呼び出しを受けられるようになっています。- 各アダプタ固有の型変換・結果処理を、
Resultプロキシクラスで再構築できるようにするためのフックや補助メソッドが追加。
Connection Pool / PoolConfig 周り
connection_pool.rb/pool_config.rbに、Ractor 対応のコネクションハンドラから呼び出されるための拡張。PoolConfigは、プロキシ用プールを初期化する際、どのアダプタ/設定を使うかといったメタ情報をメイン Ractor から引き継ぐ。
実行フローのイメージ
# メイン Ractor
ActiveRecord::Base.establish_connection(:primary)
# 非メイン Ractor 側
r = Ractor.new do
# Ractor 内でも普通に Active Record を呼ぶ
User.where(active: true).limit(10).to_a
end
r.takeUser.where...でconnectionを取得しようとすると、非メイン Ractor なのでRactorConnectionHandlerが利用される。RactorConnectionHandlerはProxyConnectionPoolを返す。ProxyConnectionPoolはMysqlProxyAdapter/PostgresqlProxyAdapterなどのプロキシアダプタを返す。- プロキシアダプタは、(あらかじめ保持している) メイン Ractor のエンドポイントに
QueryRequestを送信。 - メイン Ractor 側で本物の
Mysql2Adapter/PostgreSQLAdapterなどを使ってクエリ実行。 ActiveRecord::ResultなどをResultプロキシとしてシリアライズ可能な形に変換し、QueryResponseとして返す。- 非メイン Ractor 側で
Resultを元にActiveRecord::Result相当を組み立て、通常のクエリ結果オブジェクトとしてアプリに返す。
Rakefile / テストまわり
activerecord/Rakefileに Ractor 対応を検証するためのタスクやテストランナー拡張が追加されている可能性が高いです。- PR 説明どおりテストは追加/更新されており、Ractor 環境でのクエリ実行/トランザクションなどがカバーされています。
- 影響範囲・注意点
影響範囲
- 非 Ractor 化アプリケーション
- 「メイン Ractor チェック」で旧来の接続ハンドラをそのまま使うため、原則として挙動変更はありません。
- ただし
AbstractAdapterやdatabase_statements,transactionの内部実装が変わっているので、内部 API に依存している gem / 独自アダプタは影響を受ける可能性があります。
- Ractor 化アプリケーション
- メイン Ractor 以外からの Active Record 利用が正式にサポートされる方向になります。
- 非メイン Ractor からの DB アクセス性能は、「Ractor 間メッセージ + メイン Ractor 内 DB 実行」というオーバーヘッドを伴うため、スループットやレイテンシ特性は今までと異なります。
注意点
DB アクセスは最終的にメイン Ractor に集約される
- 実 DB コネクションはメイン Ractor にしか存在しない設計のため、Ractor ごとに独立した DB 接続を持つわけではありません。
- CPU バウンドな処理を Ractor で並列化するのが主目的であり、DB への同時接続数を増やすものではありません。
Ractor セーフでないオブジェクトをクエリに含めないこと
- Ractor 間メッセージとして送る
QueryRequestは、Ractor で共有可能なオブジェクトだけを含む必要があります。 - 通常の
String/Hash/Array/ 数値などで構成される SQL とバインド値であれば問題ないはずですが、独自クラスや Proc などを直接バインドに渡すような変わった使い方は破綻する可能性があります。
- Ractor 間メッセージとして送る
独自アダプタ・接続プールの拡張
- サードパーティの DB アダプタや ActiveRecord 接続周りを拡張する gem は、この Ractor プロキシの存在を考慮していない場合があります。
- 特に
AbstractAdapterやConnectionPoolの内部にフックしているコードは、Ractor 対応に追随する必要が出てくるかもしれません。
まだ nodoc・内部 API 扱い
- この PR の機能は
nodocと明記されており、外部ドキュメント化されていません。 - Rails 内部での実験的な位置づけであり、将来バージョンで API 形状が変わる可能性が高いです。
- これを前提にした独自コードを書く場合は、Rails のバージョンアップ時に追従コストが発生することを前提にした方が良いです。
- この PR の機能は
- 参考情報 (あれば)
- この PR は Rails の Ractor サポート強化の一環であり、Ruby 本体の Ractor モデル (共有不可能なオブジェクトの制約、メッセージパッシングによる通信) に沿って Active Record を設計し直そうとしている点が特徴的です。
- CHANGELOG は更新されていないため、正式な公開 API というより「次期バージョンのための基盤整備/実験」という位置づけと考えると理解しやすいです。
- Ractor を使った Rails アプリ実装を検討する際には、この PR の「DB アクセスはメイン Ractor 集約」という前提を踏まえたスレッディング/並列化設計が必要になります。
#58874 Manually specify security validator in herb checker
マージ日: 2026/9/25 | 作成者: @gmcgibbon
- 概要 (1-2文で)
Herb 0.11 から「セキュリティバリデータ」がデフォルトで使われなくなったため、Rails 側で明示的に security validator を指定するようにした PRです。これにより、使用する Herb のバージョンにかかわらず、Herb チェッカーの挙動を一貫してセキュアなものに保ちます。
- 変更内容の詳細
背景
- Herb 0.11 でデフォルト動作が変わり、「セキュリティバリデータ」が自動的には使われなくなった。
- Rails 側の期待値としては、これまで通り security validator が有効である前提で動いていたため、そのギャップを埋める必要があった。
主な変更点
1) action_view/herb_checker.rb でセキュリティバリデータを明示指定
ActionView::HerbChecker が Herb を実行する際に、利用するバリデータとして「security validator」を明示的に渡すように変更されています。
擬似的なイメージ(実際のコード構造を簡略化):
# 変更前(イメージ)
Herb::Checker.run(source)
# 変更後(イメージ)
Herb::Checker.run(
source,
validator: :security # もしくは Herb::SecurityValidator など
)これにより、
- Herb 0.11 以前: もともと security validator がデフォルト → 明示指定しても挙動は同じ
- Herb 0.11 以降: デフォルトでは security validator が使われない → 明示指定により、従来と同じセキュリティチェックが行われる
という状態になります。
2) Gemfile.lock の更新
Gemfile.lock にも 10 行追加・10 行削除が入っていますが、これは依存関係の解決結果の更新(Herb やその依存、または付随する gem のバージョン差分)と考えられます。
この PR の主目的はあくまで「Herb チェッカーの validator 明示化」であり、Gemfile.lock の差分はそれを成立させるための付随的な変更です。
- 影響範囲・注意点
影響範囲
- 対象:
ActionView::HerbCheckerを通じて Herb による検査(lint / チェック)を行っている箇所 - Herb のバージョンに依存せず、常に security validator を使ったチェックが走るようになります。
これにより:
- Herb 0.11 以降を使用している場合
→ これまで(0.11 以降に移行してから)「セキュリティバリデータが効いていなかった」可能性があるところが、再び有効になります。 - Herb の最小サポートバージョン(0.11 未満)を使用している場合
→ デフォルト挙動と同じチェックが明示指定に変わるだけなので、実質的な挙動の変化はありません。
注意点
- この PR は「暫定対応」の位置づけと明示されています。
- 説明文に「for now」「we may want to revisit this」とあり、将来的には
- Herb 側のデフォルト挙動の見直し
- Rails 側での設定 API 提供(例:
config.action_view.herb_validator = :securityなど) といった再設計が入る可能性があります。
- 説明文に「for now」「we may want to revisit this」とあり、将来的には
- セキュリティチェックが再度有効になることで、
- 以前はスルーされていた潜在的な XSS・危険なテンプレート構造などが検出されるようになり、テストや CI が落ちる可能性があります。
- 特に Herb 0.11 以降にアップグレード済みかつ、この PR 適用前に「エラーが出なくなった」と感じていた場合は、再度エラーが出るかもしれません。その場合は、本来検出されるべきセキュリティ問題である可能性が高いです。
- 参考情報 (あれば)
PR 作者のコメント:
- Herb 0.11 以降でデフォルトから外れた security validator を、Rails から明示的に指定することで、最小サポートバージョン〜最新バージョンまで挙動をそろえる意図がある。
- 将来的にはこの挙動をどう扱うかを再検討する余地がある(Herb の設計 / Rails との統合ポリシーの観点で見直しの可能性)。
実運用でのチェック:
- この PR 適用後に CI を流し、テンプレート関連の警告・エラーが増えていないかを確認すると、セキュリティ観点での影響を把握しやすいです。
#58863 Add Manitoba time zone mapping
マージ日: 2026/9/24 | 作成者: @dawilliams929
- 概要 (1-2文で)
Rails のタイムゾーンマッピングに「Manitoba」を追加し、America/Winnipegに対応付ける変更です。これにより、従来Central Time (US & Canada)をマニトバ州の代理として使っていた場合に冬時間で1時間ずれる問題を防ぎます。
- 変更内容の詳細
2-1. 何が問題だったか
- これまで Rails では、マニトバ州の多くのユーザーが
"Central Time (US & Canada)"を選択しており、これは IANA タイムゾーンAmerica/Chicagoにマッピングされていました。 - しかし、マニトバ州はもはや米国の Central Time と冬時間の時計が一致しておらず、冬季のオフセットがずれてしまいます。
- そのため
"Central Time (US & Canada)"をマニトバ向けの「なんちゃって」指定として使うと、冬時間で 1 時間ずれる状態になります。
2-2. 追加されたマッピング
ActiveSupport::TimeZone のフレンドリ名 → IANA名マッピングに、以下が追加されました。
# 例: フレンドリ名から TimeZone を取得
zone = ActiveSupport::TimeZone["Manitoba"]
# => #<ActiveSupport::TimeZone:0x... @name="America/Winnipeg", ...>内部的には以下のようなマッピングが追加されたイメージです(実際のハッシュ位置は既存定義に従う):
MAPPING = {
# 既存のエントリ...
"Manitoba" => "America/Winnipeg",
}これにより、Rails アプリケーション内で以下のように利用できます。
Time.use_zone("Manitoba") do
Time.zone.now # America/Winnipeg として扱われる
end2-3. テストの追加
activesupport/test/time_zone_test.rb に、次のようなテストが追加されています(要旨):
"Manitoba"というフレンドリ名でActiveSupport::TimeZoneを取得できること- それが
America/Winnipegに解決されること
テストは「名前解決」だけを検証しており、UTC オフセット(実際の時差)までは検証していません。
これは、正しい冬時間のオフセットが IANA tz データベース(tzdata)の将来バージョンに依存するためです。
2-4. CHANGELOG
activesupport/CHANGELOG.md に、以下のようなエントリが追加されています(概要):
- Manitoba 向けに
"Manitoba"→America/Winnipegのタイムゾーンマッピングを追加したこと - 背景として、
Central Time (US & Canada)がマニトバに対しては正しくなくなる点に言及
- 影響範囲・注意点
新規・既存アプリでの利用方法
- 設定画面やユーザーのプロフィールで、タイムゾーン選択肢に
"Manitoba"を追加できるようになります。 - すでに
"Central Time (US & Canada)"をマニトバ州ユーザーに割り当てているアプリは、可能であれば"Manitoba"へ移行する方が正確です。- 例: DB 上の
time_zoneカラムを"Central Time (US & Canada)"→"Manitoba"に更新するマイグレーションなど。
- 例: DB 上の
- 設定画面やユーザーのプロフィールで、タイムゾーン選択肢に
tzdata バージョンへの依存
- この PR は「マッピング(名前解決)」のみを提供します。
- 実際に正しい冬時間オフセットを得るには、OS やコンテナにインストールされている IANA tz データベース(
tzdataパッケージなど)が、マニトバ州の新しいルールを含むバージョンに更新されている必要があります。 - ただし、tzdata が古くても
- Rails から
"Manitoba"→America/Winnipegの解決は成功する - その
America/Winnipegのルール内容(オフセットやDST切り替え)は tzdata 側のバージョンに依存する という構造になっている点に注意してください。
- Rails から
他のカナダ地域との違い
America/WhitehorseやAmerica/Inuvikのように、「IANA 名はあるが Rails 側にフレンドリ名がない」地域もあります。- マニトバは「一つの州」として多くのユーザーが
"Central Time (US & Canada)"に依存してきたため、特別にフレンドリ名"Manitoba"を用意した、という位置付けです。
互換性
- 既存の
"Central Time (US & Canada)"→America/Chicagoマッピングはそのまま維持されるため、既存アプリは即座に壊れません。 - ただし、マニトバ州ユーザーに対しては時間のずれが発生し得るので、該当ユーザー向けにマッピングの見直しを検討してください。
- 既存の
- 参考情報 (あれば)
- 関連Issue:
#58862— Manitoba と US Central Time の冬時間不一致に関する報告 - 類似PR:
#58321— ブリティッシュコロンビア・アルバータのタイムゾーン変更対応(同様にフレンドリ名マッピングを追加)
- 背景技術:
- Rails のタイムゾーンは
ActiveSupport::TimeZoneがラッパとなり、内部的には IANA タイムゾーン(America/Winnipegなど)と OS のtzdataに依存してオフセットを解決します。
- Rails のタイムゾーンは
#58854 Fix non-ASCII strict locals defaults in ASCII-only templates
マージ日: 2026/9/24 | 作成者: @carlosdanielpohlod
- 概要 (1-2文で)
ERB テンプレートのlocals:マジックコメントで非ASCII文字(例:"café")をデフォルト値に指定し、テンプレート本体が ASCII のみの場合に文字化けしていたバグを修正した PRです。テンプレートのコンパイル結果のエンコーディング処理を見直し、magic comment の非ASCIIデフォルト値が正しく UTF-8 として扱われるようにしています。
- 変更内容の詳細
バグの内容
対象となるケースの例:
<%# locals: (label: "café") %>
<%= label %>テンプレート本体(<%= label %>)は ASCII のみですが、locals: マジックコメントには非ASCII文字の é を含んでいます。このとき、実行結果が:
caf��のように文字化けしてしまい、例外も発生しない、という問題がありました。
なぜ起きていたか
ポイントは Erubi のバッファのエンコーディングと、ActionView::Template#compiled_source のタグ付け にあります。
- Erubi はレンダリング用バッファを最初は
ASCII-8BIT(バイナリ) として開始する - 非ASCII文字がテンプレート本文中に登場したタイミングで、バッファを
UTF-8に昇格させる - しかし、「テンプレート本文が ASCII のみ」の場合は、最終的なハンドラ出力(コンパイル済み Ruby コード)のエンコーディングも
ASCII-8BITのままになる
一方で、過去のコミット ad5d539fde では、コンパイル済みコードの先頭に UTF-8 宣言を含めるような形で、compiled_source 側がエンコーディングをタグ付けしていました。しかし今回のケースでは:
- Erubi からのコンパイル済み Ruby コードは
ASCII-8BIT compiled_sourceがそのコード全体をASCII-8BITとして再タグ付け- その中に含まれる
locals:マジックコメント (Ruby コードとして解釈される strict locals 定義) の文字列"café"もASCII-8BITとして扱われる - 実行時に
"café"が正しく UTF-8 として解釈されず、結果として mojibake になってしまう
という流れでバグが発生していました。
なお、ad5d539fde が追加したテストは「テンプレート本文」にも非ASCII文字を含めていたため、Erubi の段階でバッファが UTF-8 に昇格し、この問題パターンをカバーできていませんでした。
今回の修正内容
actionview/lib/action_view/template.rb の compiled_source の生成処理を調整し、テンプレート本文が ASCII のみであっても、strict locals のデフォルトなど magic comment に含まれる非ASCII文字が正しく UTF-8 として扱われるようにしました。
差分は小さいですが、要点としては:
- Erubi から返ってきたコンパイル済みコードを「単純に ASCII-8BIT のまま再タグ付けする」のではなく、
- UTF-8 宣言や magic comment を含む「Ruby メソッド定義全体」が一貫して UTF-8 で扱われるようにエンコーディングを調整
するように変更されています。
テスト (actionview/test/template/template_test.rb) では、新たに「テンプレート本文は完全に ASCII」「locals のデフォルトには非ASCII」のケースを追加し、実際に "café" が文字化けせずにレンダリングされることを検証しています。
イメージとしては、おおよそ次のようなテストが追加されています:
# 擬似コードイメージ
def test_non_ascii_default_in_locals_in_ascii_only_template
template = <<~ERB
<%# locals: (label: "café") %>
<%= label %>
ERB
assert_equal "café", render_inline(template).strip
end(実際のテスト名や書き方は多少異なりますが、検証しているパターンはこれです。)
- 影響範囲・注意点
影響範囲:
- Action View の ERB テンプレートで strict locals (
locals:マジックコメント) を使っている場合 - その locals のデフォルト値に非ASCII文字 (日本語、アクセント付きラテン文字、絵文字など) を含め、
- かつテンプレート本文自体は ASCII のみ、というケース
- Action View の ERB テンプレートで strict locals (
この修正により:
- 上記のようなテンプレートで、非ASCIIのデフォルト値が正しく UTF-8 としてレンダリングされます
- 既存コードで「なぜか locals のデフォルト文字列だけが文字化けする」という症状が出ていた場合、このバグフィックスで解消される可能性が高いです
互換性・副作用について:
- 変更内容はコンパイル済みテンプレートコードのエンコーディング処理に限定されており、
- もともと UTF-8 で正しく動いていたテンプレートに対する影響は限定的です
- 例外を投げるような挙動変更ではなく、「いままでサイレントに文字化けしていたものが正しく表示される」方向の修正です
注意点:
- もしアプリ側でテンプレートのエンコーディングに対して独自な monkey patch や処理を入れている場合は、今回の変更と干渉しないか確認した方が安全です
- テンプレートファイル自体のエンコーディング(ファイルの実体)が UTF-8 であることが前提なので、古い環境で Shift_JIS などを混在させている場合は別途整備が必要です
- 参考情報 (あれば)
- 対応 Issue: #58456
- 関連コミット:
ad5d539fde(strict locals / encoding 周りの以前の修正) - 変更ファイル:
actionview/lib/action_view/template.rbactionview/test/template/template_test.rb
strict locals 機能を多用しているアプリでは、テンプレートに非ASCIIのデフォルトを定義している箇所があれば、この PR 適用後に文字化けが解消されるかどうかを一度確認しておくとよいです。
#58721 Add a framework default to compile HTML+ERB templates through Herb
マージ日: 2026/9/24 | 作成者: @marcoroth
- 概要 (1–2文で) Rails 8.2のフレームワークデフォルトとして、HTML+ERBテンプレートを新しいERB実装「Herb」でコンパイルする設定が追加されました。HTML以外のフォーマット(text, js など)は従来どおり Erubi が使われ、既存アプリは設定で簡単に従来動作へ戻せます。
- 変更内容の詳細
2-1. erb_implementation に :herb / :erubi が指定可能に
config.action_view.erb_implementation がシンボル指定に対応し、次の2つを受け付けるようになりました。
Rails.application.config.action_view.erb_implementation = :herb # 新デフォルト(8.2)
Rails.application.config.action_view.erb_implementation = :erubi # 従来どおり内部的には ActionView::Base.erb_implementation= が、このシンボルを実際のクラスに解決します。
:herb→ERB::Herb:erubi→ActionView::Template::Handlers::ERB::Erubi(従来実装)
クラスを直接渡すことも今までどおり可能です(その場合は解決処理スキップ)。
ActionView::Base.erb_implementation = MyCustomErbImpl
# :herb/:erubi の解決は行わず、そのまま使われるActionView::Base.erb_implementation= は、:herb と :erubi 以外のシンボルを渡すと ArgumentError を投げるようになっています。
2-2. デフォルト設定の変更(Rails 8.2)
Rails 8.2 の framework defaults に、次の設定が追加されました。
# config/initializers/new_framework_defaults_8_2.rb に生成される内容の一部
Rails.application.config.action_view.erb_implementation = :herbこれにより:
- 新規アプリ(Rails 8.2で
rails newしたもの)- デフォルトで HTML フォーマットのテンプレートは Herb でコンパイル
- HTML以外は引き続き Erubi
- 既存アプリで
config.load_defaults 8.2を指定した場合- 同じく Herb が有効になる(ただし opt-out 可)
2-3. Herb の挙動(HTML+ERB 一体の構文解析)
Herb は「HTML + ERB」を別々ではなく 1つの構文木としてパースします。これにより:
- 未クローズのタグなど、HTMLとして破綻しているテンプレートはコンパイル時にエラーになります
- エラーメッセージにはテンプレート内の位置情報が含まれ、どこがダメかを把握しやすくなる
- 正しいテンプレートについては、出力結果は Erubi のときと同じになるように設計されています
コンパイルエラーの例イメージ(概念的には):
<!-- どこかで <div> が閉じられていない等 -->
<div>
<%= render 'partial' %>
<% if condition %>
<span>text</span>
<% end %>
<!-- </div> が無い -->
# => Herb でコンパイルすると「未クローズのタグ div」などのエラー2-4. HTML以外のテンプレートは引き続き Erubi
このPRでは「HTMLフォーマットのテンプレート」に対してのみ Herb を適用します。
他の形式(例: .text.erb, .js.erb, .json.erb など)は、今までどおり Erubi でコンパイルされます。
つまり すべてのERBがHerbに切り替わるわけではなく、HTMLに限定されています。
2-5. 既存アプリでの opt-out(Herbを使わない)
テンプレートの構造的な問題(未クローズタグなど)が多く、すぐには修正できない場合は、設定一行で従来動作に戻せます。
# config/application.rb や任意のinitializerなどで
Rails.application.config.action_view.erb_implementation = :erubiこれにより:
- HTMLテンプレートを含め、すべてのERBテンプレートが Erubi でコンパイル
- Herb は一切使われない
また、すでに独自のERB実装クラスを指定しているアプリは、そのクラスがそのまま使われ、Herb は関与しません。
Rails.application.config.action_view.erb_implementation = MyCustomErbImpl
# → HTML/非HTML問わず常に MyCustomErbImpl
# :herb/:erubi の切り替えロジックは無視される2-6. Herb 移行用のチェックタスク(別PRとの連携)
別PR (#58770) で追加された Rake タスク:
bin/rails herb:checkこのタスクを実行すると、
- Herb でコンパイルできないテンプレート(主にHTML構造の問題があるもの)を洗い出し
- どのテンプレートを直せば Herb を安全に有効化できるかを確認
できるようになっています。
このPRの説明では、デフォルト採用前にこのタスクを回してエラーを潰しておくことが推奨されています。
- 影響範囲・注意点
3-1. 影響範囲
- 対象:
- Rails 8.2で新規作成されたアプリ
config.load_defaults 8.2を有効にした既存アプリ(ただし opt-out 可能)
- 影響内容:
- HTML+ERB テンプレートのコンパイルフェーズで、HTML構造までチェックされるようになる
- Erubi 時には問題なくレンダリングされていた「構造的に壊れた HTML テンプレート」が、Herb ではコンパイルエラーになる場合がある
3-2. 注意点・移行戦略
- チェックタスクを先に実行
bin/rails herb:checkを CI やローカルで実行し、エラーになるテンプレートを洗い出してからload_defaults 8.2や:herbを有効にするのが安全です。
- 段階的導入も可能
- すぐに全体を Herb に切り替えられない場合は、当面
:erubiに戻しておき、並行してテンプレートを修正する運用もできます。
- すぐに全体を Herb に切り替えられない場合は、当面
- 既にカスタムERB実装を使っている場合
erb_implementationにクラスを直接渡しているアプリは、この変更の影響を受けません(Herb/Erubiの切り替えロジックがスキップされる)。
- 参考情報 (あれば)
- このPR: Add a framework default to compile HTML+ERB templates through Herb (#58721)
- Herb をAction Viewに導入した前提PR: #58552
- Herbでのコンパイル可否をチェックするタスク追加PR: #58770(
bin/rails herb:check) - 関連ガイド更新:
guides/source/configuring.md:config.action_view.erb_implementationの説明に:herb/:erubi追加guides/source/upgrading_ruby_on_rails.md: 8.2 へのアップグレード時の注意点として Herb の導入が追記
#58829 Use standard input for MySQL db:schema:load
マージ日: 2026/9/23 | 作成者: @yahonda
- 概要 (1-2文で)
MySQL 向けdb:schema:load(schema_format = :sql)で、mysql --execute "SOURCE ..."を使う方式をやめ、構造 SQL を標準入力から流し込む方式に変更した PRです。これにより、MySQL 9.4+ などで導入された--commandsオプションのデフォルト設定に影響されず、最新クライアントでもdb:schema:loadが失敗しなくなります。
- 変更内容の詳細
従来の挙動
config.active_record.schema_format = :sql かつ MySQL 利用時、Rails はスキーマロードに以下のようなコマンドを実行していました:
mysql --execute "SOURCE db/structure.sql" --database app_developmentここで SOURCE は MySQL クライアントの「ローカルコマンド」であり、SQL 文そのものではありません。
MySQL 9.4.0 以降(および 26.x 系)の mysql クライアントには --commands オプションが追加され、デフォルトで off(無効)になりました。この状態だと SOURCE などのローカルコマンドが無効化されるため、上記のような実行は次のような SQL 構文エラーになります:
ERROR 1064 (42000) at line 1: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'SOURCE db/structure.sql' at line 1一方で、
- MySQL 8.0.43+ / 8.4.6+ のクライアント:
--commandsはあるがデフォルト ON → 今まで通り動く - MariaDB クライアント:
--commands自体が存在しない → 影響なし
という状況で、「Oracle MySQL クライアント 9.4+ だけが壊れる」という状態になっていました。macOS の Homebrew (brew install mysql / mysql-client) は 9.x / 26.x 系を追随しているため、macOS で Rails + MySQL を使う開発者が真っ先に影響を受けます。
新しい挙動
MySQLDatabaseTasks#structure_load を、SQLite と同様に「標準入力から SQL を読む」方式に変更しました。
新しい実行方法:
mysql --database app_development < db/structure.sqlポイント:
- クライアント側の
SOURCEコマンドを一切使わない - つまり
--commandsオプションの on/off に関わらず動作する - 既存の
structure_load_flags(ユーザーが設定するフラグ)はそのまま引き継ぎ
これにより、MySQL 9.4+ でも 8.0 系でも MariaDB でも同じ方法で安定してスキーマロードが行えるようになります。
テストの変更
- mysql2 / trilogy アダプタの rake テスト:
- 実際にテストデータベースに対して「標準入力からロードする」テストを追加
- ロード後にテーブルが存在することをアサート
- 既存のテスト:
Kernel.systemをモックしているテストは継続使用しつつ、- 期待されるコマンドラインを
SOURCE方式から「標準入力リダイレクト」方式へ更新
関連するコード変更のイメージ
実際のコードは Rails 内部ですが、イメージとしては:
従来:
# イメージ
Kernel.system(
"mysql",
"--execute", "SOURCE #{structure_file}",
"--database", db_config.database,
*flags,
)から:
# イメージ
Kernel.system(
"mysql",
"--database", db_config.database,
*flags,
"<", structure_file # 実際には Ruby の system 呼び出しでのリダイレクト指定
)のように、クライアントコマンドではなくシェルのリダイレクト機能を使う形に変わっています。
- 影響範囲・注意点
影響範囲
- 対象:
- MySQL / mysql2 / trilogy アダプタで、
config.active_record.schema_format = :sqlを利用し、db:schema:load/db:structure:loadを実行するケース
- 影響内容:
- MySQL 9.4+ / 26.x 系クライアント環境(特に Homebrew の
mysql)で発生していたERROR 1064が解消される - 8.0 / 8.4 系、MariaDB クライアントでも動作方式は変わるが、機能上の挙動は同じ(SQL を順に流しているだけ)であり、後方互換性を損なう変更ではない想定
- MySQL 9.4+ / 26.x 系クライアント環境(特に Homebrew の
なぜ --commands を Rails 側で付けないのか
単純に
mysql --commands --execute "SOURCE db/structure.sql"のように --commands を常に付ける選択肢もありますが、これには問題があります:
MariaDB の
mysqlコマンドは--commandsをサポートしていない実例(MariaDB 12.3.2 on Debian 13):
sh$ mysql --commands --execute "SELECT 1" mysql: Deprecated program name. It will be removed in a future release, use '/usr/bin/mariadb' instead mysql: unknown option '--commands' $ echo $? 2
Rails は
PATH上のmysqlが Oracle MySQL なのか MariaDB なのかを区別できない
このため、--commands をデフォルトで付与する戦略は取れず、「どのベンダーのクライアントでも確実に動く方法」として標準入力方式が選ばれています。
既存バージョンの Rails へのワークアラウンド
この PR より前の Rails を使っていて、MySQL 9.4+ クライアントで既に困っている場合の回避策:
# config/initializers/mysql_commands.rb 等
ActiveRecord::Tasks::DatabaseTasks.structure_load_flags = ["--commands"]
# 複数アダプタの場合:
# ActiveRecord::Tasks::DatabaseTasks.structure_load_flags = { mysql2: ["--commands"] }ただしこの方法は:
- Oracle MySQL クライアント 8.0.43+ / 8.4.6+ / 9.4+ / 26.x では有効
- MariaDB クライアントでは
--commands自体が不正オプションとなる(ので使えない)
といった制限があります。この PR がマージされた Rails バージョン以降では、基本的にこのワークアラウンドは不要になります。
- 参考情報
PR 内で触れられている関連情報:
- MySQL 9.4.0 リリースノート(
--commandsオプションの追加、デフォルト FALSE)
https://dev.mysql.com/doc/relnotes/mysql/9.4/en/news-9-4-0.html - MySQL 8.0.43 / 8.4.6 リリースノート(
--commands追加、デフォルト TRUE)
https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-43.html
https://dev.mysql.com/doc/relnotes/mysql/8.4/en/news-8-4-6.html mysqlクライアントの--commandsオプションリファレンス(sourceが無効化対象に含まれる)
https://dev.mysql.com/doc/refman/9.4/en/mysql-command-options.html#option_mysql_commands- 実装変更の元コミット:
- WL#16949: mysql クライアントの built-in コマンドを無効化するオプション追加
- その 8.0 系へのバックポート
- Ecto (Elixir) 側でも同様に
SOURCE方式から標準入力方式へ変更済み:
この PR は ActiveRecord の CHANGELOG にもエントリが追加されており、「MySQL クライアント 9.4+ で db:schema:load が落ちる問題」への公式な対応として扱われます。
#58828 Drop the explicit SET FOREIGN_KEY_CHECKS statements from MySQL db:schema:load
マージ日: 2026/9/23 | 作成者: @yahonda
- 概要 (1–2文で)
MySQL用のdb:schema:load(正確にはstructure_load)処理から、Rails側で明示的に送っていたSET FOREIGN_KEY_CHECKS = 0/1を削除し、mysqldump/mariadb-dumpが出力するスキーマファイル内の設定に依存するように変更したPRです。これに伴い、ダンプファイル側に外部キー制約チェックのON/OFFが含まれていることを保証するためのテストと、カスタマイズ方法のドキュメントが追加されています。
- 変更内容の詳細
2-1. 背景・動機
従来の MySQLDatabaseTasks#structure_load は、次のようなコマンドを実行していました:
mysql \
--execute "SET FOREIGN_KEY_CHECKS = 0; SOURCE db/structure.sql; SET FOREIGN_KEY_CHECKS = 1" \
--database app_developmentこれは「テーブル作成順序の関係で、まだ存在しないテーブルを参照する外部キーがあっても、とりあえずチェックを無効にして流し込む」ための歴史的な実装です。
しかし、Rails が mysqldump ベースのダンプに切り替えた 2012 年以降、mysqldump(および mariadb-dump)は自動的に以下のようなヘッダー/フッターをスキーマファイル内に出力しています(MySQL 4.1.1 以降):
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
-- 中略(CREATE TABLE や INSERT 文など)
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;structure_dump では --skip-comments を付けていますが、これは -- コメントのみを削除し、この /*!…*/ 形式の「条件付きコメント」には影響しません。したがって、Rails が改めて SET FOREIGN_KEY_CHECKS = 0 を送るのは 2012 年以降ずっと冗長だった、というのがこのPRの出発点です。
さらに、このPRは「SOURCE コマンドをやめる変更」の前段として行われています。MySQL クライアント 9.4 以降では SOURCE がデフォルトで拒否されるため、本来は --init-command への移行を考えていたものの、
- Railsが付与する
--init-command - ユーザーが
structure_load_flagsで指定する--init-command
が衝突し、クライアントは「最後の1個だけ」を採用する仕様であるため、マージロジックが必要になる、という問題がありました。結果として「そもそも Rails 側で FOREIGN_KEY_CHECKS をいじる必要がない」ことが確認できたので、このPRで削除する方向になっています。
2-2. 実際の変更点
① structure_load から SET FOREIGN_KEY_CHECKS を削除
activerecord/lib/active_record/tasks/mysql_database_tasks.rb の structure_load が、次のような形にシンプル化されています:
変更前(イメージ):
def structure_load(filename, database = nil, extra_flags = [])
args = [
"--execute", "SET FOREIGN_KEY_CHECKS = 0; SOURCE #{filename}; SET FOREIGN_KEY_CHECKS = 1",
"--database", db_name
]
# ...
Kernel.system("mysql", *args)
end変更後(イメージ):
def structure_load(filename, database = nil, extra_flags = [])
args = [
"--execute", "SOURCE #{filename}",
"--database", db_name
]
# ...
Kernel.system("mysql", *args)
endつまり、Rails 側では単に SOURCE db/structure.sql だけを実行し、FOREIGN_KEY_CHECKS の ON/OFF は完全にダンプファイル内の mysqldump / mariadb-dump の出力に任せる形になりました。
セッションごとの FOREIGN_KEY_CHECKS の値だけが変更され、グローバル値はどのみち変更されない点は従来どおりです。
② mysqldump / mariadb-dump に依存して良いことを検証するテストの追加
mysql2 / trilogy アダプタ用の rake テストに、実際に structure_dump を実行し、ダンプファイル内に FOREIGN_KEY_CHECKS のヘッダー/フッターが存在することを確認するテストが追加されました。
activerecord/test/cases/adapters/mysql2/mysql2_rake_test.rbactiverecord/test/cases/adapters/trilogy/trilogy_rake_test.rb
で共通して以下を確認:
db:test:prepareなどから呼ばれるstructure_dumpを実際に走らせる- 生成された
db/structure.sql内に、SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS
が含まれていることをassert_includes等で検証
CI 環境では Debian の default-mysql-client がインストールしている mariadb-dump を、ローカルでは PATH 上の mysqldump を使ってテストされます。
また、その他の rake テストでは Kernel.system をモックしているため、テストの更新は「期待されるコマンドライン引数から SET FOREIGN_KEY_CHECKS に関する部分を削る」だけに留められています。
③ --compact を使っている特殊ケースへのフォロー
structure_dump_flags に --compact を付けている環境では、mysqldump が上記のヘッダー/フッターを出力しないことが確認されています(mysqldump 26.7.0 で実測)。一方で、
--skip-opt--skip-set-charset--set-gtid-purged=OFF--no-tablespaces--column-statistics=0
といったオプションは、ヘッダー/フッターを削除しないことも確認されています。
つまり、--compact を付けている一部のセットアップでは、Rails が FOREIGN_KEY_CHECKS の ON/OFF を明示的に行わないと外部キー制約違反でロードが失敗しうる、ということになります。
そのため、CHANGELOG では「そういったケースではアプリケーション側で structure_load_flags を使って明示的に設定を追加してください」とガイドしています:
ActiveRecord::Tasks::DatabaseTasks.structure_load_flags = [
"--init-command=SET FOREIGN_KEY_CHECKS = 0"
]--init-command は MySQL クライアント起動時に 1 回だけ実行される SQL を指定するオプションで、これにより、
- 接続時に FOREIGN_KEY_CHECKS=0 にする
- ダンプファイル内で再度上書きされない限り、そのセッションではチェックが無効のまま
という動作になります。(--compact によってダンプファイル内のヘッダーが消えている分には、ここで補うことができます。)
④ CHANGELOG の更新
activerecord/CHANGELOG.md には以下のような内容が追記されています(要約):
db:schema:load/db:structure:loadの MySQL 系タスクでは、Rails が明示的にSET FOREIGN_KEY_CHECKSを送るのをやめ、ダンプファイル側に任せるようになった。--compactなどによりダンプ側で FOREIGN_KEY_CHECKS のヘッダー/フッターが出力されない構成では、自分でstructure_load_flagsに--init-command=SET FOREIGN_KEY_CHECKS = 0などを設定すること。
- 影響範囲・注意点
3-1. 影響範囲
対象:
- MySQL / MariaDB を使用していて、
db:structure:dump/db:structure:load(もしくはdb:schema:loadが内部で呼ぶ MySQL 用 structure タスク)を利用しているアプリケーション
直接の挙動変化:
structure_loadが発行するmysqlコマンドからSET FOREIGN_KEY_CHECKS = 0; …; SET FOREIGN_KEY_CHECKS = 1が消える- 実際には、ほとんどの環境で
db/structure.sql内に同等の処理が含まれているため、外部キー制約に関する実質的な挙動は従来と変わらない はずです。
3-2. 注意点(破壊的になりうるケース)
mysqldump/mariadb-dumpのバージョンが非常に古い場合- MySQL 4.1.1(2003年)以前の
mysqldumpには、FOREIGN_KEY_CHECKS のヘッダー/フッターがありません。 - MariaDB 系列では最初のタグからずっと含まれているため、MariaDB を使っている限りは問題ないと考えられます。
- もし極端に古い MySQL /
mysqldumpを使っていて、このヘッダー/フッターが出力されていない場合は、db:structure:load時に外部キー制約違反で失敗する可能性があります。その場合はstructure_load_flagsに--init-command=SET FOREIGN_KEY_CHECKS = 0を追加してください。
- MySQL 4.1.1(2003年)以前の
structure_dump_flagsに--compactを指定している場合--compactによって、FOREIGN_KEY_CHECKS のヘッダー/フッターが消されます。その状態で今回の変更(Railsが自前で SET しない)が入ると、スキーマロード時に外部キー制約が有効なままになり、ロード順序によってはエラーになります。
対処として、前述のとおりアプリケーション設定で:
rubyActiveRecord::Tasks::DatabaseTasks.structure_load_flags = [ "--init-command=SET FOREIGN_KEY_CHECKS = 0" ]のように補う必要があります。
structure_load_flagsで--init-commandを既に使っている場合- 現状のPRでは Rails 自体は
--init-commandを追加していないため、このPR単体では衝突は発生しません。 - ただし、このPRは「
SOURCEをやめる変更」の前段であり、今後の PR で--init-commandが使われる可能性があります。その際に「最後の--init-commandが優先される」仕様による衝突リスクがあることは、このPRの説明文に記載されています。
- 現状のPRでは Rails 自体は
- 参考情報
mysqldumpの FOREIGN_KEY_CHECKS 設定の歴史- MySQL 4.1.1 (2003-12-01) 以降の
mysqldumpにて、- ダンプ開始時:
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; - ダンプ終了時:
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; - が自動的に出力される。
- ダンプ開始時:
- MariaDB でも最初期のタグ(mariadb-5.1.38-beta)から同様のコードを保持している。
- MySQL 4.1.1 (2003-12-01) 以降の
Rails 側のカスタマイズポイント
- ダンプ時オプション:
ActiveRecord::Tasks::DatabaseTasks.structure_dump_flags - ロード時オプション:
ActiveRecord::Tasks::DatabaseTasks.structure_load_flags--init-command=SET FOREIGN_KEY_CHECKS = 0などを設定して、外部キー制約チェックの挙動を明示的に制御可能。
- ダンプ時オプション:
このPRは、将来的な「
SOURCEコマンド排除」変更の前提となるクリーンアップであり、FOREIGN_KEY_CHECKSの制御を「ダンプツール(mysqldump / mariadb-dump)に一元化する」ことで、Rails 側のフラグや--init-command周りの複雑さを減らす意図があります。
#58800 Fix #48375: Server-Timing double-counting of nested same-name events
マージ日: 2026/9/22 | 作成者: @Edilbek
- 概要 (1-2文で)
ActionDispatch::ServerTimingが同一名前のネストしたイベントを「二重計上」していたため、Server-Timingヘッダの時間が実際の処理時間より過大になる問題(#48375)を修正し、同一イベント名ごとに 排他的(exclusive)な時間の合計 を出すように変更した PR です。
これにより、render_partial.action_viewなどのメトリクスがリクエスト全体時間を超えるといった不自然な表示が解消されます。
- 変更内容の詳細
問題の背景
ActionDispatch::ServerTiming は ActiveSupport::Notifications を購読し、同一イベント名ごとに
events_collection.sum(&:duration)で Server-Timing の値を計算していました。
ところが ActiveSupport::Notifications::Event#duration は inclusive time(包括時間) であり、親イベントの duration は子イベントの時間をすでに含んでいます。
例: ネストした同名イベント (render_partial.action_view)
| イベント | 内容 | inclusive duration |
|---|---|---|
_b | 子パーシャル | 20ms |
_a | 親パーシャル(内部で _b を render) | 40ms (20ms + 自分の処理) |
壁時計(実際の経過時間)は約 40ms ですが、単純合計すると:
sum = 40ms (_a) + 20ms (_b) = 60msとなり、Chrome DevTools の Server-Timing 表示上は render_partial.action_view がリクエスト全体時間より長く見えてしまう、というのが #48375 の報告内容です。
この PR の方針:同名イベント内だけ「排他的時間」で集計
この PR では、イベント名ごと に「排他的(exclusive)時間」を合計するように変更しています。
排他的時間の定義:
exclusive(e) = duration(e) − sum(duration of direct same-name children)つまり「自分自身の inclusive duration から、直下の同名の子イベントの inclusive duration を引く」ことで、そのイベント名として「二重計上していない時間」を求めます。
先の _a / _b の例だと:
exclusive(_a) = 40ms - 20ms = 20ms
exclusive(_b) = 20ms
total = 40ms # 壁時計と一致なぜ「同名イベント内のみ」exclusiveなのか
同名イベントだけを排他的にする
- ネストした
render_partial.action_view同士の二重計上だけを解消。 render_partial.action_view全体として「この通知名に紐づく仕事がどれだけ時間を食ったか」が正しく分かる。
- ネストした
異なる名前のイベントはこれまで通り inclusive
- 例: パーシャルの中から
sql.active_recordが発行されるケースrender_partial.action_viewの inclusive duration には SQL 時間を 含めたまま。- 同時に
sql.active_recordとしても別行で計測・表示される。
- これを「すべて exclusive にする」(=全部 self time だけにする)と、Server-Timing の各行が今までと全く別の意味(純粋な self time)になってしまい、後方互換性・解釈のしやすさが落ちるため採用していません。
- 例: パーシャルの中から
実装のポイント
actionpack/lib/action_dispatch/middleware/server_timing.rb のロジックが拡張されています(擬似コードイメージ):
- イベントを名前ごとにグルーピング
- 各イベント名ごとに:
- 開始時刻でソート(開始が同時なら親が先に来るような並び)
- スタックを用いて「同名イベントの親子関係」を構築
- 「親イベントの排他的時間 = inclusive duration − 直下の同名子イベントの inclusive duration 合計」を計算
- 各イベント名ごとの排他的時間合計を
Server-Timingヘッダに使う
特徴:
- スタック+時間範囲 で「どのイベントがどのイベントを内包しているか」を判定
- 「直下の子」だけを引き算することで、多段ネスト(A → B → C)の場合にも telescoping(A から B を引き、B から C を引く)により正しい排他的合計が得られる
Before / After の挙動
ネストした同名イベント (outer sleep 20ms + inner sleep 20ms ≒ 40ms):
text# 変更前(inclusive 合計): ~75ms (壁時計の ~1.9倍) # 変更後(exclusive 合計): ~45ms (壁時計の ~1.1倍, 残りは sleep オーバーヘッドなど)フラット(非ネスト)な同名イベントの場合:
- 変更前後とも
sum(duration)= 壁時計時間とほぼ一致(行動は変わらない)
- 変更前後とも
テスト
actionpack/test/dispatch/server_timing_test.rb にテストが追加:
- ネストした同名イベントが二重計上されないこと
- 同名イベントがフラットに並ぶケースでは、従来通り単純合計されること
- 異なるイベント名 (
sql.active_recordなど) は親イベント名から引かれないこと - 再現用スクリプト
server_timing_nested_partials_48375_test.rbによる単体検証
- 影響範囲・注意点
影響を受けるのは
ActionDispatch::ServerTimingミドルウェア経由のServer-Timingヘッダ値のみ
アプリケーションロジックや他の計測には影響しません。特に影響が大きいのは、同名イベントが深くネストするケース
- 代表例: ネストした
render_partial.action_view - これまでは「Server-Timing 上で
render_partial.action_viewの合計時間がリクエスト全体を超える」ことがありましたが、それが解消されます。 - 既にこれを前提に独自のしきい値・警告ロジックを組んでいた場合は、数値が減るため調整が必要な可能性があります。
- 代表例: ネストした
異なるイベント名をまたぐ集計ロジックは変更されていない
- 例: 「SQL の時間を除いたレンダリング時間」を Server-Timing から直接読み取ろうとするような高度な解釈は、依然としてできません(従来通り、イベント名ごとの inclusive span + 同名 exclusive 調整に留まる)。
Server-Timing を用いたパフォーマンス監視の解釈がより自然になる
- 「ある通知名(例:
render_partial.action_view)に紐づく処理が、壁時計時間としてどのくらい占めたか」を、過大評価せずにおおよそ把握できるようになります。 - 特に Chrome DevTools 等で見たときに「行単位の時間合計がリクエスト全体をはみ出す」現象が起こりづらくなるため、パフォーマンス診断の直感的一貫性が上がります。
- 「ある通知名(例:
- 参考情報 (あれば)
- 対応 Issue: #48375 — Server-Timing で
render_partial.action_viewがリクエスト時間を超えて見える問題 - PR: #58800 - Fix #48375: Server-Timing double-counting of nested same-name events
- 関連ファイル:
actionpack/lib/action_dispatch/middleware/server_timing.rbactionpack/test/dispatch/server_timing_test.rbactionpack/CHANGELOG.md
#58852 Memoize MacroReflection#klass for self-referential associations
マージ日: 2026/9/22 | 作成者: @55728
- 概要 (1-2文で)
自己参照関連(class_name: "Comment"のように自分自身を指す関連)に対して、MacroReflection#klassのメモ化が効いていなかったバグを修正するPRです。これにより、自己参照関連でも関連先クラスの解決が1度だけ行われ、以降はキャッシュされたクラスが利用されるようになります。
- 変更内容の詳細
問題の背景
ActiveRecord::Reflection::MacroReflection#klass は、関連先のクラスを解決してインスタンス変数 @klass にメモ化する実装になっています(概念的には以下のようなイメージ):
def klass
@klass ||= begin
# いろいろクラス解決処理
end
endしかし自己参照関連(belongs_to :parent, class_name: "Comment" のように、demodulize 経路を通るケース)では、begin ブロック内で return を使っていたため、@klass ||= の右辺の評価途中でメソッド全体から抜けてしまい、@klass に値が代入されていませんでした。
その結果:
class Comment < ApplicationRecord
belongs_to :parent, class_name: "Comment"
has_many :children, class_name: "Comment"
end
reflection = Comment.reflect_on_association(:parent)
reflection.klass
reflection.instance_variable_get(:@klass)
# => nil (本来は Comment が入っていてほしい)klass を呼ぶたびに毎回定数解決(compute_class 相当)が走り、パフォーマンス的にも無駄が発生し、Reflection#freeze によるウォームアップも機能していませんでした。
修正内容
MacroReflection#_klass(実際には klass の内部で呼ばれる「実体」)の中で、自己参照関連の分岐にあった return をやめ、代わりにローカル変数に代入する形に変更しています。
イメージとしては、以下のような変更です(擬似コード):
def _klass
@klass ||= begin
# before:
# return compute_class_for_self_ref if self_ref?
# after:
klass = if self_ref?
compute_class_for_self_ref
else
compute_normal_class
end
klass
end
end「begin ブロックの途中で return していたため @klass への代入がスキップされる」という問題を、「ローカル変数に代入してからブロックの最後でそれを返す」ことで解消しています。
また、回帰テストが追加されており、klass を何度も呼び出したときに compute_class が1回だけ呼ばれること(= メモ化されていること)を検証しています。
ベンチマーク結果(PR記載)
100,000 回 klass を呼び出したケース(sqlite3):
| reflection 定義 | 1回あたり | アロケーション |
|---|---|---|
belongs_to :post | 0.05 µs | 0 |
belongs_to :parent, class_name: "Comment" | 0.53 µs | 1 |
修正前は自己参照関連だけメモ化をバイパスしており、毎回クラス解決とオブジェクト生成が発生していましたが、この変更により両者ともメモ化された高速パスを通るようになります。
- 影響範囲・注意点
影響対象:
class_nameなどで自己参照を定義している関連(belongs_to :parent, class_name: "Comment"/has_many :children, class_name: "Comment"など)。reflection.klassを頻繁に呼び出すコード(preloader、スコープ解決、関連読み出し・生成など内部実装を含む)。
期待される挙動の変化:
- これまで自己参照関連では
reflection.klass呼び出しのたびにクラス解決が実行されていたが、今後は1回だけ解決され、@klassにキャッシュされる。 Reflection#freezeによるウォームアップ(klassを事前に解決しておく処理)も、自己参照関連に対してきちんと効くようになる。
- これまで自己参照関連では
後方互換性:
- 表面的なAPIや返り値は変わらず、パフォーマンス改善とバグ修正に留まるため、通常は後方互換性上の問題は発生しないと考えられます。
- もし「毎回クラス解決が走る」というバグに依存した非常に特殊なメタプログラミングをしている場合は、挙動が変わりますが、そうした依存は基本的に想定されていないはずです。
- 参考情報 (あれば)
- 対象コード:
activerecord/lib/active_record/reflection.rb - 概念的キーワード:
ActiveRecord::Reflection::MacroReflection#klass- 自己参照関連(self-referential associations)
- メモ化 (
@klass ||= ...) demodulize経路でのクラス解決
- この種のバグは、「
||=とbegin ... end+returnの組み合わせ」による典型的なメモ化漏れパターンなので、類似のコードパスがないかレビュー時に注意しておく価値があります。
#58850 Fix NoMethodError when Active Job's logger is a BroadcastLogger
マージ日: 2026/9/22 | 作成者: @petrenkorf
- 概要 (1-2文で)
Active Job がBroadcastLoggerを使っているときに、非タグ付きロガーに対してNoMethodError(current_tagsがない) が発生していた不具合を修正する PR です。logger.respond_to?(:tagged)に依存せず、formatterがcurrent_tagsに対応しているかを正しく判定するように変更されています。
- 変更内容の詳細
問題の背景
ActiveSupport::BroadcastLogger#taggedは #58429 でクラスに直接定義され、「常に存在する」メソッドになりました。- それ以前は
method_missing / respond_to_missing?で実装されていて、- 配下のロガーのどれかが
taggedをサポートしていればrespond_to?(:tagged)がtrue - 誰もサポートしていなければ
falseという、実際の機能に即した判定ができていました。
- 配下のロガーのどれかが
- #58429 以降は、
BroadcastLoggerであれば常にrespond_to?(:tagged)がtrueになります。
一方、Active Job 側では、ログのタグ付け可否を以下のように判定していました(要点):
def tag_logger(*tags, &block)
if logger.respond_to?(:tagged)
tags.unshift "ActiveJob" unless logger_tagged_by_active_job?
# ...
else
yield
end
end
def logger_tagged_by_active_job?
logger.formatter.current_tags.include?("ActiveJob")
endlogger.respond_to?(:tagged)がtrueのとき、- 「このロガーはタグ付きロギングをサポートしているはずだ」とみなして
logger.formatter.current_tagsを呼び出しています。
- しかし、
BroadcastLoggerが「タグ機能を持たない普通のロガー(例:ActiveSupport::Logger.new(STDOUT))だけ」を包んでいる場合、logger.respond_to?(:tagged)は「常にtrue」になったためlogger.formatterがActiveSupport::Logger::SimpleFormatterのようなクラスでもcurrent_tagsを呼んでしまいNoMethodError: undefined method 'current_tags'が発生していました。
修正内容
logger_tagged_by_active_job? をより堅牢にし、「formatter が current_tags を実装している場合だけ呼ぶ」ように変更しています。
イメージとしては以下のような変更です(実際のコードと概念的には同じ):
def logger_tagged_by_active_job?
formatter = logger.formatter
return false unless formatter.respond_to?(:current_tags)
formatter.current_tags.include?("ActiveJob")
endこれにより、
BroadcastLogger自体がtaggedを持っていても、- 内部のフォーマッタがタグ機能 (
current_tags) を持たない場合はlogger_tagged_by_active_job?はfalseを返しtag_logger内でcurrent_tagsを呼ばないため、NoMethodErrorが防がれます。
テスト追加
activejob/test/cases/logging_test.rb に回帰テストが追加されています。内容は:
ActiveJob::Base.loggerに- 「タグ付きではない普通のロガー」を
ActiveSupport::BroadcastLoggerで包んだもの
- 「タグ付きではない普通のロガー」を
- をセットした状態でジョブを実行し、
- 例外 (
NoMethodError) が発生しないこと
- 例外 (
- を確認するテストです。
これにより、今後同種の回帰がテストで検出できるようになっています。
- 影響範囲・注意点
- 対象:
- Active Job で
ActiveSupport::BroadcastLoggerを使っているケース - 特に、配下に「タグ付きロガーと非タグ付きロガーが混在」または「非タグ付きロガーのみ」の構成で運用しているアプリケーション
- Active Job で
- 影響:
- これまで
NoMethodErrorが発生していたケースで、正常に動作するようになります。 logger.respond_to?(:tagged)を「formatter がcurrent_tagsを持つかどうかの代理判定」として利用する前提は、Active Job 内では廃止されました。
- これまで
- 破壊的変更の可能性:
- 外部 API のインターフェース変更はなく、挙動としても「例外が出ていたケースが正常化する」だけなので、実質バグフィックスです。
- アプリ側で
formatter.current_tagsを直接叩いている場合は依然として注意が必要ですが、この PR はあくまで Rails 内部の呼び出しだけを防御的にしたものであり、利用側コードには影響しません。
- 参考情報 (あれば)
- 対応 Issue: https://github.com/rails/rails/issues/58843
BroadcastLogger#taggedの振る舞い変更 (常に定義されるようになった PR): #58429- 修正 PR 本体: #58850
#58429 Fix BroadcastLogger#tagged when broadcasting to multiple tagging loggers
マージ日: 2026/9/21 | 作成者: @ousamabenyounes
- 概要 (1-2文で)
ActiveSupport::BroadcastLogger が複数の TaggedLogging ロガーに対して#taggedを呼ぶと壊れていた挙動を修正し、ブロックあり/なし両方のケースで直感的かつ一貫したタグ付きロギングができるようにした PR です。#taggedを明示的に実装し、これまでmethod_missingで起きていた Array 返却やブロックの多重実行を解消しています。
- 変更内容の詳細
問題点
ActiveSupport::BroadcastLogger はこれまで #tagged を定義しておらず、tagged が呼ばれると method_missing 経由で各ロガーに処理を委譲していました。このとき、2つ以上のロガーが tagged に対応している場合に以下の問題が発生していました。
logger = ActiveSupport::BroadcastLogger.new(
ActiveSupport::TaggedLogging.new(Logger.new($stdout)),
ActiveSupport::TaggedLogging.new(Logger.new($stderr))
)
logger.tagged("BCX").info("Hello")
# => NoMethodError: undefined method `info' for an instance of Array理由:
no-block 形式 (
logger.tagged("BCX"))method_missingは「各ロガーにtaggedを呼んだ結果の配列」を返す実装になっている- 2つ以上のロガーが
taggedを持つとArrayが返るため、その戻り値に対してinfoを呼んでしまいNoMethodErrorになる
block 形式 (
logger.tagged("BCX") { ... })method_missingは各ロガーのtaggedに同じブロックを渡す- その結果、ブロックが「タグ付きロガーの数だけ」実行されてしまい、本来意図した「タグをまとめて一度だけ有効化してブロックを実行する」という動作になっていなかった
対応内容: BroadcastLogger#tagged の明示的な定義
ActiveSupport::BroadcastLogger に #tagged メソッドを定義し、method_missing ではなく専用ロジックで処理するようにしました。
1) ブロックなしの場合
tagged_logger = logger.tagged("BCX")
tagged_logger.info("Hello")- 挙動:
respond_to?(:tagged)なロガーについては、それぞれに対してtagged(*tags)を呼び出した結果を使った「新しい BroadcastLogger」を返すtaggedをサポートしないロガーは、そのまま新しい BroadcastLogger に含める(=ログは引き続き届く)
- ポイント:
- 戻り値が常に
BroadcastLoggerになる ため、そのままチェーンしてinfo,debugなどが呼べる - 以前の「配列が返ってしまう」という問題を解消
- 戻り値が常に
疑似イメージ:
def tagged(*tags)
new_loggers = @loggers.map { |logger|
if logger.respond_to?(:tagged)
logger.tagged(*tags)
else
logger
end
}
self.class.new(*new_loggers)
end2) ブロックありの場合
logger.tagged("BCX", "PAYMENT") do
logger.info("Hello")
end- 挙動:
respond_to?(:tagged)なロガーだけを対象に、各ロガーのtagged(block 付き) をネストして呼び出し、最終的に一度だけブロックに制御を渡す- ブロックには、タグ適用済みの
BroadcastLogger(元のインスタンス or 同等のもの)が一度だけ渡される
- 重要なポイント:
- ブロックは 1 回だけ実行される(以前は tagging ロガーの数だけ実行されていた)
- ネストにより各ロガー固有の
ensureベースのタグクリーンアップ処理をそのまま利用するため、ブロック内で例外が起きてもタグがきちんと元に戻る - タグ付け非対応ロガーも、タグは付かないがブロック内の
info等の呼び出しは従来どおり届く
イメージとなる処理フロー:
def tagged(*tags, &block)
return super unless block_given? # 実際には BroadcastLogger 独自実装
tagging_loggers, other_loggers = @loggers.partition { _1.respond_to?(:tagged) }
# tagging_loggers を再帰的にネストして
# tagged_logger を1回だけ yield
nested_call(tagging_loggers, tags) do
yield self # or a suitable broadcast logger
end
endnested_call の中で:
def nested_call(loggers, tags, &final_block)
if loggers.empty?
final_block.call
else
first, *rest = loggers
first.tagged(*tags) do
nested_call(rest, tags, &final_block)
end
end
endという形で、tagged のブロック形式が各ロガーでネストされるイメージです。
テスト追加
activesupport/test/broadcast_logger_test.rb に以下の観点のテストが追加されています。
- no-block 形式の
#taggedが「Array ではなく」「BroadcastLogger を返し」「そのままログが書ける」こと - ブロック形式
logger.tagged { ... }が 1回だけ yield されること - ブロック内で例外が発生しても、タグが確実にクリーンアップされること
- tagging 非対応ロガーも、
tagged呼び出し後も引き続きメッセージを受け取り続けること
変更前はこの新テストで 3 failure、変更後は 52 runs, 113 assertions, 0 failures でグリーンになっていることが示されています。
TaggedLogging 側のテスト (tagged_logging_test.rb) もグリーンのままであり、既存の TaggedLogging 挙動に影響は出ていません。
- 影響範囲・注意点
- 影響範囲:
ActiveSupport::BroadcastLoggerとActiveSupport::TaggedLoggingを組み合わせて利用しているコード(特に複数の TaggedLogging ロガーをまとめている場合)logger.taggedを no-block / block 両方の形式で使っている箇所
- 既存コードへの影響:
- 今まで「たまたま動いていた」ケースはほぼなく、むしろ壊れていた挙動(Array 返却 + ブロック多重実行)が正される形なので、通常は改善方向の互換性変更です
- もし
BroadcastLogger#taggedの戻り値が Array であることを前提にしたようなコードがあれば、それは今回の修正で壊れますが、本来の設計意図からすると非推奨な使い方です
- 注意点:
tagged非対応のロガーを BroadcastLogger に混ぜている場合でも、今回の実装では「そのロガーはタグなしのまま」「ログは従来どおり届く」という挙動を維持します。
→tagged呼び出しで非対応ロガーが silent drop されることはありません。BroadcastLogger#taggedのブロック形式は、すべてのタグ対応ロガーでタグスタックを正しく push/pop するようになったので、これを前提にしたタグ状態の一貫性向上が期待できます。
- 参考情報 (あれば)
- 該当 issue:
- #49757: BroadcastLogger と TaggedLogging の組み合わせでの
#tagged問題 - #49745: ブロック形式の
taggedでの挙動不正
- #49757: BroadcastLogger と TaggedLogging の組み合わせでの
- 変更ファイル:
activesupport/lib/active_support/broadcast_logger.rb
→BroadcastLogger#taggedの実装追加activesupport/test/broadcast_logger_test.rb
→ 上記挙動を検証するテストを追加activesupport/CHANGELOG.md
→ バグ修正として記載追加
#58840 Use RenderedTemplate for EMPTY_SPACER
マージ日: 2026/9/21 | 作成者: @jhawthorn
- 概要 (1-2文で)
EMPTY_SPACERを「RenderedTemplate として振る舞う単なるオブジェクト」から、実際のRenderedTemplateインスタンスに置き換えた微修正です。Action View の内部実装をより一貫した形に整えるリファクタリングで、外部APIの挙動への影響はほぼありません。
- 変更内容の詳細
※ 実際の差分は 1 行追加・1 行削除のみのため、内容は概念的な説明になります。
従来:
EMPTY_SPACERは「レンダリング結果が空であること」を表すための特別なオブジェクトだったが、RenderedTemplateそのものではなかった。- しかし実装上は多くの箇所で
EMPTY_SPACERを、あたかもRenderedTemplateのように扱っていた(RenderedTemplate互換のインターフェイスを期待している)。
今回の変更:
actionview/lib/action_view/renderer/abstract_renderer.rb内で定義されているEMPTY_SPACERを、RenderedTemplateクラス(もしくはそのファクトリメソッド)を用いて初期化するように変更。具体的には、だいたい以下のようなイメージの変更が行われています(サンプルコード・実際のコードとは多少異なる可能性あり):
ruby# 変更前(イメージ) EMPTY_SPACER = Object.new # 変更後(イメージ) EMPTY_SPACER = ActionView::Template::RenderedTemplate.new("", nil) # または new/rendered_template_for などの専用コンストラクタを利用説明文にあるとおり、すでに「実質 RenderedTemplate として扱っている」のだから、定義自体も正しく
RenderedTemplateにしてしまう、という一貫性のための変更です。
- 影響範囲・注意点
主な影響範囲
ActionView::Renderer系の内部処理 (特に複数テンプレートの結合やバッファリングの際に空要素として使われる部分)。EMPTY_SPACERを直接参照している内部実装・ライブラリコード(通常のアプリ開発ではまず触らない領域)。
外部 API への影響
- 一般的なビューのレンダリング (
renderの挙動) やテンプレート出力は変わらない想定です。 - 値のクラスが
Object→RenderedTemplate(など)に変わるため、ActionView::Renderer::EMPTY_SPACERを直接参照して、- そのクラス・型・等価性(
==,is_a?など)に依存しているようなメタプログラミングをしている場合のみ、影響があり得ます。
- 一般的なビューのレンダリング (
注意するケース
- エンジン・プラグイン・Gem などで ActionView の内部にフックしている場合に、
EMPTY_SPACERとの比較ロジックがあると挙動が変わる可能性があります。 - 例:rubyのようなコードで、「同一オブジェクトかどうか」だけを見ている場合は基本的に動き続けますが、
if result.equal?(ActionView::Renderer::EMPTY_SPACER) # 特別扱い… endrubyのようにクラス階層を前提にしていた場合は挙動が変わる可能性があります。case result when Object # ... end
- エンジン・プラグイン・Gem などで ActionView の内部にフックしている場合に、
- 参考情報 (あれば)
- PR: https://github.com/rails/rails/pull/58840
- 関連しうる概念:
ActionView::Template::RenderedTemplate(あるいは同等のクラス): レンダリング結果とメタ情報をラップするオブジェクト。- Action View のレンダリングパイプライン内での「空のテンプレート・プレースホルダ」の扱い。
この変更は内部表現の整合性・明確化のためのリファクタリング色が強く、通常の Rails アプリ開発者にとっては互換性上の心配はほぼ不要ですが、Action View の内部に踏み込んだライブラリを書いている場合は一度 EMPTY_SPACER 周りの利用有無を確認すると安全です。
#58827 Backport alias fallback fixes for Active Model to 8-1-stable
マージ日: 2026/9/21 | 作成者: @yahonda
- 概要 (1-2文で)
Ruby 4.1.0dev 以降で非推奨となる「モジュール内でのaliasが暗黙にObjectを参照する」パターンを、Active Model 8.1 系で解消するバックポートです。alias使用箇所を明示的なメソッド定義に置き換えることで、Ruby 4.3 でNameErrorになる将来の問題を事前に防ぎ、同時に API ドキュメントにもメソッドが適切に掲載されるようにしています。
- 変更内容の詳細
背景となる Ruby 側の仕様変更
- Ruby 4.1.0dev 以降では、モジュール内で
aliasを書いたときに、対象メソッドをそのモジュール内に見つけられずObjectにフォールバックする挙動が非推奨になり、警告が出ます。 - Ruby 4.3 ではこのフォールバック自体が廃止され、
NameErrorを投げる予定です。
→ Rails 側としては、将来バージョンの Ruby でも警告・例外が出ないように、aliasの使い方を修正する必要があります。
対象となっていた3つの警告箇所
Active Model 8-1-stable ブランチでは、以下 3 箇所でこの非推奨パターンが残っていました。
/active_model/attribute_methods.rb: alias of 'respond_to?'
/active_model/validations.rb: alias of 'send'
/active_model/serialization.rb: alias of 'send'これらが Ruby 4.1.0dev 実行時に次のような警告を出していました:
warning: the fallback to Object for alias of 'respond_to?' in module 'ActiveModel::AttributeMethods' is deprecated and will be removed in Ruby 4.3
warning: the fallback to Object for alias of 'send' in module 'ActiveModel::Validations' is deprecated and will be removed in Ruby 4.3
warning: the fallback to Object for alias of 'send' in module 'ActiveModel::Serialization' is deprecated and will be removed in Ruby 4.3具体的な修正内容
1) ActiveModel::AttributeMethods の respond_to? エイリアス削除
- 対象ファイル:
activemodel/lib/active_model/attribute_methods.rb - 内容:
respond_to?に対するaliasによりObjectフォールバックが起こっていた箇所を修正。 - main ブランチで行われた #58817 をそのままチェリーピックしており、
aliasを使わずにモジュール内で期待する挙動を明示的に実装しています。
概念的には、例えば下記のようなスタイルへの変更に近いです(実際のコードは若干異なりますが意図として):
# 以前 (イメージ)
module ActiveModel::AttributeMethods
alias :respond_to_without_attributes? :respond_to?
end
# 以後 (イメージ)
module ActiveModel::AttributeMethods
def respond_to?(name, include_private = false)
# 独自の判定ロジックを明示的に定義
super || attribute_method?(name)
end
endポイントは、「どこかにある respond_to? を alias で引っ張る」のではなく、「モジュールとしての respond_to? をきちんと定義する」方向に変えたことです。
2) ActiveModel::Serialization#read_attribute_for_serialization
- 対象ファイル:
activemodel/lib/active_model/serialization.rb - 変更前:
alias :read_attribute_for_serialization :send- 変更後:
sendの単純エイリアスではなく、明示的なインスタンスメソッドとして定義します。
イメージ:
def read_attribute_for_serialization(name)
send(name)
endこれにより:
Object#sendに暗黙的に依存するaliasを除去read_attribute_for_serializationが Active Model 8.1 の API ドキュメントにもきちんと表示されるようになる(aliasだと rdoc/yard で拾われにくい)
3) ActiveModel::Validations#read_attribute_for_validation
- 対象ファイル:
activemodel/lib/active_model/validations.rb - 変更前:
alias :read_attribute_for_validation :send- 変更後: こちらも
sendのエイリアスをやめて メソッド定義に変更。
イメージ:
def read_attribute_for_validation(name)
send(name)
endmain ブランチでは #56130 によってこのメソッド自体の振る舞いが変わっており、「属性が読めない場合に nil を返す」ようになっていますが、今回の 8-1-stable 向けバックポートにはこの挙動変更は含めていません。
→ あくまで「alias を明示的なメソッド定義に置き換える」だけで、現行 8.1 の挙動は維持します。
- 影響範囲・注意点
実質的な挙動変更はほぼありません
read_attribute_for_serializationとread_attribute_for_validationは「内部的にsendを呼ぶ」点で従来と同じです。- main ブランチにある
read_attribute_for_validationの挙動変更(読めない属性でnilを返す)は、この PR では導入されていません。
互換性
- 8.1 系を Ruby 4.1.0dev 以降で動かした場合でも、警告が出なくなり、Ruby 4.3 でも
NameErrorにならないようになります。 - 既存アプリのコードから見たとき、これらのメソッドの呼び出し結果は従来と同じであるため、互換性リスクは非常に低いです。
- 8.1 系を Ruby 4.1.0dev 以降で動かした場合でも、警告が出なくなり、Ruby 4.3 でも
ドキュメント面
read_attribute_for_serializationとread_attribute_for_validationが 8.1 の公式 API ドキュメントに表示されるようになるため、
「どのメソッドをオーバーライドすべきか」「どのメソッドが提供されているか」が分かりやすくなります。
- 参考情報
- Ruby 側の仕様変更:
- 関連 PR:
- 本 PR でバックポートされている修正: #58817, #56705
- main ブランチでの
read_attribute_for_validationの挙動変更: #56130
- Rails Nightly (8-1-stable) の失敗ログ例:
https://buildkite.com/rails/rails-nightly/builds/4748
#58832 Restore integer bounds from JSON schema cache
マージ日: 2026/9/21 | 作成者: @luizkowalski
- 概要 (1-2文で)
JSON スキーマキャッシュ経由で読み込まれたActiveModel::Type::Integerが、min/maxの境界値を持たないまま freeze されてしまい、fixture ロード時にFrozenErrorが発生する問題を修正した PRです。JSON から復元する際にInteger型の最小値・最大値を再設定することで、シリアライズ時にエラーにならないようにしています。
- 変更内容の詳細
問題の背景
- Rails ではスキーマキャッシュを JSON 形式で保存・読み込みできる。
- JSON から型情報 (
ActiveModel::Type::Integerなど) を復元する際、initializeが呼ばれない経路でオブジェクトが生成される。 - その結果、
Integer型が本来持つべき「最小値 (@min) / 最大値 (@max)」が設定されない状態のままになる。 - モデルスキーマをロードすると、これらの型オブジェクトは freeze される。
- その後、初回の整数値シリアライズ時に「境界値をまだ持っていない」ことに気づいた処理が
@min/@maxを設定しようとするが、対象オブジェクトがすでに freeze 済みのためFrozenError: can't modify frozen ActiveModel::Type::Integerが発生する。
修正の方向性
- JSON スキーマキャッシュから
ActiveModel::Type::Integerをロードする際に、freeze される前のタイミングでmin/maxを復元(もしくは補完)するようにした。 - これにより、freeze 後はシリアライズ時に新たな書き込みは発生しなくなり、
FrozenErrorが起きない。
実際のコード変更(概略)
変更ファイル: activemodel/lib/active_model/type/integer.rb (+6 行)
実際の PR では 6 行だけの追加で、概ね以下のような処理が入っていると考えられます(概念的な擬似コード):
module ActiveModel
module Type
class Integer < Value
# もともと initialize 等で @min/@max を設定している
def init_with(coder)
super
# JSON スキーマから復元されたときに min/max が欠けている場合は補完
@min ||= DEFAULT_MIN
@max ||= DEFAULT_MAX
end
end
end
end実コードは Rails の init_with / encode_with(YAML/JSON シリアライザ)や内部 API に合わせた形ですが、ポイントは「JSON 復元時に @min / @max を必ず持たせる」ことです。
回帰テスト
- 実在する JSON スキーマキャッシュをロード
- 対象の整数カラムの型オブジェクトを明示的に freeze
- その型を使って値のシリアライズを実行
FrozenErrorが出ないことを検証
このテストは修正前だと FrozenError で落ち、修正後はパスします。
また、既存の「42 schema-cache」と「integer」関連テストも全て成功しています。
- 影響範囲・注意点
- 影響を受けるのは、JSON 形式のスキーマキャッシュを利用していて、fixture ロード(または schema キャッシュ経由の初期ロード)で
ActiveModel::Type::Integerを使うアプリケーション。 - 特に、スキーマキャッシュを pre-load / freeze している環境(本番・CI など)で、
FrozenError: can't modify frozen ActiveModel::Type::Integerが sporadic に発生していたケースが解消されます。 - 変更範囲は
ActiveModel::Type::Integerの復元処理に限定されており、通常のInteger型の挙動(バリデーション・キャスト挙動など)には影響しません。 - JSON スキーマキャッシュファイル自体のフォーマット変更はなく、「復元時の初期化ロジック」が強化されただけなので、既存のキャッシュファイルを捨てて作り直す必要は基本的にありません。
- 参考情報 (あれば)
- PR: https://github.com/rails/rails/pull/58832
- 関連クラス:
ActiveModel::Type::Integer - 類似のパターンとして、YAML/JSON 経由のオブジェクト復元で
initializeが通らない場合、init_with/deserialize/from_databaseなどで「インスタンス変数の再初期化」が必要になることがあります。Rails 内部の型クラスをカスタマイズする場合も、このパターンを意識しておくと安全です。
#58783 Avoid materializing descendants when resolving an STI subclass
マージ日: 2026/9/21 | 作成者: @stahor
- 概要 (1-2文で)
Single Table Inheritance(STI) のサブクラス解決時にdescendants.include?(subclass)を使っていたチェックを、subclass <= selfに置き換えることで、STI モデルのインスタンス生成コスト(CPU時間・メモリ割り当て)を大幅に削減した変更です。挙動は変えずに、STI クラス解決のガードロジックだけを高速化しています。
- 変更内容の詳細
対象メソッドは ActiveRecord::Inheritance::ClassMethods#find_sti_class です。ここでは、type カラムなどから求めたクラスが、本当に STI のベースクラスのサブクラスかどうかをチェックするために、以前は以下のように descendants を使っていました。
# 旧コード(概念的)
def find_sti_class(type_name)
subclass = compute_type(type_name)
# STI ベースクラスの known descendants に含まれるかを確認
raise SubclassNotFound unless descendants.include?(subclass)
subclass
enddescendants は以下のような性質があります:
- メモ化されていないため、呼ぶたびに
Class#subclassesをたどってツリーを走査- ノードごとに配列を2つ割り当て
- 最後に線形スキャンで
include?をチェック
- STI モデルのインスタンス生成時 (
find,instantiate,subclass_from_attributesなど) に毎回呼ばれるため、
「レコード数 × サブクラス数」に比例してコストが増える
この PR では、このチェックを Ruby のクラス演算 <= を用いる形に置き換えています:
# 新コード(概念的)
def find_sti_class(type_name)
subclass = compute_type(type_name)
# subclass が self のサブクラス/同一クラスかどうかを ancestors チェーンで確認
raise SubclassNotFound unless subclass <= self
subclass
endRuby において、クラスに対する subclass <= self は以下を意味します:
subclass <= selfがtrueになるのは、selfがsubclass.ancestorsに含まれる場合のみ- これは、
selfを起点にClass#subclassesを再帰的にたどってsubclassを見つけるのと同値な関係
sti_class_for は「定数として現在有効なクラス」だけを返す仕様であり、reloader によってクラスが無効化された場合は constantize / compute_type からそもそも返ってこないため、descendants が内部で行っているリロード用フィルタ(DescendantsTracker.reject!)の有無で結果が変わることはありません。そのため、この置き換えによって挙動や SubclassNotFound のエラーメッセージは変わりません。
テスト面:
- 「直接の子」「孫」「曾孫」「無関係クラス」の lookup が
descendantsを呼ばないことを確認するテストを追加 - 既存の
SubclassNotFound系テストで例外パスの挙動を引き続き担保
パフォーマンス計測(抜粋):
テストスイート内の
Company階層(サブクラス15個)で20000 × Company.instantiate("id" => 1, "type" => "VerySpecialClient"), Ruby 4.0.5:- 旧: 12.80 µs/instantiate, 62 alloc/instantiate
- 新: 4.64 µs/instantiate, 14 alloc/instantiate
46 サブクラスを持つベースクラスに対するガード単体:
descendants.include?: 14.44 µs / 141 allocationssubclass <= self: 0.02 µs / 0 allocations
- 影響範囲・注意点
影響範囲:
- 対象は「STI ベースクラスで
typeカラムなどからサブクラスを解決する処理」のみfindinstantiatesubclass_from_attributesなど、STI モデルのインスタンス生成全般で効果があります。
- API の挙動は変わりません:
- 不正な
typeに対してはこれまで通りActiveRecord::SubclassNotFoundが発生 - 正当なサブクラスはこれまで通りに解決される
- 不正な
注意点:
- クラスリロード・オートローディング:
sti_class_forが返すのは常に「定数として現在有効なクラス」であり、
reloader により削除済みのクラスはconstantize/compute_typeからそもそも返らないため、descendantsによる reloader 経由のフィルタがなくなっても、意味的な差は出ません。
- メタプログラミングで動的にクラスを定義/除去している場合でも、
- STI ベースクラスとサブクラスの継承関係さえ正しく構築されていれば、
subclass <= selfで同じ判定結果になります。
- STI ベースクラスとサブクラスの継承関係さえ正しく構築されていれば、
開発者視点での実務的な影響:
- 多数のサブクラスを持つ STI ベースクラス(数十クラス規模)+
リクエストごとに多数のレコードを instantiate するようなアプリでは、 リクエストの CPU 使用率および GC 負荷が目に見えて減る可能性があります。 - アプリ側のコード変更は不要で、Rails バージョンを上げるだけで恩恵を受けられます。
- 参考情報 (あれば)
- PR: https://github.com/rails/rails/pull/58783
- 関連コード:
activerecord/lib/active_record/inheritance.rb内ActiveRecord::Inheritance::ClassMethods#find_sti_class - この変更は、
descendantsベースのガードを削除しつつ、.newの高速化(3295ff95)など既存の最適化路線に沿う形で、
STI 解決のボトルネックを削るものです。
#58817 Avoid aliasing Kernel#respond_to? in ActiveModel::AttributeMethods
マージ日: 2026/9/20 | 作成者: @yahonda
- 概要 (1-2文で)
ActiveModel::AttributeMethods 内でKernel#respond_to?をaliasで別名定義していた実装を、Ruby 4.1 以降の仕様変更に対応するためにdefine_methodを使う実装に置き換えた PRです。これにより Ruby master / 将来の Ruby 4.2〜4.3 での警告・エラーを回避しつつ、挙動は従来どおり維持されます。
- 変更内容の詳細
背景: Ruby 本体側の仕様変更
Ruby 4.1.0dev 以降では、モジュール内で alias を使う際の探索先の仕様が変わるため、以下のようなコードが警告の対象になりました:
module ActiveModel::AttributeMethods
alias :respond_to_without_attributes? :respond_to?
endここで respond_to? は Kernel に定義されているメソッドですが、ActiveModel::AttributeMethods モジュール自身やその ancestors には定義されていません。
Ruby は歴史的に、このような場合に Object まで遡ってメソッドを探す「フォールバック」 をしていましたが、この挙動が非推奨となり、以下のように段階的に変更されます:
- Ruby 4.1: verbose モードで deprecation warning(今回の Buildkite の警告)
- Ruby 4.2: 常に warning
- Ruby 4.3:
NameErrorを投げる予定
Rails はこのフォールバックに依存して Kernel#respond_to? を別名定義していたため、Ruby master 上で警告が出ていました。
対応内容
alias を使わずに、Kernel#respond_to? を明示的に拾って define_method で定義し直すように変更しました。
元のコード(イメージ):
# ActiveModel::AttributeMethods 内
# もともとはこういう alias があった
alias :respond_to_without_attributes? :respond_to?変更後のコード:
# ActiveModel::AttributeMethods 内
# Kernel#respond_to? の UnboundMethod を使って定義する
#:method: respond_to_without_attributes?
#:call-seq: respond_to_without_attributes?(method, include_private = false) -> true or false
define_method(:respond_to_without_attributes?, Kernel.instance_method(:respond_to?))ポイント:
Kernel.instance_method(:respond_to?)でKernel#respond_to?の UnboundMethod を取得define_method(:respond_to_without_attributes?, ...)で、そのままの実装をrespond_to_without_attributes?としてコピー
→ つまり「ActiveModel 独自のrespond_to?ロジックを一切通さない、純粋なKernel#respond_to?」を得るaliasのように「対象メソッドがどこにあるか Ruby に探索させる」のではなく、Kernel を明示して依存先を固定している
RDoc / API ドキュメントまわりの修正
define_method + UnboundMethod という定義方法は RDoc が通常のメソッド定義として認識しないため、API Docs に載らない問題が出ます。
そこで、以下のような RDoc 指示コメントを追加して、respond_to_without_attributes? を API ドキュメントに明示的に載せるようにしています:
#:method: respond_to_without_attributes?
#:call-seq: respond_to_without_attributes?(method, include_private = false) -> true or false
define_method(:respond_to_without_attributes?, Kernel.instance_method(:respond_to?))これにより:
respond_to_without_attributes?が正式にドキュメント化される- 以前は
respond_to?向けの説明コメントがalias行の直前にあったため、RDoc がそれをrespond_to_without_attributes?に紐付けてしまい、respond_to?側には説明がないというドキュメントのバグも合わせて解消されます
- 影響範囲・注意点
ランタイム挙動は変わらない想定
- これまで
respond_to_without_attributes?は実質的にKernel#respond_to?の別名でしたが、今回もKernel#respond_to?の UnboundMethod を使っているため、同じ動きを維持します。 - Active Model の
respond_to?をオーバーライドしたコードにおいても、respond_to_without_attributes?を呼べば Active Model 側の拡張をバイパスして元の Kernel 実装を呼び出す という従来の前提は変わりません。
- これまで
Ruby 4.1 以降での警告が解消
rails/rails-nightlyの Ruby master(4.1.0dev)上でRAILS_STRICT_WARNINGS=1を指定してテストスイートを実行した結果、警告が出なくなったことが確認されています。- 将来の Ruby 4.2 での無条件警告、および 4.3 での
NameError発生もこの変更により回避されます。
テスト・CHANGELOG
- 仕様追加や挙動変更ではなく内部実装の置き換えであるため、テスト追加 / CHANGELOG 追記は行われていません(既存テストでカバーされている想定)。
- 参考情報
Ruby 本体の関連チケット・PR
- https://bugs.ruby-lang.org/issues/22276
- https://github.com/ruby/ruby/pull/18740
- 関連議論: https://bugs.ruby-lang.org/issues/22273#note-8, #note-9, #note-3
- Matz の方針:
モジュールは自分自身とその ancestors にあるメソッドだけを alias できるべきで、それ以外を alias しようとしたら
NameErrorを投げるべき - 代替案として
define_method経由でオリジナルメソッドをコピーするやり方が推奨されている
- Matz の方針:
Rails API ドキュメント上の該当箇所(修正前の状態)
#58826 Avoid implied <p> tags in collection cache test
マージ日: 2026/9/20 | 作成者: @Saidbek
概要 (1-2文で)
Rails のCollectionCacheTestが、libxml2 2.15 以降で暗黙的に挿入されなくなった<p>タグに依存して失敗していたため、HTML スナップショットではなくテキスト内容と順序を検証する形にテストを修正した PR です。動作仕様やランタイムの挙動には影響せず、テストのみの変更です。変更内容の詳細(あればサンプルコードも含めて)
- 変更ファイル:
actionpack/test/controller/caching_test.rb - 行数レベルの小さな変更ですが、主な内容は以下です。
従来のテストは、HTML パーサ(libxml2)によって補完される「暗黙の <p> タグ」を含んだ HTML 断片を期待値として保持し、「HTML の文字列全体が一致するか」を見る形だったと考えられます。
libxml2 2.15 からは HTML パーサの挙動が変わり、以前は自動的に挿入されていた <p> タグが入らなくなり、その結果「期待している HTML スナップショット」と実際の出力 HTML が不一致になってテストが落ちていました。
この PR では、以下のような方向性でアサーションが書き換えられています。
- 「タグ構造(暗黙の
<p>の有無)」に依存せず - 「コレクションとしてのテキスト内容」と「その順序」が正しいかを検証
イメージとしては、例えば以前が:
# 例: 暗黙の <p> を含む HTML 文字列をそのまま比較していた
assert_dom_equal <<~HTML, response.body
<div>
<p>item 1</p>
<p>item 2</p>
</div>
HTMLのような形だったのに対し、今回の変更後は:
# 例: DOM をパースし、テキストのリストと順序を検証するような形
assert_equal ["item 1", "item 2"], extracted_collection_textsといった形に近づけた、という意図です(実際のコードは 1 行差分のみですが、ロジック上はこういう方向への修正)。
ポイント:
- libxml2 のバージョンによって HTML の「タグ補完」結果が変わってもテストが安定するようにした
- 元々確認したかったのは「コレクションキャッシュの内容と並び」であり、「HTML パーサがどうタグを埋めるか」ではないため、テストの目的にもより忠実な形になっている
- 影響範囲・注意点
- 影響範囲:
- Rails のテストコードのみ(
CollectionCacheTest)。 - 本番環境・アプリケーションコードの挙動や API は一切変更されません。
- Rails のテストコードのみ(
- 注意点:
- Rails 本体を開発しつつ、libxml2 2.15 以降の環境でテストを回している場合、この PR により
CollectionCacheTestの失敗が解消されます。 - 同様に「HTML スナップショットをそのまま文字列比較していて、libxml2 のタグ補完挙動に依存しているテスト」が他にもある場合、今後もバージョン差で落ちうるため、同じように「本当に検証したい内容(テキストや要素の順序など)」に絞ったアサーションに変更するのが望ましいです。
- Rails 本体を開発しつつ、libxml2 2.15 以降の環境でテストを回している場合、この PR により
- 参考情報 (あれば)
- 関連 issue: #58825 — libxml2 2.15 環境での
CollectionCacheTest失敗の報告。 - libxml2 2.13 と 2.15 の両方でテストが通ることが明示されており、「HTML パーサ実装依存のテストを避ける」というテスト設計上のベストプラクティスの一例と言えます。
#58812 Update Active Model Ractor tests skips
マージ日: 2026/9/19 | 作成者: @byroot
- 概要 (1-2文で)
このPRは、Active Model における Ractor 関連テストのスキップ条件を、最新の Ruby 本体側の修正状況に合わせて更新したものです。Ruby本体にマージされた https://github.com/ruby/ruby/pull/18515 により Ruby 4.1 ではテストが動作するはず、という前提で、Ractorテストの「どのバージョンで実行してどのバージョンでスキップするか」を整理しています。
- 変更内容の詳細
※実際のコードは提示されていないため、PR説明と変更ファイルから推測される内容を技術的背景込みで解説します。
背景
- Rails の Active Model には、Ractor(Ruby 3.0 以降の並行実行機構)対応を検証するテストが含まれています。
- しかし、Ruby 本体側に未修正のバグや足りない機能がある場合、そのバージョンではテストが「Rails側の問題」ではなく「Ruby側の制約」で落ちてしまうため、Railsのテストでは条件付きで
skipする必要があります。 - 先行 PR: https://github.com/rails/rails/pull/58780 で、Active Model の Ractorテストのスキップロジックが導入/調整されており、本PRはそれの followup として、Ruby本体の修正状況を踏まえて再調整したものです。
今回の主な変更
対象ファイル:
activemodel/test/cases/attribute_registration_test.rbactivemodel/test/cases/conversion_test.rbactivemodel/test/cases/naming_test.rb
これらはいずれも Active Model の機能(属性登録、変換、命名)に関するテストケースで、Ractor 上での動作検証を含んでいます。
具体的な変更点の性質としては:
Ractorテストの skip 条件の更新
- 以前は「Rubyの特定バージョン(X系)では Ractor がサポートされていない / バグがある」として
skipしていた条件を、Ruby本体PR https://github.com/ruby/ruby/pull/18515 のマージを踏まえて更新しています。 - 説明文より:
https://github.com/ruby/ruby/pull/18515 was merged, so at least it should work on 4.1 now- → Ruby 4.1 では、該当の Ractor 関連バグが修正済みとみなし、Ruby 4.1 以降ではテストをスキップせず実行するように変更している可能性が高いです。
- 「for now there's no backport plans」とあるので、
- Ruby 4.0 以前へのバックポート予定はなく、
- 4.0 以前では依然として Ractorテストを
skipする、という条件分岐にしていると考えられます。
想定されるコードイメージ(※概念的な例):
ruby# 例: テストクラス内 def setup skip "Ractor tests require Ruby 4.1+ with PR 18515" unless ruby_supports_ractor_for_active_model? end private def ruby_supports_ractor_for_active_model? ruby_version >= Gem::Version.new("4.1") end実際の実装では
RUBY_VERSIONやdefined?(Ractor)、あるいは Rails の内部ヘルパー(ruby_versionユーティリティなど)を用いてバージョンチェックや機能チェックを行っているはずです。- 以前は「Rubyの特定バージョン(X系)では Ractor がサポートされていない / バグがある」として
テストの Ractor 対応前提の明確化
- Ractor 上で Active Model が動くことを前提にしたテスト(例: モデルの属性定義や命名規則が Ractor 間で安全に利用できるか)について、「どの環境で公式にサポートされるか」が明示的になりました。
- 以前は暫定的なスキップ条件だったものが、Ruby 4.1 を境界にした、より明確なポリシーに変更されています。
テストの微調整
- 行数の増減(+53/-45)の中には、単純なスキップ条件の書き換えだけでなく、メッセージや条件式の整理・リファクタリングが含まれている可能性があります。
- たとえば、複数のテストファイルで同じような Ractor サポート条件を繰り返していたものを、共通化したり、条件を統一したりしていることが考えられます。
- 影響範囲・注意点
CI / テスト環境への影響
- Ruby 4.1 以降の環境では、これまで
skipされていた Active Model の Ractor 関連テストが実行されるようになります。 - そのため、Ruby 4.1 + 最新 Rails で CI を回している場合、Ractor関連の不具合があればテストが落ちるようになります(これまで「Rubyの制約として隠れていた問題」が顕在化する可能性がある)。
- Ruby 4.1 以降の環境では、これまで
Ruby バージョン互換性
- PR本文にある通り、Ruby本体側の修正は 4.1 以降でのみ提供され、現時点でのバックポート予定はありません。
- Ruby 3.x や 4.0 で Rails を使っている場合、Ractor 関連の Active Model テストは引き続きスキップされる想定です。
- したがって、Ractorを前提にアプリを運用したい場合は、Ruby 4.1 以降を使うのが事実上の前提になります。
- PR本文にある通り、Ruby本体側の修正は 4.1 以降でのみ提供され、現時点でのバックポート予定はありません。
アプリケーション側コードへの直接の影響
- 変更対象はテストコードのみであり、Active Model の本体実装には変更がないため、アプリケーションコードの挙動そのものには影響しません。
- ただし、Ractor を活用している(または検証している)アプリでは、Ruby 4.1 以降で Rails のテストを走らせることで、Active Model 周辺が Ractor セーフかどうかをより正確に検証できるようになります。
- 参考情報 (あれば)
- 本PR:
- 先行PR(スキップロジックを導入した/更新した PR):
- Ruby本体側の修正(Ractor関連の不具合対応):
- Ractor 概要(Ruby公式ドキュメント / リファレンス):
- https://docs.ruby-lang.org/ja/latest/doc/ractor_md.html (日本語ドキュメントが更新されていれば参照推奨)
#58820 Stop leaking RACK_ENV out of Rails::DBConsoleTest#test_env
マージ日: 2026/9/19 | 作成者: @yahonda
- 概要 (1-2文で)
Rails::DBConsoleTest#test_envがテスト実行前に設定されていたRACK_ENVをテスト後に消してしまい、LeakCheckerによって「環境変数リーク」と判定されていた問題を修正するPRです。テスト開始時点のRAILS_ENV/RACK_ENVの値を保存し、ensureで必ず元の状態に戻すように変更しています。
- 変更内容の詳細
問題の背景
該当テスト:
Rails::DBConsoleTest#test_env(railties/test/commands/dbconsole_test.rb)以前の実装では
ensureブロック内で無条件に以下のような処理を行っていました(擬似コード):rubyensure ENV["RAILS_ENV"] = nil ENV["RACK_ENV"] = nil endENV["FOO"] = nilは「ENVからFOOキーを削除する」動作になるため、- テスト実行前に
RACK_ENVが設定されていた場合(例: CIのDockerイメージでRACK_ENV=test) - テスト後に
RACK_ENVが消える
→LeakCheckerが「Removed variables: RACK_ENV」として環境変数のリーク(実際には“テストが環境を壊した”)を検出する。
- テスト実行前に
修正内容
テスト開始時点での
RAILS_ENVとRACK_ENVの状態を保存し、ensureでその値を復元するようになりました。典型的には以下のような形になります(イメージコード):
rubydef test_env original_rails_env = ENV["RAILS_ENV"] original_rack_env = ENV["RACK_ENV"] # ... テスト本体 ... ensure ENV["RAILS_ENV"] = original_rails_env ENV["RACK_ENV"] = original_rack_env endポイント:
- もともと環境変数が「なかった」場合は
original_*がnilになり、ENV["FOO"] = nilにより「なかった状態」に戻る。 - もともと環境変数が「存在していた」場合はその値が再度代入されるため、テストによる変更はクリーンアップされる。
- もともと環境変数が「なかった」場合は
これにより:
RACK_ENVが事前に設定されていても、テスト終了後には元の値に戻る。RACK_ENVが事前に未設定でも、終了後には未設定のままになる。LeakCheckerが「追加・削除された環境変数」として検出しなくなる。
- 影響範囲・注意点
影響範囲:
- 対象は
railties/test/commands/dbconsole_test.rb内のtest_envテストのみ。 - Rails本体のランタイム挙動や本番環境の
RACK_ENV/RAILS_ENV処理には影響しません。 - 他のテストも含め、
test/commands/dbconsole_test.rb全体は引き続き成功することが確認済みです。
- 対象は
注意点:
- テスト内で
ENVを書き換える場合は、このPRと同様に「元の値を保存してensureで戻す」パターンを守らないとLeakCheckerによる環境リーク検出の対象になります。 - CI やローカルで
RACK_ENVをあらかじめ設定していても、この修正によりRails::DBConsoleTest#test_envがそれを壊さなくなります。 ENV["FOO"] = nilが「キー削除」であることを前提にした挙動なので、今後同様のテストを書くときもこの点を意識する必要があります。
- テスト内で
- 参考情報 (あれば)
LeakChecker導入コミット:64aa7ee98e02d002e44e562c45eb7a390b93d753問題の原因となった過去の変更:
0c885d68189(2014年のコミットでENV["RACK_ENV"] = nilが導入)再現コマンド(PR説明より):
bashcd railties RACK_ENV=test bundle exec ruby -w -Itest test/commands/dbconsole_test.rb -i test_envこのPR適用前は
Environment leak detected: - Removed variables: - RACK_ENVが出て失敗し、適用後は通過するようになります。
#58792 Stop leaking RAILS_ENV when build_app is called twice
マージ日: 2026/9/19 | 作成者: @yahonda
- 概要 (1-2文で)
build_appが同一テスト内で複数回呼ばれた際にRAILS_ENV環境変数がリークする問題を修正した PR です。RAILS_ENVの元の値の保持方法を||=からdefined?チェックに変更し、1回だけ正しくキャプチャされるようにしています。
- 変更内容の詳細(あればサンプルコードも含めて)
問題となっていたのは、テストヘルパ内での RAILS_ENV の退避処理です。もともと以下のようなコードになっていました(概念的な例):
def build_app
@prev_rails_env ||= ENV["RAILS_ENV"]
ENV["RAILS_ENV"] = "development"
# ... 中略 ...
end
def teardown_app
if @prev_rails_env
ENV["RAILS_ENV"] = @prev_rails_env
else
ENV.delete("RAILS_ENV")
end
endここでの問題点:
- テスト開始時に
RAILS_ENVが「未設定」の場合、ENV["RAILS_ENV"]はnil @prev_rails_env ||= ENV["RAILS_ENV"]はnilのとき毎回評価されるため、- 1回目の
build_app呼び出し:@prev_rails_envにnilをセット ENV["RAILS_ENV"] = "development"に変更- 2回目の
build_app呼び出し:@prev_rails_envはnilなので、再度ENV["RAILS_ENV"]を読み取り、今度は"development"が保存されてしまう
- 1回目の
その結果、teardown_app 実行時には「元の値」として "development" が保存されており、本来は削除すべき RAILS_ENV が "development" に設定されたまま外側(テストプロセス全体)にリークしていました。
PR ではこの退避ロジックを次のような形に変更しています(意図としては以下のようなコード):
def build_app
unless defined?(@prev_rails_env)
@prev_rails_env = ENV.key?("RAILS_ENV") ? ENV["RAILS_ENV"] : :__rails_env_unset__
end
ENV["RAILS_ENV"] = "development"
end
def teardown_app
if @prev_rails_env == :__rails_env_unset__
ENV.delete("RAILS_ENV")
else
ENV["RAILS_ENV"] = @prev_rails_env
end
end実際には 1 行の変更で、退避のトリガーを @prev_rails_env ||= ... から
@prev_rails_env = ENV["RAILS_ENV"] unless defined?(@prev_rails_env)という形に変えています(PR 説明にも「Capturing with unless defined?(@prev_rails_env) records the original value exactly once」と明記されています)。
この defined?(@prev_rails_env) チェックにより:
- 「インスタンス変数がそもそも定義済みか」を見ているため、
- 最初の
build_app呼び出し時にだけ元のRAILS_ENVをキャプチャ - 2回目以降の呼び出しでは
@prev_rails_envを上書きしない
となり、元々の状態(「存在していた値」または「未定義であったこと」)が正しく 1 度だけ保存されます。
これにより、TestHelpersTests::GenerationTest#test_build_app_after_tmp_path_is_removed テストが build_app を 2 回呼び出しても、teardown_app 実行時には「本当にテスト開始前の状態」に戻せるようになり、LeakChecker による「RAILS_ENV が追加された」という検出が解消されます。
- 影響範囲・注意点
- 影響範囲:
railtiesの isolation テストヘルパでのbuild_app/teardown_appのペアでのみ影響します。- 本番コードや通常のアプリ実行時の
RAILS_ENVの扱いには影響しません。 - LeakChecker による環境変数リーク検出のノイズが減り、テストの信頼性が向上します。
- 注意点:
build_appを 1 テスト中に複数回呼び出すケースで、これまで「たまたま」環境変数が戻っていた/戻っていなかったような動きが、今回の修正で安定します。- 元の
RAILS_ENVが未設定だったケースでは、teardown_app実行後にRAILS_ENVは必ず削除され、テスト前の状態(未定義)に戻ります。 - あくまでテストヘルパ内部の扱いなので、アプリ側が
ENV["RAILS_ENV"]の有無に依存するコードを書いていても、この PR がそれを壊すことはありません(テスト環境内でのみの挙動修正)。
- 参考情報 (あれば)
- 該当テスト:
railties/test/isolation/test_helpers_test.rbTestHelpersTests::GenerationTest#test_build_app_after_tmp_path_is_removed - LeakChecker 実装:
tools/support/leak_checker.rb
環境変数やグローバル状態の差分を検出し、テスト間の状態リークを検知するための仕組み。 - 問題の原因となったコミット:
64aa7ee98e02d002e44e562c45eb7a390b93d753(LeakChecker 追加)
LeakChecker によって初めてRAILS_ENVのリークが可視化され、この PR で修正されています。
#58822 Make ActionController::ParamsWrapper._wrapper_options ractor safe
マージ日: 2026/9/18 | 作成者: @gmcgibbon
- 概要 (1-2文で)
ActionController::ParamsWrapper::_wrapper_optionsが Ractor 共有可能になるよう、内部オプションオブジェクトを「深い freeze」とミューテックスのクリアで Ractor-safe にした PR です。合わせてRails::Application#ractorize!に ParamsWrapper 用の初期化処理を追加し、テストで Ractor 対応を検証しています。
- 変更内容の詳細
2-1. ActionController::ParamsWrapper::Options の変更
actionpack/lib/action_controller/metal/params_wrapper.rb に定義されている ActionController::ParamsWrapper::Options クラスの freeze 実装が変更されています。
目的:
- このオプションオブジェクトが Ractor 間で共有される前提になったため、
- ミューテックスのような共有不能オブジェクトを状態として持たない
- 内部の構造を「深く freeze」してイミュータブル化 することで Ractor-safe にする。
典型的には以下のような対応が行われています(コードイメージ):
module ActionController
class ParamsWrapper
class Options
def freeze
# 含まれている Hash / Array / その他の設定値を
# 再帰的に freeze する (deep freeze)
@format.freeze
@include.freeze
@exclude.freeze
@wrap_key.freeze
# ...など、内部状態をすべてfreeze
# Ractor 共有できない同期用オブジェクトを破棄
@mutex = nil
super
end
end
end
end※正確なインスタンス変数名は PR 本体に依存しますが、趣旨としては「内部構造全部を freeze し、mutex をクリア」がポイントです。
従来:
_wrapper_optionsはコントローラごとに生成され、内部でミューテックスを持つなど、スレッドセーフさを担保していたが、Ractor 間共有は想定していなかった。
変更後:
freezeされると、内部構造がすべてイミュータブルかつ Ractor 共有可能になるよう調整。- ミューテックスは Ractor 共有できないため、
freeze時に捨てる設計へ。
2-2. テスト追加 (ParamsWrapper の Ractor 対応)
actionpack/test/controller/params_wrapper_test.rb にテストが追加され、ParamsWrapper::Options の freeze 後に Ractor 間で使えること、あるいは Ractor での利用時にエラーにならないことを検証しています。
例として、以下のようなパターンが含まれるはずです:
- コントローラで
wrap_parametersを設定 _wrapper_optionsを freeze する- 新しい Ractor にそのオプションを渡してパラメータラッピングを実行
- 期待通りにラップされる、または例外が発生しないことを確認
2-3. Rails::Application#ractorize! への追記
railties/lib/rails/application.rb に 1 行の変更が入り、#ractorize! で ParamsWrapper のオプションを Ractor 共有向けに準備する処理が追加されています。
イメージ:
module Rails
class Application
def ractorize!
super
# ここで ParamsWrapper のオプションを前もって freeze / 初期化する
ActionController::ParamsWrapper.ractorize! # または同等の呼び出し
end
end
end目的:
- アプリケーションを Ractor モードにするときに、各コントローラの
_wrapper_optionsが Ractor-safe な状態(deep freeze 済み & mutex なし)で確定するようにする。
2-4. Railties 側の Ractor テスト追加
railties/test/application/ractors_test.rb にテストが追加されています。
内容としては:
app.ractorize!を呼び出した後に Ractor 内でコントローラを使い、wrap_parameters付きのアクションを通す- Ractor 間で
_wrapper_optionsを共有した状態で動作することの確認
といった流れになっていると考えられます。
- 影響範囲・注意点
影響範囲
ActionController::ParamsWrapperを利用するすべてのコントローラで、_wrapper_optionsのオブジェクトライフサイクルやイミュータビリティに関する挙動が変わる可能性があります。- 特に Rails 7+ で Ractor を利用している、あるいは今後利用予定のアプリケーションで影響が顕在化します。
イミュータブル化の影響
_wrapper_optionsが deep freeze されるため、- freeze 後に
_wrapper_options内部を動的に書き換えるようなメタプログラミングは動かなくなります(本来非推奨)。 - Rails が公式に提供している
wrap_parametersの DSL 経由での設定は、Ractor 化前に完了させるべきです。
- freeze 後に
Ractor 利用時の前提
Rails::Application#ractorize!を呼ばずに独自に Ractor を立ち上げてコントローラを共有する場合、_wrapper_optionsが Ractor-safe でない状態で共有される可能性があります。- Ractor を利用するアプリケーションでは、必ず
app.ractorize!(またはそれを呼び出す標準の初期化フロー)を通すことが前提になります。
パフォーマンス
- deep freeze 処理自体は一度きりの初期化時であり、通常のリクエスト処理中に毎回行われるものではないため、一般的には大きなパフォーマンス劣化は想定されません。
- ただし wrap パラメータ設定が極端に大きなデータ構造を持つ場合、freeze 時のオーバーヘッドがわずかに増える可能性はあります(通常は無視できるレベル)。
後方互換性
- 公開 API (
wrap_parametersDSL 等) の使い方を守っている限り、挙動上の破壊的変更はほぼありません。 _wrapper_optionsとその内部構造に直接アクセスしていたり、ミューテックスや内部フィールドを前提にしたハックは壊れる可能性があります。
- 公開 API (
- 参考情報 (あれば)
Ractor の制約:
- Ruby の Ractor では、Ractor 間で共有できるのは「共有可能オブジェクト(frozen なオブジェクトなど)」のみで、ミューテックスや非 freeze オブジェクトは共有できません。
- そのため、Rails 内部の設定オブジェクトを Ractor-safe にするには、deep freeze と共有不能オブジェクトの排除が必須となります。
関連しそうなコード/キーワード:
ActionController::ParamsWrapperActionController::ParamsWrapper::OptionsRails::Application#ractorize!wrap_parametersDSL- Ractor / Ractor safety / shareable objects
この PR により、ParamsWrapper の設定も含めて Rails コントローラレベルの設定オブジェクトが Ractor 対応に一歩近づいており、将来的なマルチ Ractor 実行環境での安定性向上につながります。
#58733 Avoid deadlocks when concurrent find_or_create_by calls read back the…
マージ日: 2026/9/18 | 作成者: @kwestein
- 概要 (1-2文で)
MySQL 環境でfind_or_create_by/create_or_find_byをトランザクション内で並行実行した際に起きていたデッドロック(1213)を防ぐため、重複 INSERT 後の「読み直し」のロック戦略を変更した PRです。SELECT ... FOR UPDATEではなく共有ロックを使った読み直しに切り替えることで、意図せずデッドロックが発生するケースを解消します。
- 変更内容の詳細
問題の背景
Rails 8 では:
find_or_create_by→create_or_find_byに委譲- レコードが存在しない前提で
INSERTを行い、Duplicate entry (1062)が出たら「すでに存在する」と判断して読み直す、というパターンになっている
MySQL (InnoDB) + トランザクション + セーブポイントの組み合わせで、以下のような流れになると問題が起きていました:
- 複数のトランザクション T1, T2 が同時に
create_or_find_byを実行 - 先にコミット(あるいは外側トランザクションで INSERT)した側により、同じユニークキーのレコードがすでに存在している状態になる
- 後から来た側(「loser」)の
INSERTはセーブポイント内で1062 Duplicate entryエラーになり、重複したインデックスレコードに対して shared lock (S ロック) を保持したまま になる create_or_find_byは「トランザクション中なので」「直前の書き込みを確実に見る」ためにSELECT ... FOR UPDATEで読み直す- 複数の loser がそれぞれ S ロックを持ったまま、同じレコードに対して X ロック(FOR UPDATE)を要求し合い、S→X のロックアップグレードでお互いをブロックしてデッドロック(1213) になる
本来 find_or_create_by / create_or_find_by は「並行に安全に使いたい」メソッドであるため、ここでのデッドロックは意図しない挙動です。
この PR の対策
この PR は、重複 INSERT 後の読み直しで使用するロックモードを変更することで、上記のデッドロックを避けています。
要点:
- 対象:
find_or_create_by/find_or_create_by!create_or_find_by/create_or_find_by!
- 対象 DB:
- MySQL / MariaDB(両アダプタ)
- 変更内容:
- 「トランザクション中かつ直前の
INSERTが重複だった場合の、レコード読み直し」において、従来のSELECT ... FOR UPDATE(排他ロック)ではなく、共有ロックでの読み直しに切り替える
- 「トランザクション中かつ直前の
具体的には:
- InnoDB の
REPEATABLE READでは、共有ロックによる読み取りでも「自トランザクション内の直前の書き込み/失敗情報の可視性」は保持される - しかし
FOR UPDATE(X ロック)は「S ロック → X ロックへのアップグレード」を伴うため、複数トランザクションがお互いに S を持ちつつ X を要求してデッドロックしうる - そこで「読み直し時点では共有ロックに留める」ことで、
- デッドロックを起こしやすい S→X の競合をそこで発生させない
find_or_create_by自体の実行でデッドロックに陥るのを避ける
ソース的なポイント(概念レベル):
ActiveRecord::Relationや MySQL アダプタ (abstract_mysql_adapter.rb) に、- 「重複 INSERT 後の再読込用のロックモード」
- ないし「共有ロックでの再読込」を行うための分岐・ヘルパー が追加されている
abstract_adapter.rbに、抽象的なロック指定やアダプタ依存のロック戦略を切り替えるためのフックが追加されている可能性が高いrelations_test.rbに、以下をカバーする回帰テストが追加REPEATABLE READとREAD COMMITTEDの両隔離レベル- セカンダリユニークインデックス経由の
find_or_create_by/create_or_find_by - ネストしたセーブポイント(
transaction do ... transaction(requires_new: true) do ... end endのようなケース) - MySQL 8.4 / MySQL 5.7 / MariaDB での挙動確認
擬似的なイメージコード
(実際の Rails 内部コードではありませんが、変更イメージ)
def create_or_find_by(**attributes, &block)
transaction(requires_new: true) do
begin
record = new(attributes)
yield record if block
record.save!
record
rescue ActiveRecord::RecordNotUnique
# 以前はここで exclusive ロックを取るために FOR UPDATE を使っていた
# record = where(attributes).lock("FOR UPDATE").take!
# 今回の PR では、MySQL の場合ここを共有ロック(あるいはそれに準ずるモード)で読み直す
record = where(attributes).lock(shared: true).take!
record
end
end
end実際はアダプタ毎に lock(shared: true) をどう SQL に落とすか(LOCK IN SHARE MODE 相当 or 新しい構文)を切り替えているイメージです。
- 影響範囲・注意点
影響範囲
- 主に MySQL / MariaDB を使用している Rails アプリで、
find_or_create_bycreate_or_find_by- その bang 版 (
!) をトランザクション内で高い並行度で利用しているケースに影響します。
期待される効果:
- これらのメソッドを高並行で呼び出しても、
- 「重複 INSERT →
RecordNotUnique→ 読み直し」の流れで - デッドロック(
ActiveRecord::StatementInvalid: Mysql2::Error: Deadlock found ... (1213)) が発生しにくくなります。
- 「重複 INSERT →
トレードオフ / 注意点
PR の説明にもある通り、ロック戦略の変更により次のような性質になります:
同じレコードを複数のトランザクションが「同時に見て返す」ことがありうる
SELECT ... FOR UPDATEであれば、ある程度「誰かが対象レコードを先に独占的に握る」ことで、後続トランザクションは待機(あるいはブロック)し、結果的に更新順がシリアライズされる場合がありました。- 共有ロック読み直しに変わることで、複数のトランザクションが同じレコードを「ほぼ同時に取得」することがより起こりやすくなります。
find_or_create_by/create_or_find_by自体ではなく、「その後の更新」でデッドロックが起こる可能性は残るfind_or_create_byの呼び出し後に、取得したレコードに対してupdateやsaveを行うと、その更新タイミングで X ロック取得が必要になるため、そこで S→X の競合が起きる可能性は依然としてあります。- つまり、**デッドロックの可能性を「読み直しのタイミングから、実際の更新タイミングに後ろ倒しした」**とも言えます。
- PR では、「find_or_create 系のメソッドは、あくまで並行安全に『レコードを取得・新規作成』するところまでを保証し、その後の任意の更新ロジックまでは面倒を見ない」という設計寄りの判断になっています。
アプリケーション側での考慮点
find_or_create_by/create_or_find_byの直後にそのレコードの更新を前提としたロジックを組む場合、必要に応じて明示的なロック制御を検討してください。- 例: どうしても「同時更新を完全に直列化したい」場合は、
with_lockやトランザクション全体でのロック戦略(テーブル単位・行単位)を設計し直す - あるいは、アプリケーションロジック上の競合そのものを避ける(冪等設計・再試行戦略など)
- 例: どうしても「同時更新を完全に直列化したい」場合は、
- 参考情報 (あれば)
元 issue:
- https://github.com/rails/rails/issues/54281
- MySQL での
create_or_find_by/find_or_create_byにおけるデッドロック報告と再現手順が議論されています
- MySQL での
- https://github.com/rails/rails/issues/54281
InnoDB のロック・隔離レベルに関する参考(公式ドキュメント):
- MySQL 8.0 Reference Manual: InnoDB Locking
https://dev.mysql.com/doc/refman/8.0/en/innodb-locking.html - InnoDB Transaction Isolation Levels
https://dev.mysql.com/doc/refman/8.0/en/innodb-transaction-isolation-levels.html
- MySQL 8.0 Reference Manual: InnoDB Locking
この PR でのテスト対象:
- MySQL 8.4 / 5.7
- MariaDB
REPEATABLE READ/READ COMMITTED両隔離レベル- セカンダリユニークインデックス / ネストしたセーブポイント
Rails アプリの側としては、「MySQL 環境で find_or_create_by / create_or_find_by の並行実行がより安全になったが、その後の更新ロジックに対しては依然としてデッドロックの可能性がある」点を理解しておくとよいです。
#58818 Add bundle exec to guides help command example [ci-skip]
マージ日: 2026/9/18 | 作成者: @p8
- 概要 (1-2文で)
Railsガイド執筆者向けドキュメント中の「ヘルプコマンド」の実行例に、bundle execを付けるよう修正したPRです。実行例を、より実際の開発環境に即した正しい形に揃えるためのドキュメントのみの変更です。
- 変更内容の詳細
変更ファイル:
guides/source/ruby_on_rails_guides_guidelines.md(1行の置き換え)
具体的には、Railsガイドの「Guidelines」(ガイド執筆ルール)内にある「ガイド関連の rake タスクや help コマンドの実行例」が、次のように修正されています(内容は推定ですが、典型的な修正はこのパターンです):
- rake guides:help
+ bundle exec rake guides:helpもしくは同等の「rake だけで実行していた」例を「bundle exec 経由で実行する」書き方に変更しています。
ポイント:
- Bundler 管理下の gem 環境でタスクを実行することを前提にした表記に統一
- ガイド執筆者・メンテナ向けに、「Rails リポジトリを clone して作業する場合は bundle exec を付けるのが正しい」という運用を明示
- 影響範囲・注意点
影響範囲
- コード(アプリ本体やテストコード)には一切影響なく、ドキュメントのみの変更です。
- Rails ガイドをローカルで生成したり、ガイド関連タスクを実行する人(コントリビューター、メンテナ)が対象です。
実務上の注意点
- Rails のリポジトリを手元に clone してガイド関連の rake タスクやヘルプを実行する場合は、今後は原則として:bashのように
bundle exec rake guides:helpbundle execを付けるのが推奨されます。 bundle execを付けることで、Gemfileに定義された依存関係に沿ったバージョンの rake / railties / その他の gem が確実に使われ、環境差異やグローバル gem の影響による不具合を避けられます。
- Rails のリポジトリを手元に clone してガイド関連の rake タスクやヘルプを実行する場合は、今後は原則として:
- 参考情報 (あれば)
- Bundler 公式ドキュメント(
bundle execの説明)
https://bundler.io/man/bundle-exec.1.html - Rails コア開発やガイド生成では、一般的に
bundle exec rake .../bundle exec ruby ...といった形で Bundler 管理下での実行が推奨されています。
#57181 [RF-DOCS] Rewrite the Asset Pipeline Guide
マージ日: 2026/9/18 | 作成者: @ayushn21
- 概要 (1-2文で)
Asset Pipelineガイドが Propshaft を前提に、開発者視点で「どう使うか」にフォーカスした内容へほぼ全面的に書き換えられました。Sprockets からの移行や特定ライブラリ(Tailwindなど)の細かい手順はガイド外部へのリンクに整理され、全体構成・見出し・導入文が見直されています。
- 変更内容の詳細
ガイド全体のリライト方針
- 「Asset Pipelineの仕組み」よりも「開発者がどう使うか」を重視した構成に変更
- Propshaft を用いた現行の Rails 標準的な使い方が中心
- 同じ内容の重複や誤りを削除し、流れを整理
- 導入部("After reading this guide, you will know:")の見直しが示唆されており、今後:
- 「JavaScript / CSS アセットの配信方法」
- 「JS / CSS 用 bundler の使い方」 など、ガイドの対象範囲が分かる書き方にする方針が提示されています。
- 「Propshaft の機能」というより「Asset Pipeline の機能」を説明する、よりライブラリ非依存な書き方に寄せる方向がコメントされている。
「Asset Pipelineとは何か?」セクションの書き換え
- タイトル・内容が「一般的な asset pipeline」ではなく「Rails の Asset Pipeline」を説明するように修正方向。
- 内部実装寄り/Propshaft寄りだった説明を、Rails 実開発でのイメージに寄せた解説へ変更。
- 使用例を中心に説明する提案:erbが最終的に:
<%= stylesheet_link_tag "application", media: "all" %>htmlのような fingerprint 付き URL に解決される、という典型的な流れを示す形へ。<link rel="stylesheet" href="assets/application-55f8f81.css" media="all" /> - 「Propshaft はトランスパイル/バンドル/圧縮がそれほど重要でない時代向け」というような将来陳腐化しやすい説明文は削除方向。
Propshaft の説明と構成の整理
- Propshaft の説明が「Asset Pipelineとは?」のセクションに散らばっていたものを、
- 「Propshaft Features」
- 「Working with/Configuring Propshaft」 などに整理して、Propshaft固有の話と Asset Pipeline一般の話を分離する方向へ変更。
- 「Propshaft Features」セクション:
- ただちに参照した読者にも意味が通るよう、Propshaft の基本的な説明をここに集約。
- アセットの読み込み順(Asset Load Order)など、やや高度な挙動・注意点は Propshaft の基本セットアップ説明の後に説明する構成に変更。
- 依存関係戦略(dependency strategies)の説明を番号付きリストではなく、見出しつきの小セクションとして整理しスキャンしやすくする方向。
- 「Working with Propshaft」セクション:
- タイトルが曖昧なので「Configuring Propshaft」や「Using Propshaft for the Asset Pipeline」など、役割が明確な名称に変更方向。
- Propshaft セットアップ手順は箇条書きステップではなく、文章+コード例の通常セクションとして再構成。
- 「コンパイル後のファイル名が .js / .css で終わる必要がある」という注意点は、旧 Sprockets 前提の文脈(
.sass,.coffee)説明を失っており分かりにくかったため、Propshaft の「事前フルコンパイルしない」特性を踏まえて場所や表現を見直し。 - CDN利用の説明を独立したトップレベルセクションにする方向に変更(大きな節だがサイドバーに出てこない問題を解消するため)。
- サンプルURLのスペルミス/不正なドメイン(
http://www.example/assets/...)をhttp://www.example.com/assets/...のように修正。
Sprockets → Propshaft 移行関連の整理
- ガイド中にあった Sprockets から Propshaft への移行手順・比較説明を大幅に整理:
- 「Sprockets to Propshaft」 セクションは本来「Migrating from Sprockets to Propshaft」とすべきという方針が示されており、新規アプリの標準ルートではないことを明示。
- 移行手順や、Sprockets 時代の機能との比較解説は、Propshaft リポジトリ側の migration guide へのリンクに一本化。
- 「Bundling」「Transpilation」などの説明は、「Advanced Asset Management」セクションに集約する構成。
- Sprockets でサポートされ Propshaft では非サポートな機能は、
- 「Evolution of Asset Management Techniques」
- 「Sprockets vs. Propshaft」 という分散した説明ではなく、「Unsupported features in Propshaft」のようなまとめに集約する方向。
- 全体の読者層に応じたセクション順序を定義:
- Basic Propshaft 利用(大多数)
- 高度なアセット管理(Bundler などを使う読者)
- 既存アプリの Sprockets からの移行(ニッチ)
Advanced Asset Management セクションの整理
- jsbundling-rails / cssbundling-rails / importmap-rails など「高度なアセット管理」の説明を1つのトップレベルセクションに集約。
- 各ツールの「How X Works」説明:
- 順序を意味しない番号付きリストではなく、通常の箇条書きか段落+コード例に変更する方針。
- cssbundling-rails や importmap-rails に関する注意書きは、単なるテキストではなく Rails Guides 標準の
NOTE:/WARNING:ブロックとして明示。 - 見出しも「ライブラリ名」ではなく「ユースケース」が分かる名前に寄せる方向:
jsbundling-rails→ 「JavaScript Bundling」/「Using JavaScript Bundlers」cssbundling-rails→ 「CSS Bundling」/「Using CSS Bundlers」tailwindcss-rails→ 「Tailwind CSS integration」importmap-rails→ 「Using Import Maps」
- Tailwind の章はガイドから削除し、
tailwindcss-rails/dartsass-rails双方の GitHub へのリンクのみ掲載:- 特定ツール(Tailwind)のみ深掘りするのはバランスが悪いこと
- 詳細手順は各リポジトリの README にあるため、ガイド本体からは外す方針。
画像・構成ファイルの更新
- 新しい Asset Pipeline のフローを示す図:
guides/assets/images/asset_pipeline/asset_pipeline_flow_diagram-dark.jpgguides/assets/images/asset_pipeline/asset_pipeline_flow_diagram-light.jpgを追加(あるいは差し替え)。Propshaft 中心の新しいパイプライン像を可視化。
guides/source/documents.yamlで Asset Pipeline ガイドのメタ情報(タイトル等)も更新されており、サイドバーやガイド一覧での表示内容・順序が変更されています。
- 影響範囲・注意点
- ドキュメント利用者(Rails 開発者)
- Asset Pipeline の公式説明が Propshaft 中心の最新構成になり、Sprockets ベースの古い記述やミスリーディングな情報が減少。
- Tailwind / Dart Sass 等、特定ライブラリの手順はガイドから消え、代わりに外部リンク参照となるため、
- 以前ガイドだけで完結していたセットアップ手順を期待している場合は、各 gem の README を読む必要がある。
- Sprockets ユーザ
- Rails Guides からは移行の細かいステップがほぼ消え、Propshaft リポジトリの migration guide に一本化される。
- Sprockets 特有の機能を探す場合、ガイドよりも Sprockets / Propshaft 各リポジトリ側のドキュメントを参照する必要が強まる。
- Propshaft / Bundler 関連の設定
- ガイド記載のコマンド・URL サンプルの一部が修正されているため、古いバージョンのガイド・書籍・ブログ記事と食い違う箇所が出る可能性がある。
- Rails コア / ガイド翻訳者
- 行数ベースで +654/-919 と大きく改変されているため、各国語版ガイドはかなりの量の差分追従が必要。
- 新しいセクション構成(Basic → Advanced → Migration)の流れを前提に翻訳・インデックスを見直す必要がある。
- 参考情報 (あれば)
- Propshaft:
- https://github.com/rails/propshaft
- Migration guide(Sprockets → Propshaft)はこのリポジトリ側に集約。
- アセット関連公式ツール:
- jsbundling-rails: https://github.com/rails/jsbundling-rails
- cssbundling-rails: https://github.com/rails/cssbundling-rails
- importmap-rails: https://github.com/rails/importmap-rails
- tailwindcss-rails: https://github.com/rails/tailwindcss-rails
- dartsass-rails: https://github.com/rails/dartsass-rails
この PR により、Rails 7 以降の「Propshaft + Bundler/Importmap」という標準的な構成を前提に、Asset Pipeline ガイドが現代化された形になります。
#58794 Deprecate ActiveRecord::Callbacks::CALLBACKS
マージ日: 2026/9/18 | 作成者: @kamipo
- 概要 (1-2文で)
ActiveRecord::Callbacks::CALLBACKS定数が非推奨になり、Rails 9.0 で削除予定になりました。
この定数は長年更新されておらず、特にトランザクション関連のコールバックが含まれていないため、利用するとコールバックが一部無視される危険があることが明示されています。
- 変更内容の詳細
2-1. 何が非推奨になったか
非推奨になったのは以下の定数です:
ActiveRecord::Callbacks::CALLBACKSこの定数は「Active Record で利用できるコールバック名の一覧」として歴史的に存在していましたが、以下のようなトランザクション関連コールバックが含まれていませんでした:
before_commitafter_save_commitafter_create_commitafter_update_commitafter_destroy_commit
その結果、この定数を元に「サポートしているコールバックを列挙する」といったロジックを書くと、上記のコールバックが“存在しないもの”として扱われ、実質的にスキップされてしまうという問題がありました。
PR では次のような変更が入っています:
ActiveRecord::Callbacks::CALLBACKSに対して「非推奨 (deprecate)」の警告を出す処理を追加- Rails 9.0 で削除予定であることを
CHANGELOGに追記 - テストコードを更新して、非推奨であることを確認するテストを追加
2-2. 具体的なコードイメージ
実際の実装は概ね以下のようなイメージになります (簡略化した擬似コード):
module ActiveRecord
module Callbacks
# 旧来の定数
CALLBACKS = [:before_validation, :after_validation, ...] # ただし実際は不完全
def self.const_missing(name)
if name == :CALLBACKS
ActiveSupport::Deprecation.warn(
"ActiveRecord::Callbacks::CALLBACKS is deprecated " \
"and will be removed in Rails 9.0. " \
"It does not include newer transaction callbacks " \
"like before_commit, after_commit, etc."
)
super
else
super
end
end
end
end実際のコードはこれと多少異なる可能性はありますが、要点は「この定数を参照したときに deprecation warning を出す」という点です。
- 影響範囲・注意点
3-1. 影響を受けるケース
次のようなコードを書いているアプリケーションやライブラリが影響を受けます:
例1: 利用可能なコールバック一覧として使っている
ActiveRecord::Callbacks::CALLBACKS.each do |callback|
# モデルに定義されているコールバックを列挙する など
endこのようなコードは:
- そもそもトランザクション関連コールバックが含まれていないため、元から不正確
- 今後は deprecation warning が出る
- Rails 9.0 では 定数自体が削除されて動かなくなる
という状態です。
例2: バリデーション用途やメタプログラミングに使っている
def valid_callback?(name)
ActiveRecord::Callbacks::CALLBACKS.include?(name.to_sym)
endこのようなバリデーション・メタ定義も、before_commit などを誤って「無効」と判定する可能性があります。
3-2. どう修正すべきか
この PR では「置き換えとなる新 API」は用意されていません。
理由として:
- 一覧を公開 API として提供すること自体が意図されていない
- 追加・変更されるコールバックを API で“網羅的に列挙”できるようにすると、将来の互換性を縛る
といった設計上の背景が考えられます(PR 説明からも「no replacement」と明言済み)。
代替の考え方:
- そもそも「すべてのコールバックを列挙する」前提の設計をやめる
- 必要なコールバック名を明示的に定義する
- 内部的な DSL などでは、自分で許可するコールバックを列挙する
- Active Record の内部実装に依存しない形にリファクタする
- 例: 自作の concern やモジュールで、サポートするコールバック名を自前で定数化する
修正例
もともと:
SUPPORTED_CALLBACKS = ActiveRecord::Callbacks::CALLBACKSのようにしていた場合:
SUPPORTED_CALLBACKS = %i[
before_validation after_validation
before_save after_save
before_create after_create
before_update after_update
before_destroy after_destroy
before_commit after_commit
after_save_commit after_create_commit
after_update_commit after_destroy_commit
]と、自分のライブラリがサポートしたいコールバックを自前で列挙するようにします(実際に何をサポートするかは要件次第)。
3-3. 移行のポイント
- 短期的には:
- deprecation warning が出ていれば、
ActiveRecord::Callbacks::CALLBACKSを参照している箇所を洗い出す - その箇所を「自前で定義したコールバック名リスト」もしくは別のロジックに置き換える
- deprecation warning が出ていれば、
- 中長期的には:
- Rails 9.0 移行時に、この定数に依存したコードはすべて壊れるため、完全に排除しておく必要があります
- 参考情報 (あれば)
- この PR:
https://github.com/rails/rails/pull/58794 - 変更概要は
activerecord/CHANGELOG.mdにも追記されています - コールバック一覧のドキュメント (最新版のドキュメントを参照推奨):
https://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html
#58793 Stop probing for a local MySQL socket when generating config/database.yml
マージ日: 2026/9/18 | 作成者: @kamipo
- 概要 (1-2文で)
rails new --database=mysql実行時に、ローカルの MySQL ソケットファイルを探してconfig/database.ymlにsocket:を書き込む挙動を廃止し、常にhost:ベースの設定を生成するようにした PR です。Docker/devcontainer などコンテナ環境とより相性の良い、シンプルで持ち運びしやすい設定になります。
- 変更内容の詳細
2-1. これまでの挙動
rails new --database=mysql(および Trilogy 用のテンプレート)を実行すると、Rails のジェネレータは:
- 2008 年頃からハードコードされた複数の MySQL ソケットパスのリストを走査し
- 例:
/tmp/mysql.sock、古い MacPorts のmysql4/mysql5用パスなど
- 例:
- 実在するパスが見つかった場合:
config/database.ymlにsocket: /path/to/mysql.sockを書き込む
- 見つからなかった場合:
host: localhost(などのホスト指定) を書き込む
さらに、devcontainer(VS Code Dev Containers など)を検知している場合は、このソケット検出ロジックを避けるための !devcontainer? ガードがテンプレート内にありました。
2-2. 変更後の挙動
この PR により、次のように変更されました。
ローカルの MySQL ソケットパスを探すロジックを 完全に削除
railties/lib/rails/generators/database.rbからソケット探索コードが削除(-15 行)
MySQL/Trilogy 用の
config/database.ymlテンプレートから:socket:を条件付きで埋め込むコードを削除(-4 行 x 2)!devcontainer?ガードも不要になったため削除
生成される
config/database.ymlは、常にhost:ベース の設定となる例(イメージ):
yamldefault: &default adapter: mysql2 encoding: utf8mb4 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: <%= ENV.fetch("DB_USERNAME", "root") %> password: <%= ENV.fetch("DB_PASSWORD", "") %> host: <%= ENV.fetch("DB_HOST", "localhost") %>
guides/source/configuring.mdとrailties/CHANGELOG.mdに、この挙動変更を反映するための更新が加えられています(合計 +5 行程度のドキュメント更新)。
PR 説明にもある通り:
- 古い MacPorts 用の
mysql4/mysql5ソケットパスは、現代の環境ではほとんど意味がない - 生成したアプリを Docker / devcontainer などで動かす場合、生成マシン固有のソケットパスを
config/database.ymlに焼き込むのは不適切 - そもそも
DB_HOST環境変数でhost:を上書きする前提がある
といった背景から、設計的にも host: 一択に整理された形です。
- 影響範囲・注意点
3-1. 影響範囲
- 対象:
rails new --database=mysql(およびtrilogy)で生成される 新しいアプリケーション - 既存アプリの
config/database.ymlには一切影響なし(マイグレーションは行われない)
3-2. ローカルの Unix ソケット接続を使っている場合
既存の Rails アプリで、例えばこんな設定になっている場合:
development:
adapter: mysql2
database: myapp_development
username: root
password:
socket: /tmp/mysql.sock- この PR によってこれが自動的に変更されることはありません。
- ただし今後、新規に
rails newでアプリを作ると、デフォルトではsocket:は設定されず、host:接続(デフォルトはlocalhost)になります。 - 「ソケットでつなぎたい」「TCP ではなくローカルソケットを強制したい」場合は、手動で
socket:を設定する必要があります。
3-3. Docker / devcontainer 環境でのメリット
- 以前は、ローカル環境で
rails newをするとそのマシンのソケットパスがconfig/database.ymlに書き込まれ、それをそのままコンテナに持ち込むと接続できない、というパターンが起こり得ました。 - 今後は、生成直後から
host:ベースなので:DB_HOSTをdbやmysqlなど Docker ネットワーク内のサービス名にするだけでよい- devcontainer 用の特別な分岐やガードも不要
結果として、「同じレポジトリを複数の環境で共有する」ユースケース(チーム開発、CI、コンテナなど)において、データベース設定の一貫性と移植性が向上します。
3-4. 互換性と注意点のまとめ
- 「ソケットを自動検出して欲しい」というニッチな期待は捨てられたため、そうしたワークフローに依存していた人は:
config/database.ymlを生成後に手で編集する- もしくは独自テンプレート/自前ジェネレータを用意する などが必要です。
- とはいえ、
DB_HOSTによる上書きが一般化しているため、多くのプロジェクトではほぼ問題にならない変更と考えられます。
- 参考情報 (あれば)
- 変更ファイル:
railties/lib/rails/generators/database.rb
→ ソケット検出ロジックの削除railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml.tt
→ MySQL 用テンプレートからsocket:関連と!devcontainer?ガードを削除railties/lib/rails/generators/rails/app/templates/config/databases/trilogy.yml.tt
→ Trilogy 用テンプレートも同様に整理guides/source/configuring.md
→ ガイドの記述を新しい挙動に合わせて更新railties/CHANGELOG.md
→ 挙動変更の記録を追加
この変更を前提としたアプリで、Docker/クラウド/ローカルをまたいで開発する場合は、DB_HOST / DB_USERNAME / DB_PASSWORD など環境変数による設定を標準にすることが、今回の PR の意図とも整合します。
#58809 Deprecate supports_datetime_with_precision?
マージ日: 2026/9/18 | 作成者: @kamipo
- 概要 (1-2文で)
supports_datetime_with_precision?が非推奨になり、実質常にtrueを返すだけのメソッドとして整理されました。これに伴い、datetime/time カラムのprecisionオプションをサポートしていない DB を考慮した分岐やテストコードが削除・簡略化されています。
- 変更内容の詳細
なぜ非推奨になったか
- 元々このメソッドは、「DB が datetime/time のサブ秒精度 (precision) をサポートしているか」を判定するために導入されました(MySQL 5.5 以前ではサポートされていなかったため)。
- しかし Rails から MySQL 5.5 サポートが削除され (#52817)、Active Record がサポートする主要 DB(MySQL 5.6+ / PostgreSQL / SQLite / SQL Server / Oracle enhanced)はすべて datetime/time の precision を扱えるようになっています。
- その結果、
supports_datetime_with_precision?は常にtrueを返すだけのメソッドになり、意味のあるガードとしては機能していませんでした。この PR でメソッドを「非推奨」とし、実質的な役割を終えさせています。
具体的なコード変更ポイント
1) 抽象アダプタでの非推奨化
ActiveRecord::ConnectionAdapters::AbstractAdapterでsupports_datetime_with_precision?が非推奨としてマークされます。- 既存アプリから呼び出すと、非推奨警告が出るような形になります(
ActiveSupport::Deprecation経由を想定)。
- 既存アプリから呼び出すと、非推奨警告が出るような形になります(
おおよそのイメージ:
def supports_datetime_with_precision?
ActiveSupport::Deprecation.warn(
"supports_datetime_with_precision? is deprecated and always returns true."
)
true
end(正確なメッセージや実装は PR 本体に準拠)
2) 各アダプタからのオーバーライド削除
- 以下のアダプタで定義されていた
supports_datetime_with_precision?のオーバーライド実装が削除されています:abstract_mysql_adapter.rbpostgresql_adapter.rbsqlite3_adapter.rb
- すべて「常に true を返すだけ」の実装になっていたため、抽象アダプタのデフォルト実装に統一されました。
3) スキーマ定義周り (schema_definitions.rb)
t.datetimeやt.time定義時にsupports_datetime_with_precision?を見て:precisionオプションを付与するかどうか判定していた部分が簡略化されています。- これにより、「precision をサポートしない DB のために precision オプションを削る」といった古い互換性コードが削除され、常に precision を考慮する前提になっています。
イメージ:
# 変更前(概念図)
if @delegate.supports_datetime_with_precision?
column_options[:precision] = options[:precision]
end
# 変更後(概念図)
column_options[:precision] = options[:precision]4) テストコードの整理
date_time_precision_test.rb/time_precision_test.rbで、- 「precision 非対応アダプタ」を想定した条件分岐やスキップ
supports_datetime_with_precision?をモックして挙動を切り替えるテスト が大幅に削除・書き換えられています。
- 代わりに、「全てのサポート DB で precision が有効である」ことを前提にしたテスト構造にシンプル化されています。
adapter_test.rbには、非推奨メソッドの挙動(=現状は true を返す)を担保するテストが追加されています。
5) Active Storage 既定マイグレーションの整理
activestorage/db/migrate/20170806125915_create_active_storage_tables.rbで、datetime カラムの定義周りが簡略化されています。- 元々は「precision がサポートされない DB でも動くように」条件付きで precision を変える/付けない、といった互換性処理がありましたが、これが不要になったため削除されています。
- 代わりに、Rails がサポートする DB を前提とした素直な datetime カラム定義になっています。
- 影響範囲・注意点
ライブラリ/アプリコード側での影響
- もしアプリケーションや独自アダプタ、ライブラリ内で
supports_datetime_with_precision?を直接呼んでいる場合:- 今後このメソッドは非推奨です。
- 返り値は常に
trueと見なせるため、その前提でコードを書き換え可能です。 - 将来的なバージョンで削除される可能性が高いので、「precision 非対応 DB の分岐コード」は整理しておく方が安全です。
- 独自の DB アダプタ実装で
supports_datetime_with_precision?をオーバーライドしていた場合:- 非推奨 API に依存していることになります。
- 対象 DB が本当に precision 非対応なら、そもそも Rails が公式に想定していないケースになるため、そのアダプタ側で別の互換層を考える必要があります。
- 一般的には、「Rails がサポート対象として想定していないほど古い DB バージョン」への対応は今後ますます厳しくなると考えた方がよいです。
マイグレーション・スキーマ定義
t.datetime :foo, precision: 6といった記述は、今後も引き続きそのままサポートされます。- これまでは「古い MySQL 等では precision が効かない」前提でいた人もいるかもしれませんが、Rails がサポートする範囲においては、precision は常に有効である前提に変わっています。
- 極端に古い DB を無理に使っている環境では、挙動がサポート外になる可能性があります(ただし、そのような環境はそもそも Rails のサポート対象外です)。
非推奨警告への対応
- テスト実行時や起動時に
supports_datetime_with_precision?の deprecation warning が見えたら、その呼び出し箇所を削除・書き換えてください。 - 「DB の capability を問い合わせるためのメソッド」としては、もはや意味を持たないため、単純に条件分岐ごと消せるケースがほとんどです。
- 参考情報 (あれば)
- 元 PR:
- #18886:
supports_datetime_with_precision?が導入された背景(MySQL 5.5/5.6 の互換性対応)。 - #52817: MySQL 5.5 サポート削除に関する PR。
- #18886:
- この PR (#58809) の主眼:
- 「もはや常に true な capability メソッド」を非推奨にしてコードベースを整理し、Rails がサポートする DB バージョンの現実に合わせたシンプルな実装に寄せること。
#58808 Read related PostgreSQL relation names with ::regclass
マージ日: 2026/9/17 | 作成者: @kamipo
- 概要 (1-2文で)
PostgreSQL 用アダプタで、関連テーブル名の取得にpg_classの JOIN を使わず::regclassを用いるように変更し、外部キー・継承テーブルの名前解決を簡潔かつ正確にしました。これにより、スキーマの異なる親テーブルやクオートが必要なテーブル名も正しくschema.rbなどにダンプされます。
- 変更内容の詳細
主な変更点
1) fetch_foreign_keys での名前解決の簡略化
これまで fetch_foreign_keys では、外部キーの参照先テーブル名を得るために pg_class と JOIN していましたが、実際には外部キー定義 (pg_constraint) がすでに参照先の OID (c.confrelid) を持っています。
PR では、この OID をそのまま ::regclass キャストを通してテーブル名に変換するようにしています。
概念的には以下のような変更です(擬似コード):
-- 変更前(イメージ)
SELECT t2.relname AS to_table
FROM pg_constraint c
JOIN pg_class t2 ON t2.oid = c.confrelid
...
-- 変更後(イメージ)
SELECT c.confrelid::regclass::text AS to_table
FROM pg_constraint c
...ポイント:
::regclassは PostgreSQL 独自のキャストで、OID から完全修飾テーブル名(必要に応じてスキーマ付き)を得られます。- これにより、わざわざ
pg_classを JOIN してrelnameを取ってくる必要がなくなります。 - 参照先テーブルが別スキーマにあっても、適切にスキーマ付きで取得されます。
2) fetch_table_options(継承テーブルの親取得)の修正
テーブル継承 (INHERITS) の親テーブル取得では、これまで parent.relname だけを使っていたため、以下のような問題がありました:
- 親テーブルが別スキーマにある場合:
INHERITS (parent)のようにスキーマ名が抜け落ちる - 大文字・記号などクオートが必要なテーブル名の場合: クオートされずに
INHERITS (WeirdName)のようにダンプされ、不正な SQL になる可能性
今回の PR では、親テーブルについても OID を ::regclass でキャストして取得することで、スキーマ名とクオートを含めた正しい表現を得るようにしています。
擬似コード:
-- 変更前(イメージ)
SELECT parent.relname
FROM pg_inherits i
JOIN pg_class parent ON parent.oid = i.inhparent
...
-- 変更後(イメージ)
SELECT i.inhparent::regclass::text
FROM pg_inherits i
...これにより、schema.rb などに出力される INHERITS 句が、
INHERITS (schema_name."WeirdName")のように、正確なテーブル識別子でダンプされます。
3) テストの追加
activerecord/test/cases/adapters/postgresql/schema_test.rb に 18 行のテストが追加され、以下のようなケースがカバーされています:
- 継承元が別スキーマにある場合の
INHERITSの出力 - クオートの必要なテーブル名を持つ継承親テーブルの出力 -(推測)外部キーの参照先テーブル名がスキーマ付きで正しくダンプされること
これにより、今回の変更が期待通りのダンプ結果を生むことを回帰テストで保障しています。
- 影響範囲・注意点
- 対象: PostgreSQL アダプタを利用している Rails アプリケーション(特に
schema.rb/structure.sqlの生成・スキーマダンプを行うケース)。 - 外部キー・継承テーブルを使っている場合に、ダンプされるテーブル名の文字列表現が変わる可能性があります:
- 以前はスキーマ無しで
INHERITS (parent)だったものが、INHERITS (public.parent)などスキーマ付きになる - クオートされていなかったテーブル名が
"CamelCaseTable"のようにクオートされる
- 以前はスキーマ無しで
- いずれも PostgreSQL の正しいテーブル名表現への修正であり、互換性を壊すというより、これまで「たまたま動いていた」または「間違ったダンプになっていた」ケースを是正する変更です。
pg_classJOIN の削減により、fetch_foreign_keys/fetch_table_optionsのクエリが簡潔になり、若干ながらクエリ負荷・パース負荷が下がる可能性があります。
運用上の注意:
- 既存の
schema.rbとの差分が増える可能性があるため、今回の Rails バージョンに上げた直後にdb:schema:dumpをすると、多くの差分が出ることがあります。- CI で
schema.rbをコミットしているプロジェクトは、想定内の差分か確認した上でコミットしてください。
- CI で
- 参考情報 (あれば)
- 該当 PR: https://github.com/rails/rails/pull/58808
- フォローアップ元 PR(コンテキスト): https://github.com/rails/rails/pull/58795
- PostgreSQL ドキュメント:
regclass型- https://www.postgresql.org/docs/current/datatype-oid.html
- テーブル OID を
::regclassにキャストすると、schema."TableName"形式の識別子文字列を安全に取得できるため、Rails のスキーマダンプとの相性が良いといえます。
#58806 Update CHANGELOGs for 8-1-stable backports
マージ日: 2026/9/17 | 作成者: @hmcguire-shopify
- 概要 (1–2文で)
このPRは、Rails 8.1 系の安定ブランチ(8-1-stable)にバックポートされた変更内容を、各コンポーネントの CHANGELOG にまとめて追記したものです。実際の機能追加やバグ修正はすでに別 PR で取り込まれており、本 PR は「8.1.3.1 以降に何が変わったか」を公式ドキュメントとして整理・反映する目的のメンテナンス変更です。
- 変更内容の詳細
※このPR自体はコードや挙動を変えず、「既に入っている修正・機能」の一覧をテキストとして追加しているだけです。そのため、以下は「どのあたりがまとめて記述されたか」「どういうタイプの変更が記録されているか」をコンポーネント別に整理しています。実際の個々の修正は、それぞれのバックポート PR を参照する必要があります。
共通的な変更の性質
- 8.1.3.1 以降に 8-1-stable にバックポートされた:
- バグ修正
- 小さめの機能追加 / 挙動改善
- パフォーマンス改善
- 非推奨の明示やドキュメント更新
- 各 CHANGELOG ファイルには、上記の内容が「どのバージョンに含まれるか」「どの PR / Issue に対応するか」といった形で列挙されています。
- 実際のコード差分は本PRには含まれておらず、
+584 / -0は純粋に CHANGELOG の追記のみです。
コンポーネント別の更新概要
2-1. Action Cable (actioncable/CHANGELOG.md +4)
- 変更はごく小規模(4行追加)で、8.1.3.1 以降に行われた Action Cable の細かい修正が記録されています。
- 想定される内容:
- 接続・切断時の挙動やログの改善
- 内部的なデフォルト設定やエラーメッセージの調整
などが CHANGELOG に反映された可能性が高いです。
2-2. Action Mailer (actionmailer/CHANGELOG.md +10)
- メール送信に関する不具合修正や細かな仕様改善が追記されています。
- 典型的には:
deliver_later/ Active Job 経由の送信挙動の明示- マルチパートメールやエンコーディングに関する修正
- URL ヘルパーとの連携(ホスト名・プロトコルなど)に関する改善
などが 8.1.x 向けにバックポートされ、それが CHANGELOG に列挙された形です。
2-3. Action Pack (actionpack/CHANGELOG.md +50)
- ルーティング、コントローラ、リクエスト/レスポンス処理など Web レイヤーの変更点が比較的多く記録されています。
- 主なタイプのエントリとして想定されるもの:
- ルーティングのバグ修正(constraints、format、
default_url_optionsまわり) redirect_to,renderの扱い改善(例外やヘッダとの相互作用)- CSRF 保護やパラメータフィルタリング関連の安定化
- HTTP/2, HTTP/3 対応や Rack とのインターフェイス改善の修正点の明文化
- ルーティングのバグ修正(constraints、format、
(例)ルーティングや URL 生成に関する変更がある場合、CHANGELOG には
「url_for が X のケースで誤った URL を生成していた問題を修正」
のような記述が追加されているはずです。
2-4. Action View (actionview/CHANGELOG.md +5)
- ビュー層での小さな修正や改善が追記されています。
- 想定されるもの:
form_with/form_forのオプション挙動の修正- 一部ヘルパー(path/url ヘルパーやタグヘルパー)のバグ修正
- セーフバッファ(HTML セーフティ)に関するエッジケースの対応
2-5. Active Job (activejob/CHANGELOG.md +24)
- ジョブキューに関する不具合修正や挙動明文化がそれなりのボリュームで追加されています。
- よくある内容の例:
- 冪等性・再実行時の例外処理やリトライ戦略の改善
- キューアダプタ(Sidekiq, DelayedJob など)との互換性修正
- シリアライズ/デシリアライズのエッジケース(Time、BigDecimal、ActiveRecord オブジェクトなど)
- これらが「8.1.3.1 以降の 8-1-stable に含まれる変更」としてリストされた形です。
2-6. Active Model (activemodel/CHANGELOG.md +15)
- バリデーションや属性 API まわりの改善が記されています。
- 例として想定されるもの:
- カスタムバリデータのエッジケース修正(
validateブロックの動作など) - 型付き属性 (
attribute :foo, :integer等) の変換・デフォルト値の扱い修正 - エラーメッセージの i18n キーや
errors.full_messagesの改善
- カスタムバリデータのエッジケース修正(
(サンプルイメージ)
class SignupForm
include ActiveModel::Model
attr_accessor :email
validates :email, presence: true, format: /@/
# このあたりのバリデーション・エラーオブジェクトの挙動改善が
# CHANGELOG に明示されている可能性があります。
end2-7. Active Record (activerecord/CHANGELOG.md +299)
- この PR の中で最も大きな差分であり、Active Record に関する多数のバックポート変更が CHANGELOG に集約されています。
- おおまかなテーマとして考えられるもの:
- クエリ生成のバグ修正(
joins,includes,left_outer_joins, サブクエリなど) - マイグレーションやスキーマダンプに関する修正(特定DBでの型定義、インデックスオプションなど)
- マルチ DB, レプリカ接続, コネクションプーリングの安定化
- enum / serialized attributes / type casting の修正
- パフォーマンス改善やN+1関連の修正
- クエリ生成のバグ修正(
- 8.1.3.1 → 次の 8.1.x にかけての「挙動の細かい修正」がかなり多く、開発者が「いつからこの修正が入っているか」を確認するための重要な情報源になります。
(サンプルイメージ)
# 例えば、特定条件下で誤った SQL が生成されていたケースの修正が
# CHANGELOG に記録されている可能性があります。
User.left_outer_joins(:posts).where(posts: { published: true })
# -> 以前は特定DBで不正なSQLになることがあったが修正 …といった類2-8. Active Support (activesupport/CHANGELOG.md +132)
- コア拡張、時間・日付、通知、キャッシュ、インスツルメンテーションなど多岐に渡る変更がまとめられています。
- 典型的には:
Time.zone,Date,Duration周りのエッジケース修正(タイムゾーン、DST 関連など)Hash#deep_symbolize_keys,Object#blank?などユーティリティメソッドの仕様調整- ActiveSupport::Notifications / Instrumentation の改善
- キャッシュ(
cache_key_with_versionなど)のバグフィックス
- こうした横断的な変更は、すべてのレイヤーに影響しうるため、CHANGELOG の内容確認が重要です。
2-9. Railties (railties/CHANGELOG.md +45)
- Rails 本体のフレームワーク構成や CLI(
railsコマンド)に関する変更がまとめられています。 - 想定される内容:
- ジェネレータの修正(新規アプリ / scaffold / plugin 生成時のテンプレート改善)
- Boot プロセス(
config/application.rb, initializers)の挙動修正 - Railtie / Engine のロード順・設定マージに関するバグ修正
- 新規アプリ作成時のデフォルト構成や、Rails をエンジンとして組み込んでいる場合などに関係してきます。
- 影響範囲・注意点
- この PR は「CHANGELOG の追加のみ」であり、実行時の挙動や API をこの PR 自体が変更することはありません。
影響範囲はドキュメント(CHANGELOG)に限定されます。 - ただし、CHANGELOG に列挙されている各修正は、すでに 8-1-stable ブランチのコードに反映済みのものです。そのため:
- 8.1.3.1 から 8-1-stable の最新(この PR 反映後のバージョン)へアップデートするプロジェクトは、
Active Record や Active Support を中心に多数の細かい挙動変更が入りうることに注意が必要です。 - 特に以下のような部分を多用しているアプリでは、CHANGELOG を精読した上でテストを十分に行うのが望ましいです。
- カスタムクエリ(複雑な
joins, サブクエリなど) - 時間/日付計算 (
Time.zone, 時差, DST) - カスタムバリデーション、シリアライズ属性、enum
- Active Job 経由の非同期処理
- カスタムクエリ(複雑な
- 8.1.3.1 から 8-1-stable の最新(この PR 反映後のバージョン)へアップデートするプロジェクトは、
- 移行時の実務的なポイント:
- まずは CHANGELOG を見て、自分のアプリで依存していそうな変更(例えば「特定DBでの挙動修正」「非同期ジョブのリトライ仕様」など)に印をつける。
- ステージング環境でフルテストを走らせるほか、その変更が影響しそうなユースケースを重点的に E2E テスト・手動テストする。
- 参考情報 (あれば)
- 対象ブランチ:
8-1-stable
→ 8.1.x 系の安定版向けバックポートの集約ブランチ - この PR 番号: #58806 – 「Update CHANGELOGs for 8-1-stable backports」
→ 実装の変更ではなく、「これまでのバックポートを CHANGELOG に反映するためのメタ PR」 - 実際の挙動に関する詳細は:
action*/activerecord/activesupport/railties各 CHANGELOG 内のエントリ- 各エントリが参照している個別 PR / Issue
を参照することで、どの変更がどの理由でバックポートされたかを追うことができます。
#58799 Fix tuple where key corrupting later association conditions
マージ日: 2026/9/17 | 作成者: @e-akashsaini
- 概要 (1-2文で)
ActiveRecord のwhereにおける「タプル(複合)キー」と「関連(association)キー」を同時に使ったとき、キーの順序によってクエリが壊れる回帰バグを修正する PR です。ブロック内でメソッド引数attributesを上書きしていたことにより、無限再帰防止用のガードロジックに誤った値が渡され、関連の展開が壊れていました。
- 変更内容の詳細
不具合の内容
対象メソッドは ActiveRecord::PredicateBuilder#expand_from_hash です。
このメソッドは where(hash) の各キーを解析して Arel の述語に展開しますが、その中で:
複合キー(タプル)な
where:rubyModel.where([:col1, :col2] => [[1, 2]])関連キーな
where:rubyModel.where(order: some_order)
を同一の where 呼び出しに混在させた場合に、処理順序によって壊れる、という問題がありました。
問題のコアは、#58446 で以下のような変更が行われたことです(イメージ):
# 以前(問題なし)
expand_from_hash(key.zip(ids_set).to_h)
# 変更後(問題のあるコード)
attributes = key.zip(ids_set).to_h
expand_from_hash(convert_dot_notation_to_hash(attributes))Ruby では「ブロックは外側スコープと同じローカル変数を共有する」ため、ブロック引数名と同じ名前のローカル変数に代入すると、そのメソッド引数自体が上書きされます。
expand_from_hash 内では、attributes は「再帰防止用のガード値」としても使われており、
- 関連キー
order: orderを展開するとき、 - 関連の外部キー(例:
[:shop_id, :order_id])から{ shop_id: 1, order_id: 2 }といったハッシュを作り、 - 「今処理している
attributesと同じか?」を比較して、「全く同じ場合は無限再帰になるので展開しない」というガードをしている
という仕組みになっています。
ところが、タプルキー側の処理で attributes = key.zip(ids_set).to_h としてしまったため、
- タプルキーを処理したタイミングで
attributesが「そのタプル由来のハッシュ」に上書きされる - その後に関連キーを処理するとき、この「上書き済みの
attributes」と比較する - 値・キー構造が一致してしまうと、「あ、同じだ。無限再帰だ」と誤判定し、関連を正しく外部キーへ展開せず、「関連名そのものをカラム名として使う」ような壊れた述語を生成してしまう
というバグが発生していました。
PR で示されている再現例:
order = Cpk::Order.create!(id: [1, 2])
book = order.books.create!(id: [3, 4])
# 1. タプル → 2. 関連 の順
Cpk::Book.where([:shop_id, :order_id] => [[1, 2]], order: order)
# 生成SQLの一部:
# ... AND "cpk_books"."order" IN (1, 2)
# => ActiveRecord::StatementInvalid (SQLite3::SQLException: no such column: cpk_books.order)
# 1. 関連 → 2. タプル の順(順序を変えると成功)
Cpk::Book.where(order: order, [:shop_id, :order_id] => [[1, 2]])
# ... AND shop_id = 1 AND order_id = 2 # 正しいここで cpk_books.order のようなカラムは存在しないため、SQL エラーになります。本来は order_id(あるいは複合外部キー)に展開されるべきところが、「関連名 order をカラム名と誤認している」状態です。
なお、これは
- タプルキーが先
- かつ、そのタプルが示すハッシュと、関連の外部キーから組み立てたハッシュが「まったく同じ構造・値」になる
という条件で発火するため、「キーの順序と値の一致」に依存する、かなりわかりづらいバグです。
修正内容
修正は非常に小さく、「メソッド引数 attributes を上書きしないように戻す」ものです。
attributes = key.zip(set).to_hのような「引数への代入」をやめる- 代入せず、
expand_from_hash(convert_dot_notation_to_hash(key.zip(set).to_h))のように式をインライン化して呼ぶ
という方向に戻しています。
これにより:
- 引数
attributesは、再帰ガード用として保持され続ける - タプルキー処理がガード値を壊さない
- その後に処理される関連キーも、正しく「元の
attributes」と比較される
ようになり、誤判定が解消されます。
テスト面では:
activerecord/test/cases/relation/where_test.rbにテストを追加- タプルキーと関連キーを「両方の順序(タプル→関連 / 関連→タプル)」で指定したケースをカバー
- 順序に依存せず、どちらも正しくクエリが生成されることを確認
- 既存の複合主キー用モデル
Cpk::Order/Cpk::Bookを利用(新モデル・新スキーマ追加なし)
- 影響範囲・注意点
影響を受けるバージョン・ブランチ
説明によると、この回帰は #58446 由来で、以下のブランチにのみ存在します:
main8-1-stable8-0-stable
一方で:
7-1-stable7-2-stable
はまだ「元の一行版(インライン呼び出し)」のままで、この回帰は入っていません。
さらに、この回帰は「まだどのリリースにも入っていない」と明記されています。つまり、現時点のリリース版 Rails(少なくとも 7.x / 公開済み 8.x)ではこのバグは発生しない想定です(8.1 のどのタグまでが出ているか次第ですが、説明上は「未リリースのコード上のバグ」という扱い)。
条件付きで影響を受けるコード
このバグに実際に引っかかるのは、以下の条件が揃うケースです:
whereにおいて「タプル(複合)キーの条件」を使っている:rubyModel.where([:col1, :col2] => [[1, 2]])同じ
where呼び出しの中で「関連(association)キー」を使っている:rubyModel.where(order: order, [:shop_id, :order_id] => [[1, 2]])かつ、
- モデルが複合主キー、または
belongs_to :x, foreign_key: [:a, :b]のような「複合外部キー」を定義している
かつ、
- タプルキーによるハッシュ
{ a: 1, b: 2 }と、 - 関連から導出される外部キーのハッシュ
{ a: 1, b: 2 }
が「全く同じキーと値」で一致する
- タプルキーによるハッシュ
かつ、
whereのハッシュ内で「タプルキーが関連キーより前に書かれている」
このように条件がやや特殊なので、すぐに多くのアプリが壊れる類いの不具合ではありませんが、複合キーを本格的に使っているアプリでは十分にあり得るパターンです。
実務上の注意ポイント
main/8-0-stable/8-1-stableを追いかけながら開発している場合- すでにこの回帰が入っている期間のコミットを使っていると、上記の条件で不定期な SQL エラーが出る可能性があります
- この PR のパッチを取り込む(もしくは、回帰が入る前のコミットまで戻す)ことで解消します
- 現行リリース版 8.1 系を使っている場合
- 説明どおりであれば、まだこの回帰はリリースに入っていない想定なので、特別な対応は不要なはずです
- ただし、もし「タプル + 複合外部キーの関連」という構成で、キー順によって
no such column: <table>.<association_name>のようなエラーが出るなら、この PR と同種の問題が入っていないかバージョン・コミットを確認する価値があります
- 参考情報 (あれば)
- 問題の発端となった PR: #58446
- 内容: タプルキー展開時に「ドット記法のキーをハッシュに変換する」処理を追加した際、
attributes引数を書き換える形にしてしまった
- 内容: タプルキー展開時に「ドット記法のキーをハッシュに変換する」処理を追加した際、
- この PR による修正方針:
- 再帰ガード自体のロジックには手を触れず、「ガードに渡す
attributesが途中で壊されないようにする」ことにのみフォーカス - より安全な形(引数を上書きせずにインラインで式を渡す)で #58446 の仕様追加を保持
- 再帰ガード自体のロジックには手を触れず、「ガードに渡す
- テスト状況:
- SQLite3 + Ruby 4.0.6 で ActiveRecord SQLite3 スイートを実行: 9644 runs, 0 failures, 0 errors
- MySQL/PostgreSQL/Trilogy は CI 任せ(ロジックレイヤの変更であり、アダプタ依存ではないため)
- メンテナ観点のメモ(PR本文より要約):
- 回帰は
8-1-stableにも存在し、8.1 は現行リリース系列なので、必要なら backport PR を作る用意あり - #58605 も同じ 3 行付近を編集しており、merge 順によっては軽い rebase が必要になる見込み
- 回帰は
#58766 [Ractor] Compute serialize_cast_value_compatible? on the main Ractor
マージ日: 2026/9/17 | 作成者: @yaroslav-shopify
- 概要 (1-2文で)
Rails の ActiveRecord 型システムにおいて、serialize_cast_value_compatible?のメモ化を「メイン Ractor でのみ」実行するように変更し、Ractor 使用時に発生していたIsolationError(@serialize_cast_value_compatible関連)を解消する修正です。Ractor 環境でも型のシリアライズ・キャスト互換判定が安全かつ一貫して行えるようになります。
- 変更内容の詳細
背景
- 対象は
ActiveModel::Type::SerializeCastValue(およびそれを使うActiveRecord::Type系クラス、例:ActiveRecord::Type::DateTime)。 - このモジュールには
serialize_cast_value_compatible?というメソッドがあり、その結果をクラスインスタンス変数@serialize_cast_value_compatibleにメモ化しています。 - クラスインスタンス変数への書き込みは Ractor 的には「メイン Ractor で行う」必要があり、別 Ractor から書こうとすると
Ractor::IsolationErrorが発生します。
従来の前提:
- 通常は
#initializeの中で eager にserialize_cast_value_compatible?を計算することで、「最初の書き込み」はメイン Ractor で済む、という前提になっていました。 - ところが
ActiveRecord::Type::TypeMapは型を「遅延生成」するため、あるカラムに初めてアクセスするのがサブ Ractor 側だった場合、#initializeがそのサブ Ractor 内で動き、そこでクラスインスタンス変数への初回書き込みが発生 →IsolationErrorで落ちる、という問題が発生していました。 - 別の到達経路である
.serializeメソッド側はrescue nilでIsolationErrorを握りつぶしており、そこで初回書き込みに失敗しても、その後プロセス全体としては壊れた状態のまま動き続ける、という微妙な状態にもなり得ました。
何をしたか
serialize_cast_value_compatible? の「初回書き込み」だけを、必ずメイン Ractor 経由で行うように変更しました。
コード的には概ね以下のようなイメージです(実際のコードは Rails 内部実装ですが、要旨のみ):
def serialize_cast_value_compatible?
# すでにメモ済みなら、そのまま読む(読み取りは Ractor セーフ)
return @serialize_cast_value_compatible if defined?(@serialize_cast_value_compatible)
# 初回計算・書き込みだけメイン Ractor に代理実行させる
ActiveSupport::Ractors.on_main(self) do |type|
# ここは必ずメイン Ractor 上で実行される
unless defined?(type.instance_variable_get(:@serialize_cast_value_compatible))
compatible = compute_serialize_cast_value_compatible
type.instance_variable_set(:@serialize_cast_value_compatible, compatible)
end
type.instance_variable_get(:@serialize_cast_value_compatible)
end
endポイント:
defined?(@serialize_cast_value_compatible)を「高速パス」として残しているfalseも正当なメモ化結果になり得るため、@ivar ||= ...のようなパターンにはできません(falseだと再評価されてしまう)。- そのため、「定義済みかどうか」だけで分岐している。
- 「最初の書き込み」だけを
ActiveSupport::Ractors.on_mainでメイン Ractor にプロキシしている。- 2 回目以降は単純な読み取りのみ(
defined?が true なのでメイン Ractor 呼び出しに行かない)。
- 2 回目以降は単純な読み取りのみ(
ActiveSupport::Ractors.on_mainは「ブロックをメイン Ractor 上で実行して結果を返す」ためのユーティリティで、Ractor 対応のために導入されているサポート機能です。
テスト
- 3つのテストが追加されており、いずれもこの変更がなければ失敗するケースとのことです。
- 想定される内容:
- Ractor 内で初めて特定の型(例: DateTime 型)を触ったときに例外が出ないこと。
- メモ化値が Ractor を跨いでも一貫して利用できること。
- 以前の
.serialize周りのrescue nil依存の挙動ではなく、正しくメイン Ractor を経由していること。
- 想定される内容:
- 影響範囲・注意点
影響範囲
- 対象:
ActiveModel::Type::SerializeCastValueを include する型クラス(主に ActiveRecord の型クラス群)。- 特に
ActiveRecord::Type::DateTimeなど、serialize_cast_value_compatible?を使う型。
- シナリオ:
- Ractor を使うアプリケーション(Ractor ベースの並行処理、並列テスト実行、マルチ Ractor Web サーバなど)。
- 初めて特定の型インスタンスが生成されるのが、メインではない Ractor 内であるようなケース。
期待される改善
- 以前は:
- Ractor 内で当該クラスを初めて使うと
Ractor::IsolationErrorが#initializeから発生していた可能性がある。 - 場合によっては
.serialize内で握りつぶされ、不安定なメモ化状態が残ることもありえた。
- Ractor 内で当該クラスを初めて使うと
- 今回から:
- 初回のメモ化が常にメイン Ractor 上で安全に行われるため、Ractor 内から使っても
IsolationErrorが起きない。 .serializeでの曖昧なrescue nilに依存することなく、一貫した安全な挙動になる。
- 初回のメモ化が常にメイン Ractor 上で安全に行われるため、Ractor 内から使っても
パフォーマンス・互換性
- パフォーマンス:
defined?(@serialize_cast_value_compatible)でのガードにより、多くのケースでは Ractor 介入なしの単純な読み取りになるため、通常リクエストのパフォーマンスへの影響はごく小さいと考えられます。- メイン Ractor 経由のコールは「1 型あたり 1 回」の初期計算時のみ。
- 既存コードへの互換性:
- 公開 API のシグネチャ変更はなく、挙動も「Ractor が絡まない環境」ではほぼ同一です。
- Ractor を使っていない Rails アプリでは、この変更による挙動の違いは実質的にありません。
- 参考情報 (あれば)
- 関連コンセプト:
- Ractor とオブジェクト分離 (
Ractor::IsolationError)- クラスインスタンス変数など、共有的に見えるオブジェクトへの「書き込み」は Ractor の隔離ルールに抵触しやすい。
- メモ化の落とし穴:
||=パターンは「falseをキャッシュしたい」ケースには不向きであり、defined?などによる「定義済みかどうか」のチェックが必要。
- Ractor とオブジェクト分離 (
- プロキシ機構:
ActiveSupport::Ractors.on_mainは、Ractor 化対応で「グローバルな変更を一元的にメイン Ractor で行う」ために設計された補助 API で、今後も同種の問題(クラス変数・グローバル状態の書き込みなど)に対して使われていく可能性があります。
#58795 Read PostgreSQL indexes and constraints from the table a name resolves to
マージ日: 2026/9/17 | 作成者: @kamipo
- 概要 (1–2文で)
PostgreSQL アダプタが、スキーマを省略したテーブル名からインデックスや制約を取得する際の挙動を修正し、「名前解決された 1 つのテーブル」だけを対象にするように統一した PR です。::regclassと同等の名前解決ロジックを共通化し、複数スキーマに同名テーブルがある場合でも誤って両方のインデックス・制約を取ってきてしまう問題を防ぎます。
- 変更内容の詳細
何が問題だったか
PostgreSQL アダプタの以下のメソッドは、「スキーマを省略したテーブル名」を渡されたときに、search_path 上のすべてのスキーマに同名テーブルがあると、両方のテーブルの定義を混ぜて返していました。
fetch_indexesfetch_foreign_keysfetch_check_constraintsfetch_unique_constraintsfetch_exclusion_constraints
つまり、例えば:
-- schema_a.users, schema_b.users が両方存在
SET search_path = schema_a, schema_b;
# Rails 側
connection.indexes("users")のようなケースで、本来は search_path に従って 1 つに決まるはずのテーブル (PostgreSQL 的には users::regclass) からだけインデックスを取るべきところ、schema_a.users と schema_b.users の両方のインデックス情報を混在して返すことがありました。
一方で、同じくテーブルメタ情報を読む:
fetch_primary_keysfetch_table_options
は、すでに DISTINCT ON (relname) を使ったサブクエリを通じて、::regclass と同様の「名前解決済みテーブル」を 1 つに絞ってから情報を取得する実装になっており、挙動に一貫性がありませんでした。
何をどう直したか
- 名前解決ロジックを共通化 (
resolved_tables)
PostgreSQL の ::regclass が行うテーブル名の解決 (search_path を見てどのスキーマのテーブルかを一意に決める) を模倣するクエリ部分を resolved_tables という共通の CTE(共通テーブル式)として切り出しました。
- これまで
fetch_primary_keys/fetch_table_optionsにだけ書かれていたDISTINCT ON (relname)ベースのクエリロジックを、WITH tables AS (...)という CTE に再構成し、共通利用できるようにしたイメージです。 - CTE には「解決されたテーブル (schema + relname) のセット」が含まれ、他のクエリは直接
pg_class等をフィルタリングするのではなく、このtablesに JOIN して対象テーブルを限定します。
- すべての reader が CTE に JOIN するように変更
次の reader メソッドが、relname や nspname を直接条件に持つのではなく、resolved_tables が生成する tables CTE を参照するように修正されています。
fetch_indexesfetch_foreign_keysfetch_check_constraintsfetch_unique_constraintsfetch_exclusion_constraints- 既存の
fetch_primary_keys - 既存の
fetch_table_options
これにより、「一度名前解決されたテーブル集合」だけを対象に、インデックス・制約・主キー・テーブルオプションなどすべてを取得するよう挙動が統一されました。
- テストの追加・更新
activerecord/test/cases/adapters/postgresql/schema_test.rb に 48 行分のテストが追加され、主に以下のようなシナリオがカバーされていると考えられます(PR 文脈から推測):
search_path上の複数スキーマに同名テーブルがある場合- その状態で
indexes,foreign_keys,check_constraints,unique_constraints,exclusion_constraintsを呼び出した際に、名前解決された 1 つのテーブルに対応する結果だけが返ることを確認 - 既に正しく動いていた
primary_keys,table_optionsとの挙動の一貫性も検証
- CHANGELOG の更新
activerecord/CHANGELOG.md に、この挙動変更が記載され、Rails の利用者が「PostgreSQL アダプタのテーブルメタ情報取得の挙動が修正された」ことを認識できるようになっています。
- 影響範囲・注意点
影響を受けるのはどんな環境か
- DB: PostgreSQL
- 条件:
schemasを分けて同名テーブルを定義している (例:public.usersとtenant1.users)search_pathを使ってスキーマを切り替えている- Rails 側でスキーマを明示せず、
"users"のような 非修飾テーブル名 をメタ情報取得に使っている (connection.indexes,foreign_keys,check_constraintsなど)
このような環境で、以前は「複数スキーマの同名テーブルの定義がまとめて返ってくる」状態を前提にしていたコードがある場合、返るインデックス・制約の集合が変わるため注意が必要です。
ただし、PostgreSQL 本来の ::regclass 挙動に合わせた修正であるため、一般的には:
- 想定していなかった「別スキーマのインデックス/制約」を誤って見てしまう
- それを元にマイグレーション生成や検査を行ってしまう
といったバグを 改善する方向 の変更です。
Rails アプリ側で意識すべき点
もし以下のようなコードを書いており、両スキーマ分の情報が返ることを期待していた場合:
ruby# 例: 以前は a.users, b.users 両方の index が混ざって返ることに依存しているようなコード connection.indexes("users")この PR 以降は、search_path に従って 1 つだけに解決されるようになります。その場合は、明示的にスキーマを指定してください:
rubyconnection.indexes("schema_a.users") connection.indexes("schema_b.users")マルチテナント構成で search_path に複数テナントスキーマを並べ、それを前提にメタ情報を集約していた場合は、設計の見直しが必要です。「1 つの非修飾名は 1 つのテーブルにしか解決されない」という PostgreSQL 本来のモデルに合わせたほうが安全です。
- 参考情報 (あれば)
PostgreSQL の
regclass型と名前解決:SELECT 'users'::regclass;は、search_pathに従って「最初に見つかったusersテーブル」を一意に解決します。- Rails の今回の変更は、この
regclass相当の解決結果に基づいてメタ情報取得を行うようにした、という位置づけです。
関連しそうな Rails ガイド/コード:
- ActiveRecord PostgreSQL アダプタ:
activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb - マルチスキーマ・マルチテナント構成時は、
schema_search_pathをどう設定するか、およびテーブル名にスキーマをどこまで明示するかを再確認するとよいです。
- ActiveRecord PostgreSQL アダプタ:
#58791 Replace the mysql5 fallback in ActiveRecord.database_cli with mariadb
マージ日: 2026/9/16 | 作成者: @kamipo
- 概要 (1-2文で)
ActiveRecord のdatabase_cli(rails dbconsoleなどから使われる DB クライアント実行コマンド)の MySQL 系クライアント検出ロジックで、古いmysql5バイナリのフォールバックを廃止し、代わりにmariadbをフォールバック候補として扱うように変更した PR です。これにより、近年の MariaDB 環境でも、rails dbconsoleがより確実に動作するようになります。
- 変更内容の詳細
背景
- これまで ActiveRecord は、MySQL クライアントコマンドを探す際に、
mysql- 見つからなければ
mysql5(MacPorts の古いmysql5port 用)
のような順でフォールバックしていました。
mysql5バイナリを提供していた MacPorts のmysql5port はすでに廃止されており、対応していた MySQL バージョンもすべて EOL。- 一方で MariaDB は、10.4.6 以降自前のクライアントバイナリを
mariadbという名前で提供しており、11.0 ではmysqlへのシンボリックリンクが非推奨になっています。 - そのため、現代の環境では
mysql5をフォールバックに持つ意味は薄く、代わりにmariadbをフォールバック対象にした方が実用的です。
コード上の主な変更点
1. ActiveRecord.database_cli のフォールバック変更
activerecord/lib/active_record.rb 内で、MySQL/MariaDB 用の CLI コマンドを決める処理が次のように変わります(イメージ):
# 変更前(概念的なイメージ)
def self.database_cli
case adapter_name
when "Mysql2"
detect_cli("mysql", "mysql5")
end
end
# 変更後(概念的なイメージ)
def self.database_cli
case adapter_name
when "Mysql2"
detect_cli("mysql", "mariadb")
end
end実際の実装は多少抽象化されていますが、「mysql がなければ mysql5」という古いフォールバックが、「mysql がなければ mariadb」に置き換わっています。
このロジックは、少なくとも以下のアダプターで利用されています:
mysql2アダプターtrilogyアダプター(MySQL プロトコル互換の別実装)
2. テストコードの更新
以下 2 つのテストファイルで、期待される CLI 名の検証が mysql5 → mariadb に変更されています。
activerecord/test/cases/adapters/mysql2/dbconsole_test.rbactiverecord/test/cases/adapters/trilogy/dbconsole_test.rb
具体的には:
- 「
mysqlが見つからない場合に使われるコマンドは何か」を確認するテストの期待値がmysql5からmariadbに変更。 - MySQL 系 CLI 検出順序の前提が、
["mysql", "mariadb"]になったことがテストでも保証されるようになります。
3. CHANGELOG の更新
activerecord/CHANGELOG.md に、本変更に関するエントリが追加されています。
内容としては、「ActiveRecord.database_cli の MySQL CLI フォールバックに mariadb を利用するようにした」といった趣旨の記述です。
- 影響範囲・注意点
影響範囲
rails dbconsole(またはbin/rails dbconsole) を MySQL/MariaDB と組み合わせて使うプロジェクト- ActiveRecord の
ActiveRecord::Base.connection_db_config.configuration_hashなどを元に、自前でActiveRecord.database_cliを利用しているスクリプトやツール(あれば)
想定される挙動の変化
MariaDB-only 環境で、これまでは:
mysqlコマンド(シンボリックリンク)が無効・削除済みmysql5も存在しない
→rails dbconsoleが動かない可能性があった。
変更後は:
mysqlが見つからない場合でもmariadbを探す- MariaDB 10.4.6 以降で標準的なクライアントバイナリ
mariadbが見つかれば、それでdbconsoleを起動可能
→ 近年の MariaDB 環境で rails dbconsole が成功しやすくなります。
注意点
古い MacPorts/MySQL5 環境への考慮はなくなる
- まだ MacPorts の非常に古い
mysql5port を使っていて、mysqlではなくmysql5コマンドだけが存在するようなレガシー環境では、rails dbconsoleがクライアントを見つけられなくなる可能性があります。 - その場合は:
mysqlという名前でシンボリックリンクを張る- ActiveRecord 側の設定やラッパースクリプトで明示的に CLI を指定する
などの対処が必要です。
- まだ MacPorts の非常に古い
MariaDB で
mariadbすら提供していない独自パッケージング- ディストリビューションやパッケージが、
mysql/mariadbのどちらの名前も提供していない場合、本変更では対応できません。 - その場合は、やはり何らかのシンボリックリンクまたはラッパーを用意する必要があります。
- ディストリビューションやパッケージが、
Trilogy など MySQL 互換アダプターも同じ影響
- Trilogy アダプターを利用していても、CLI 検出ロジックは同様に
mysql→mariadbの順になります。 - つまり、Trilogy + MariaDB な環境でも
rails dbconsoleの使い勝手が改善します。
- Trilogy アダプターを利用していても、CLI 検出ロジックは同様に
- 参考情報 (あれば)
PR 本文のポイント:
mysql5は MacPorts の古いmysql5port 専用バイナリ名であり、その port 自体がすでに終了している。- それが指しうる MySQL バージョンもすべて EOL。
- MariaDB は 10.4.6 以降
mariadbというクライアント名を採用しており、11.0 ではmysqlシンボリックリンクを非推奨としている。 - よって現時点で有用なのは
mysql5ではなくmariadbをフォールバックにすること。
関連しうるファイル:
activerecord/lib/active_record.rb
…ActiveRecord.database_cli(またはそれに相当するメソッド)の実装activerecord/test/cases/adapters/mysql2/dbconsole_test.rbactiverecord/test/cases/adapters/trilogy/dbconsole_test.rb
… MySQL 系 CLI 検出ロジックのテストactiverecord/CHANGELOG.md
… 変更履歴の確認用
#58790 Consolidate common methods of Mysql2Adapter and TrilogyAdapter
マージ日: 2026/9/16 | 作成者: @kamipo
- 概要 (1–2文で)
Mysql2AdapterとTrilogyAdapterの共通処理がAbstractMysqlAdapterに集約され、重複コードが削減されました。あわせてtext_type?とerror_numberの可視性が、実際の利用状況に合わせて整理されています。
- 変更内容の詳細
2-1. 共通ロジックの AbstractMysqlAdapter への集約
変更差分から読み取れる主なポイントは次の通りです。
Mysql2AdapterとTrilogyAdapterに重複していたメソッドが、AbstractMysqlAdapterに移されました。- 結果として、
mysql2_adapter.rbから約 58 行削除trilogy_adapter.rbから約 50 行削除- 共通実装は
abstract_mysql_adapter.rbに +55 行として追加
という構造になっています。
対象になっているのは、MySQL 系アダプタに共通な低レベルの処理(例: 型判定やエラー番号取得など)で、今後 MySQL 系アダプタを拡張・修正する際の保守性が上がる変更です。
text_type? の取り扱い変更
- もともと
text_type?はプライベートメソッドで、呼び出し側ではsendを使ってアクセスしていました。 - この PR では:
text_type?を「publicだが:nodoc:(ドキュメント非掲載)」とすることで、- 呼び出し側から
sendを使わずに直接呼び出せるようにしています。
text_type? は、MySQL のカラム型が TEXT 系かどうかを判定するためのヘルパーで、スキーマ定義まわりや型マッピングで利用されています。
例 (イメージ):
# 抽象的なイメージ。実際のコードはバージョンにより異なります。
def text_type?(sql_type)
# sql_type が "text", "mediumtext", "longtext" などかどうかを判定
endsend を介さずに直接呼び出すことで、
- 呼び出しコードが簡潔になる
- リフレクション利用が減り、将来的なリファクタリングや可読性にプラス
といった効果があります。
error_number の取り扱い変更
error_numberはエラーオブジェクトから MySQL のエラー番号を取り出すヘルパーです。- この PR では、「唯一の呼び出し元がプライベートな利用に限られている」ことに合わせて、
error_number自体をprivateに変更しています。
これも AbstractMysqlAdapter 側に寄せられているため、Mysql2Adapter と TrilogyAdapter は、この共通メソッドを通じてエラー番号の取り扱いを行うようになります。
2-2. mysql/schema_definitions.rb の細かな修正
activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb にも 1 行レベルの変更が入っています(+1/-1)。
- 文脈的には、
Mysql2Adapter/TrilogyAdapterから移動した共通処理 (text_type?等) を参照するように修正された、あるいはメソッドの呼び出し方法・場所が変わった、という程度の微調整です。 - スキーマ定義 (例:
t.textなど) の動き自体は変わらない想定です。
- 影響範囲・注意点
3-1. 一般的な Rails アプリケーションへの影響
- 通常のアプリケーションコードにはほぼ影響はありません。
Mysql2Adapter/TrilogyAdapterを標準的に利用しているだけであれば、この変更による挙動変更は基本的にない想定です(リファクタリング+可視性整理が主眼)。
3-2. 内部 API を使っている場合の注意
次のようなコードを書いている場合は影響する可能性があります。
アダプタの内部メソッドを直接叩いている場合
例:
rubyadapter = ActiveRecord::Base.connection # 旧: send を使って private メソッドを叩いていたケース adapter.send(:text_type?, "longtext") # 旧: error_number を直接見ていたケース(そもそも非推奨に近い) adapter.send(:error_number, some_error)この PR により:
text_type?public :nodoc:となったので、sendを使わずにadapter.text_type?(...)と呼べるようになっている可能性が高いです。
ただし「内部 API」扱いなので、将来の互換性保証はありません。
error_numberprivateに揃えられているため、send(:error_number, ...)のような呼び出しは、将来の変更で簡単に壊れる前提です。- もともと内部用のユーティリティなので、アプリケーションや gem から直接使うべきではありません。
カスタムアダプタや monkey patch で
Mysql2Adapter/TrilogyAdapterを拡張している場合- 以前は各アダプタに定義されていたメソッドが、
AbstractMysqlAdapterに移動しています。 - そのため、
- メソッドの
super呼び出し先 - モジュールの再オープン (
class Mysql2Adapter; ... end) の対象 が変わっている可能性があります。
- メソッドの
自前でアダプタを拡張している場合は、
abstract_mysql_adapter.rbに移されたロジックを前提とした構成にリファクタリングするのが望ましいです。- 以前は各アダプタに定義されていたメソッドが、
- 参考情報 (あれば)
PR: Consolidate common methods of
Mysql2AdapterandTrilogyAdapter(#58790)- 目的:
- MySQL 系アダプタ (
Mysql2Adapter,TrilogyAdapter) 間の重複実装をAbstractMysqlAdapterにまとめて保守性を改善。 - メソッドの可視性を「実際の利用形態」に揃えて整理 (
text_type?を public :nodoc:、error_numberを private)。
- MySQL 系アダプタ (
- 実質的にはリファクタリングであり、API の表面的な挙動や SQL の発行には影響しない想定。
- 目的:
関連ファイル:
activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rbactiverecord/lib/active_record/connection_adapters/mysql2_adapter.rbactiverecord/lib/active_record/connection_adapters/trilogy_adapter.rbactiverecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb
MySQL 系アダプタを直接いじるような高度な拡張をしていない限り、アップデート時の特別な対応は不要ですが、内部 API に依存している場合はこの機会に依存を減らしておくと安全です。
#58789 Don't reference connection in on_main block
マージ日: 2026/9/16 | 作成者: @hmcguire-shopify
- 概要 (1-2文で)
ActiveRecord::ModelSchema内でon_mainブロックの中から DB コネクションオブジェクトを直接参照しないようにリファクタリングし、Ractor 間でブロックを共有する際に発生していたエラーを回避する変更です。非メイン Ractor からの呼び出し時にも安全にon_mainを使えるようにするためのスレッド/Ractor 対応の改善です。
- 変更内容の詳細
背景: on_main と Ractor 共有可能性
on_mainは「メイン Ractor 上で処理を実行する」ためのヘルパで、
非メイン Ractor から呼び出された場合、ブロックをメイン Ractor に渡して実行します。- Ruby の Ractor は「shareable(共有可能)」なオブジェクトしか他 Ractor に渡せないため、
on_mainは 渡されるブロックを shareable にしようとする前提で実装されています。 - ところが、ブロックの中で ActiveRecord の connection オブジェクト(内部に多くの非共有オブジェクトを持つ)をキャプチャしていると、
そのブロック自体が shareable でなくなり、Ractor::IsolationErrorなどが発生する可能性があります。
この PR のポイント
- 問題:
on_mainブロックの中で ActiveRecord のconnectionを参照していたため、
ブロックが shareable でなくなっていた。 - 対応:
on_mainの外側でconnectionを使って必要な値を取得する。on_mainブロックには「取得済みの結果」だけを渡すように変更。
- これにより、ブロックが「単なる値(shareable なオブジェクト)のみを扱う」構造になり、
Ractor 間でのブロック共有時にエラーになりにくくなります。
疑似コードレベルのイメージ差分(実際のコードから簡略化):
# Before
on_main do
connection = ActiveRecord::Base.connection
schema_cache = connection.schema_cache
do_something_with(schema_cache)
end
# After
connection = ActiveRecord::Base.connection
schema_cache = connection.schema_cache
on_main do
# ここでは connection を触らず、外で準備した値のみ使う
do_something_with(schema_cache)
end実際の差分は activerecord/lib/active_record/model_schema.rb 内の
スキーマ関連メソッド(おそらくカラム情報やテーブル情報を扱う部分)で、
上記のような「connection をブロック外で評価 → ブロックには結果だけ渡す」パターンに変更されています。
- 影響範囲・注意点
- 対象:
ActiveRecord::ModelSchemaが関与する処理(テーブル/カラム情報の取得など)で、on_mainを経由して実行され、- かつ 非メイン Ractor から呼び出されるケース。
- 期待される効果:
- 非メイン Ractor 内で ActiveRecord モデルのスキーマ情報にアクセスしても、
on_main内のブロックが shareable でないことによる Ractor エラーが出にくくなります。 - 将来的な Ractor 対応(マルチ Ractor 実行)を考えた際の安定性向上。
- 非メイン Ractor 内で ActiveRecord モデルのスキーマ情報にアクセスしても、
- 互換性:
- 公開 API(メソッドシグネチャなど)は変えておらず、既存のアプリ側コードの修正は不要と考えられます。
- ただし、「Ractor 上で ActiveRecord を使ったときに落ちていたケース」が静かに動くようになる可能性があり、 これまで Ractor が前提ではなかったコードで新しい並列化パターンが成立し得ます。
- 注意点:
- この PR は 「connection をブロックの外に出した」だけなので、 ActiveRecord 全体が Ractor セーフになったわけではありません。
- Ractor 上で ActiveRecord を積極的に使う場合は、
- 共有可能でないオブジェクトを Ractor 間で渡していないか
- グローバル状態(クラス変数、シングルトン)をどう扱うか
などを別途検証する必要があります。
- 参考情報 (あれば)
- Ractor と shareable オブジェクト:
- Ruby では Ractor 間でやり取りできるのは「shareable なオブジェクト」に限られます。
- 非 shareable なオブジェクト(IO, Mutex, 多くのクラスインスタンスなど)をキャプチャしたブロックを Ractor 間で渡そうとするとエラーになります。
- 本 PR はファイル変更 1 件(
model_schema.rbのみ)、+12/-4 と小さいですが、 「on_mainブロック内で非 shareable な状態を持たない」という設計方針を明確化しており、 今後の Ractor 対応コードを書く際の指針としても参考になります。
#58786 Avoid some allocations and speed up the template lookup path
マージ日: 2026/9/16 | 作成者: @jhawthorn
- 概要 (1–2文で) Rails のテンプレート探索(lookup)周りの実装を見直し、余分なオブジェクト割り当て(allocation)を削減してパフォーマンスを改善する変更です。特に
LookupContextからPathSetにパス正規化処理を移すことでテンプレート検索時の配列生成を避け、部分テンプレート大量レンダリング時のスループットが約 12% 向上しています。
- 変更内容の詳細
2-1. 目的:テンプレート lookup パスの高速化
この PR が狙っているのは主に次の 2 点です。
- テンプレート/パーシャルの探索処理で発生していた「不要なオブジェクト割り当て」を減らす
LookupContextとPathSetの責務を整理し、より低レイヤー(PathSet)側でパス正規化を行うことで処理を単純化かつ高速化する
Rails のビュー描画では、
- テンプレート名 → 実際のファイルパス
- どのフォーマット(
html,json等)・ハンドラ(erb,haml等)・バリアントを使うか をLookupContext/PathSetを通じて解決しますが、その過程で毎回配列などのオブジェクトを生成していた箇所を削る、という性質の変更です。
2-2. パス正規化を LookupContext から PathSet に移動
これが PR の「一番大きい」変更点と記載されています。
従来:
ActionView::LookupContextが「テンプレートパスの正規化(例:"users/show"を正規化して"users/show.html.erb"探索用のキーに変換、あるいはパスの配列を生成)」を行い、- その正規化結果を基に
PathSetに問い合わせてテンプレートを探していました。
変更後:
- パスの正規化ロジックを
LookupContextではなくActionView::PathSet側に移しました。 - これによって
LookupContext側では配列など「中間的なデータ構造」を組み立てず、PathSet内で直接正規化・探索する形になります。
効果:
- 「正規化結果を返すための配列」を生成していた部分が不要になり、1 回の lookup あたりの割り当てが減る。
- コードの責務も、テンプレートパスを実際に扱う
PathSetに集約されるためシンプルになる(呼び出し側は「パスを渡して結果を受け取る」だけ)。
実際のコードとしては:
actionview/lib/action_view/lookup_context.rb- テンプレート検索時に配列や中間オブジェクトを返していた経路が簡素化されているはずです。
normalize_*系の処理(normalize_templateやパスの分解など)が縮小・削減され、PathSetに任せるようになっています。
actionview/lib/action_view/path_set.rb- これまで
LookupContextでやっていたパス正規化のロジックが新たに追加・拡張されています(+57/-14 と大きめの変更)。 - 具体的には:
- テンプレート名・パーシャル名を「ディレクトリ+ベース名+ハンドラ+フォーマット」の形に分解する
- キャッシュキーを組み立てる
- 実際のファイルシステム/
Resolverを使った探索 といった処理の途中で、余計な配列・文字列の一時オブジェクトを生まないような形に最適化していると考えられます。
- これまで
2-3. PartialRenderer の軽微な修正
actionview/lib/action_view/renderer/partial_renderer.rb も +4/-1 だけ変更されています。
- ここはパーシャルの lookup を多用する箇所なので、
PathSetの新しい API あるいは新しい戻り値仕様に合わせる形の修正だと考えられます。 - 例として:
- 以前:
lookup_context.find_all(...).firstのように配列を受け取って最初の要素を取っていた - 変更後:
lookup_context.find(...)のように最初から単一のテンプレートだけを返すメソッドを使う のような変更であれば、配列生成自体が不要になるので allocation 削減に直結します。
- 以前:
今回の PR では
- 「テンプレート 1 枚+パーシャル 100 個」を描画するベンチマーク
view_context.render(template: "bench/show")の実測
が示されていますが、show テンプレート内で同一のパーシャルを 100 回レンダリングしているため、PartialRenderer の微小な改善もそのまま効いてきます。
ベンチマーク結果
Before:
template + 100 partials: 4.527k i/s (± 3.4%, GC 16.7%)
After:
template + 100 partials: 5.095k i/s (± 1.1%, GC 14.6%)- 約 4,527 → 5,095 i/s と約 12.5% 向上
- GC 時間の割合も 16.7% → 14.6% と減っており、allocation 削減の効果が表れています。
- 影響範囲・注意点
3-1. 直接の影響を受ける箇所
ActionView::LookupContextActionView::PathSetActionView::Renderer::PartialRenderer
これらを直接拡張/モンキーパッチしているコードがある場合は、以下に注意してください。
3-2. 互換性リスク
Rails アプリ側で正常に public API(render, view helpers 等)だけを使っている限り、この PR は「高速化のみ」を目的としており、挙動は変えないはずです。
ただし、次のようなケースでは影響を受ける可能性があります。
LookupContextを直接触っている場合- 例:
view_context.lookup_contextから内部のpaths/details_key/view_pathsを直接参照・上書きしている LookupContext内部でのパス正規化や戻り値の型(配列か単一オブジェクトか等)に依存したコードがある場合、壊れる可能性があります。
- 例:
PathSetを継承またはモンキーパッチしている場合ActionView::PathSet#find,find_all,eachなど、パス探索関連のメソッドの中身が変わっているため、そこに依存したハックは挙動が変わる可能性があります。- 特に、
PathSetが「どのタイミングでパス正規化を行うか」「どの型のオブジェクトを返すか」に依存していた場合は確認が必要です。
自作 Resolver / Template クラスとの組み合わせ
- 独自の
ActionView::Resolver実装やActionView::Templateラッパーなどで、PathSetに渡される/返される情報形式を前提にしている場合は、念のためテストを回して挙動を確認してください。
- 独自の
3-3. パフォーマンス面の期待
- パーシャルを大量にレンダリングする画面(リスト表示・テーブル表示など)では、今回の最適化により描画速度の向上が期待できます。
- 専門的には「テンプレート lookup のホットパスにおける allocation 削減」が主目的なので、
- 描画にかかる CPU 時間
- GC 発生頻度・時間 の両方に、わずかでもよい影響が見込まれます。
アプリケーションコード側で特別な対応は不要ですが、
- ビュー描画性能を計測している場合は、この PR を含むバージョンへのアップデート後にベンチマークを取り直すと、変化が確認できる可能性があります。
- 参考情報 (あれば)
- PR: https://github.com/rails/rails/pull/58786
- 関連コンポーネントのドキュメント:
- Action View Overview
https://guides.rubyonrails.org/action_view_overview.html - レンダリングの仕組み(特に lookup context / view paths についての節)
Guides には詳細実装までは書かれていませんが、lookup_context・view_pathsの概念理解に役立ちます。
- Action View Overview
内部 API に踏み込んでカスタマイズしているプロジェクトでは、LookupContext / PathSet に依存したコードがないかを一度確認してからアップデートすることを推奨します。
#58780 Skip leaky ractor tests
マージ日: 2026/9/16 | 作成者: @byroot
- 概要 (1-2文で)
Ractor 内でrequireが呼ばれてしまうテストをスキップすることで、Kernel#requireをエイリアス/ラップしている Zeitwerk・Bootsnap・Rubygems などとの互換性問題を回避する PR です。Ractor の制約に反する「リーキー」なテストを一時的に無効化し、テスト環境の安定性を優先しています。
- 変更内容の詳細
問題の背景
- Rails の一部テストが Ractor 内部で
requireを呼び出す 挙動をしていた。 - 一方、多くの gem(Zeitwerk, Bootsnap, Rubygems など)はロードフックやパフォーマンス向上のために
Kernel#requireに alias method chain / ラップ処理 を行っている。 - Ractor はスレッドとは異なる並列モデルであり、
- グローバルな状態(特にメソッドの再定義・モンキーパッチ)
- Ractor セーフでない API の利用
などに対して非常に厳しい制約があるため、
こうしたラップ済みのrequireを Ractor 内から呼ぶとエラーや不整合が発生 しうる。
この PR は「テスト側が Ractor の制約を破っている」ために発生する問題を、テストをスキップすることで暫定的に解決 しています。
具体的な変更 (ファイルごと)
1. activemodel/test/cases/attribute_registration_test.rb (+20/-17)
主な変更点:
- Ractor を使うテストのうち、
requireを Ractor 内部から呼び出してしまうケースを スキップ するように変更。 - おそらく以下のような構造のテストが対象:
- Ractor 内で Active Model 属性定義クラスを動的ロードする
- その過程で
requireが呼び出される
- PR タイトルの “Skip leaky ractor tests” から、Ractor の外に影響(= leak)する副作用を持つテストを
skipまたはomitする形に書き換えていると考えられます。
イメージとしては以下のような変更が入っている可能性が高いです(擬似コード):
if defined?(Ractor)
def test_attribute_registration_in_ractor
skip "Leaky with Ractor due to require hooks (Zeitwerk/Bootsnap/Rubygems)"
# もともとはここで Ractor を起動し、その中で require を実行していた
# Ractor.new do
# require "some/model"
# # ...
# end.take
end
endもしくは Ractor 利用のパス自体を条件分岐で避ける形に変えた可能性もあります。
2. activemodel/test/cases/conversion_test.rb (+1/-0)
3. activemodel/test/cases/naming_test.rb (+1/-0)
- これらは行数が +1 のみで -0 なので、
- テストをスキップする条件メソッドの 共通化のためのヘルパー を
requireする - または
ractor: trueのようなタグ付け・コメント追加 など、小さな追加のみ が行われています。
- テストをスキップする条件メソッドの 共通化のためのヘルパー を
例としては以下のようなものが考えられます(擬似コード):
require_relative "../support/ractor_test_helpers"あるいは:
# These tests are known to be leaky when require is hooked (Zeitwerk/Bootsnap)実際のロジック変更は attribute_registration_test.rb がメインで、conversion_test.rb と naming_test.rb はそれに付随した最小限の調整です。
- 影響範囲・注意点
影響範囲
- 対象はテストコードのみ であり、Rails 本体のランタイム挙動(本番アプリケーションの動作)は変わりません。
- Active Model の:
- attribute registration 関連の Ractor テスト
- conversion / naming テストの一部(Ractor 関連の補助的なもの) がスキップされる/条件付き実行になります。
- Zeitwerk, Bootsnap, Rubygems など、
Kernel#requireをモンキーパッチしている gem と Ractor を併用してテストを走らせる環境 でのクラッシュ・不具合を回避します。
注意点・開発者視点での示唆
- Ractor 内での
require呼び出しは慎重に扱う必要がある:- 特に、
Kernel#requireに対して alias method chain / prepend / モンキーパッチを施す gem がある場合、Ractor セーフでないコードが呼ばれてしまう。 - Ractor 対応を本格的にするなら、
- require フックを Ractor セーフに書き換える
- Ractor 内からはフック済み
requireを呼ばない設計にする などの対応が必要になる。
- 特に、
- この PR は 根本対応ではなくテスト回避 なので、
- 「Ractor 環境下で Rails + Zeitwerk/Bootsnap を完全に安全に動かせる」ことを保証するものではない
- あくまで、既存の gem 構成で Rails テストを走らせる際の 実務上の不具合を解消 するための措置。
- Ractor 対応や並列実行テストを強化したい場合、
- 「どこで
requireが呼ばれているか」 - 「その
requireがどの gem のフックを経由しているか」 を意識して設計・テストを書く必要があります。
- 「どこで
- 参考情報 (あれば)
- この PR が言及している主な関連コンポーネント:
Kernel#requireにフック/モンキーパッチを行う gem:- Zeitwerk: Rails 標準のコードローダー
- Bootsnap: require の高速化・キャッシュ
- Rubygems: gem ローディング機構本体
- Ractor とグローバルメソッドの再定義・モンキーパッチは相性が悪く、
Ruby 本体側でも Ractor セーフティの観点から多くの制約・警告があります。
Ractor 対応を進める際は、Kernel#requireのような中核メソッドのラップ処理と並行実行の組み合わせに特に注意が必要です。
#58769 Add JSON schema cache serializer
マージ日: 2026/9/16 | 作成者: @ilianah
- 概要 (1-2文で)
Rails のSchemaCacheを JSON 形式でダンプ/ロードできる新しいシリアライザが追加され、既存の YAML に比べて約 20 倍以上高速に読み込めるようになりました。ActiveRecord のスキーマキャッシュの内部表現を JSON で安全にシリアライズできるようにするため、型周りを中心に多数のクラスに JSON 対応が入っています。
- 変更内容の詳細
2-1. JSON スキーマキャッシュシリアライザの追加
新ファイル: activerecord/lib/active_record/connection_adapters/schema_cache_serializer.rb
主な役割:
ActiveRecord::ConnectionAdapters::SchemaCacheの内容を- YAML だけでなく
- JSON でもダンプ/ロードできるようにする
- スキーマキャッシュに含まれる情報(テーブル定義、カラム、インデックス、type metadata など)を、JSON で表現可能な素朴な Hash/Array/数値/文字列に落とし込む
- 逆に、JSON から ActiveRecord 内部オブジェクト (
Column,SqlTypeMetadata, 各種 OID 型など) を再構築する
ベンチマーク結果(944 テーブル):
YAML load: 4.6 i/s (約 219ms/回)
JSON load: 99.6 i/s (約 10ms/回)
=> JSON は YAML より約 22 倍高速API 的には既存の SchemaCache#dump_to を拡張する形で、拡張子やオプションに応じて JSON / YAML を切り替える構造になっていると考えられます(PR 説明にある通り、実際に dump_to("/tmp/schema.json") で JSON が出力される)。
schema_cache = ActiveRecord::Base.connection.schema_cache
# 拡張子で自動判別される想定(PR の利用例より)
schema_cache.dump_to("/tmp/schema.yml")
schema_cache.dump_to("/tmp/schema.json")
# 読み込み側(内部 API)
ActiveRecord::ConnectionAdapters::SchemaCache._load_from("/tmp/schema.yml")
ActiveRecord::ConnectionAdapters::SchemaCache._load_from("/tmp/schema.json")2-2. SchemaCache 本体の拡張
ファイル: activerecord/lib/active_record/connection_adapters/schema_cache.rb (+20)
_load_from(path)が JSON/YAML 両対応になるよう、ファイルパスや中身を見てSchemaCacheSerializerを通すように変更dump_toも同様に、内部でSchemaCacheSerializerを利用して、フォーマットに応じたシリアライズを委譲- これにより、アプリケーション側は基本的な API を変えずに、拡張子や設定で JSON を使えるようになる設計
2-3. 各種 type / metadata クラスの JSON 対応
スキーマキャッシュにはカラム定義とともに「型情報」が含まれるため、JSON で出し入れできるよう、以下のクラスにシリアライズ用のメソッド(またはそれに類する仕組み)が追加されています。
主な変更ファイル:
activerecord/lib/active_record/connection_adapters/column.rb(+12)activerecord/lib/active_record/connection_adapters/sql_type_metadata.rb(+12)activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb(+9)activerecord/lib/active_record/connection_adapters/postgresql/type_metadata.rb(+10)- PostgreSQL OID 系:
postgresql/oid/array.rb(+11)postgresql/oid/range.rb(+9)postgresql/oid/specialized_string.rb(+11)postgresql/oid/vector.rb(+9)
activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb(+44)
典型的には次のようなことをやっているはずです(擬似コード):
class SqlTypeMetadata
# JSON/dump 用
def as_json_schema_cache
{
sql_type: sql_type,
type: type,
limit: limit,
precision: precision,
scale: scale,
# アダプタ固有の情報などもここに含める
}
end
# JSON/load 用
def self.from_json_schema_cache(hash)
new(
sql_type: hash[:sql_type],
type: hash[:type],
limit: hash[:limit],
precision: hash[:precision],
scale: hash[:scale],
# ...
)
end
endこれにより、JSON へは単純な Hash として書き出し、復元時にはクラスメソッドからインスタンスを生成するパターンで往復できるようにしています。
2-4. ActiveModel の型クラスへの変更
ファイル:
activemodel/lib/active_model/type/value.rb(+10)activemodel/lib/active_model/type/immutable_string.rb(+10)activerecord/lib/active_record/type/internal/timezone.rb(+11)
スキーマキャッシュ内では、カラムの default 値や型情報に ActiveModel::Type 系が絡むことがあるため、これらも JSON シリアライズ可能な形に変換するためのフックが追加されています。
内容としては、型オブジェクトをそのまま保存するのではなく、型名や必要なオプションなど「再構築に必要な最低限の情報」を Hash で表現し、ロード時に復元する形式になっていると推測されます。
2-5. アダプタ固有の schema_definitions 拡張
ファイル:
activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb(+44)activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb(+11)
これらでは
- テーブル・カラム定義オブジェクトを JSON 化するためのメソッド
- MySQL 固有のオプション(encoding/collation など)を JSON へ落とす/戻す処理
が追加されています。
抽象スーパークラスに共通の JSON 化ロジックを追加し、アダプタ毎に必要な情報を上書きできるようにする設計と考えられます。
2-6. テストと CHANGELOG
activerecord/test/cases/connection_adapters/schema_cache_test.rb(+13)- JSON での dump/load が正しく動作し、YAML と同等の挙動になることを検証するテストが追加。
activerecord/CHANGELOG.md(+7)- 「SchemaCache が JSON キャッシュをサポートした」旨のエントリが追加。
- 影響範囲・注意点
3-1. 影響範囲
- 対象:
- ActiveRecord を利用している Rails アプリケーション全般
- 特に
schema_cache.dump_to/schema_cache.load_from相当の API を利用し、スキーマキャッシュをファイルに落としているアプリ
- DB アダプタ:
- PostgreSQL / MySQL については type metadata と OID 型周りの JSON 対応が入っており、公式にサポートされる想定
- 他アダプタも基本的には影響を受けるが、個々の実装によって JSON 化の恩恵や必要な拡張が異なりうる
3-2. 互換性・移行
- YAML キャッシュは引き続きサポートされており、既存の YAML ベースの運用はそのまま動作する想定です。
- 新たに JSON を使う場合:
.jsonでダンプしたキャッシュは.ymlとは互換性がない(拡張子・フォーマットセットを揃える必要がある)- デプロイ環境で
schema_cache.dumpをしている場合、YAML から JSON への切り替えタイミングで一度キャッシュを作り直す必要がある
- 独自に
SchemaCacheの内部表現を直接触っている場合:- 型オブジェクトや metadata 周りの実装追加の影響を受ける可能性があるが、この PR は基本的に後方互換を意識した追加実装になっているため、通常は問題にならないはず
3-3. パフォーマンスと運用面での注意
- 読み込み速度が大幅に向上しているため、以下のようなケースで恩恵が大きいと考えられます:
- テーブル数が多い本番環境
- マルチプロセス (Puma clustered / forking サーバー) でプロセスごとにスキーマキャッシュを読み込む構成
- 短時間で多数の Rails プロセスをスピンアップするバッチ・ジョブ・ジョブワーカー環境
- JSON の方がフォーマットがシンプルな分、YAML に比べて
- 解析コストが低く高速
- デシリアライズ時の安全性も一般的に高い(オブジェクトインスタンス化を伴わないため)
- 一方で、キャッシュファイルサイズそのものは JSON の方が若干大きくなる可能性があります(ただし I/O よりパースコストの方がボトルネックになりやすいため、多くのケースで JSON の方が有利)。
- 参考情報 (あれば)
- PR タイトル: Add JSON schema cache serializer
- PR 番号: #58769
- 追加された主な API/機能:
ActiveRecord::ConnectionAdapters::SchemaCache#dump_toで JSON (.json) を出力できるようになったActiveRecord::ConnectionAdapters::SchemaCache._load_from(path)が YAML/JSON 両対応になった- スキーマキャッシュに関係する type/metadata クラスに JSON 用のシリアライズ/デシリアライズロジックが追加された
- ベンチマーク: 約 944 テーブルのアプリで JSON ロードが YAML の約 22 倍高速(219ms → 10ms 程度)
#58751 Speed up TimeWithZone#xmlschema, #to_s and TimeZone.seconds_to_utc_offset
マージ日: 2026/9/16 | 作成者: @yaroslav
- 概要 (1-2文で)
TimeWithZone の文字列表現関連メソッドと TimeZone.seconds_to_utc_offset の実装を見直し、同じ出力を維持したままパフォーマンスとメモリ効率を大きく改善する変更です。ActiveSupport 全体で時刻のフォーマットを行う箇所が、少ないオブジェクト確保で数倍高速になります。
- 変更内容の詳細
2-1. TimeWithZone#xmlschema / #iso8601 / #rfc3339 の高速化
対象メソッド:
TimeWithZone#xmlschema- そのエイリアス:
#iso8601,#rfc3339
従来は:
if @is_utc
utc.iso8601(precision)
else
str = time.iso8601(precision)
offset = formatted_offset(true, "Z") # "+09:00" など
# 末尾の Z または "+HH:MM" を ActiveSupport 側で生成した offset に置換
str.sub!(/(Z|[+-]\d{2}:\d{2})\z/, offset)
str
endというように、
- 一度
Time#iso8601で文字列を生成 - 正規表現で末尾のタイムゾーン部分を検出
formatted_offsetの結果に差し替え
という「一回フォーマットしてから書き換える」処理をしていました。
今回の変更では、「UTC 以外の TimeWithZone」について:
time.getlocal(utc_offset).iso8601(precision)のように、
- まず
getlocal(utc_offset)で、UTC オフセット付きのTimeオブジェクトを直接作る - その
Timeに対してTime#iso8601を呼ぶ
という形に変更しています。
Ruby の Time#iso8601 自体が:
+HH:MM形式の固定オフセットを出力する- 秒を含むオフセットは分単位に丸める
といった挙動をすでに備えているため、ActiveSupport 側で正規表現やformatを使って再フォーマットする必要がなくなっています。
DateTime ベースの TimeWithZone への対応
DateTime には getlocal がないため、DateTime から作られた TimeWithZone(ローカル DateTime や、その utc を元にしたもの)は、既存の private メソッド transfer_time_values_to_utc_constructor を経由させて、Time ベースの値に変換した上で上記の処理を行います。
これは TimeWithZone#initialize がすでに行っているパスと同等で、挙動は従来どおりに保たれます。
2-2. TimeWithZone#to_s の高速化
TimeWithZone#to_s は Ruby の Time#to_s が出すフォーマット:
"YYYY-MM-DD HH:MM:SS +HHMM"
# UTC の場合は "YYYY-MM-DD HH:MM:SS UTC"を ActiveSupport 側で「手書き」で模倣していました:
"#{time.strftime("%Y-%m-%d %H:%M:%S")} #{formatted_offset(false, 'UTC')}"この PR では xmlschema と同様に、
- Ruby の
Time#to_sに実際のフォーマットを任せる - オフセット付きの
Timeをgetlocal(utc_offset)で得る
という形に変更しています。
結果として、フォーマット文字列・strftime・オフセット文字列の連結といった余計な処理を避けられます。
UTC の場合も Ruby 標準の Time#to_s が "UTC" を出す挙動に合わせることで、今まで手で再現していたものをそのまま標準ライブラリに委譲する形になります。
2-3. TimeZone.seconds_to_utc_offset の最適化
対象メソッド:
ActiveSupport::TimeZone.seconds_to_utc_offset(seconds, colon = true)
従来:
UTC_OFFSET_WITH_COLON = "%s%02d:%02d"
UTC_OFFSET_WITHOUT_COLON = "%s%02d%02d"
def seconds_to_utc_offset_before(seconds, colon = true)
format = colon ? UTC_OFFSET_WITH_COLON : UTC_OFFSET_WITHOUT_COLON
sign = seconds < 0 ? "-" : "+"
hours = seconds.abs / 3600
minutes = (seconds.abs % 3600) / 60
format % [sign, hours, minutes]
endというように、format + 配列を使っていました。
今回の変更では:
- 符号(
+/-) - 2 桁の時・分 (
00〜23,00〜59)
などを「凍結済みのテーブル」から取り出して文字列連結する形に変えています。
イメージとしては:
TWO_DIGITS = %w[00 01 02 ... 59].freeze
sign = seconds < 0 ? "-" : "+"
h = TWO_DIGITS[hours]
m = TWO_DIGITS[minutes]
colon ? "#{sign}#{h}:#{m}" : "#{sign}#{h}#{m}"のような構造です(実際のコードはもう少し最適化されているはずですが、方向性はこのイメージ)。
このメソッドは ActiveSupport 内でかなり広く使われており:
Time#formatted_offsetDateTime#formatted_offsetTimeZone#formatted_offsetTimeWithZone#formatted_offset
を通じて:
to_sinspectrfc2822to_fs(:rfc822)
など多くのフォーマッタから呼ばれます。
そのため、この 1 箇所の最適化が、ActiveSupport 全体の時刻フォーマットに波及します。
2-4. ベンチマークと互換性確認
PR に同梱されているベンチマークスクリプトでは:
- 12 タイムゾーン(UTC, Berlin, New_York, Kolkata, Kathmandu, etc.)
- 7 種類の時刻(DST 切替前後、歴史的オフセットなど含む)
の組み合わせに対して:
xmlschema(precision)(precisionに 0, 3, 6, nil)as_jsonto_sformatted_offset(colon = true/false)TimeZone.seconds_to_utc_offset(seconds, colon)
を旧実装と新実装で比較し、すべて同じ出力になることを確認しています。
性能面の代表的な結果(Ruby 4.0.6 +YJIT, arm64-darwin):
Europe/Berlin xmlschema- 1.53M → 4.74M i/s(約 3.1 倍)
- allocations/call: 6 → 2
Europe/Berlin as_json- 約 2.7 倍高速
- allocations/call: 6 → 2
Europe/Berlin to_s- 約 1.2 倍高速
- allocations/call: 4 → 2
TimeZone.seconds_to_utc_offset(-18_000)- 約 2.0 倍高速
- allocations/call: 2 → 1
- UTC の
xmlschemaはほぼ同速(元々が十分軽量)
- 影響範囲・注意点
3-1. ロジック・フォーマット上の互換性
- ベンチマークスクリプトで、様々なタイムゾーン・時刻に対して旧実装との完全一致が検証されています。
- UTC / 非 UTC、コロンあり/なし、秒を含むオフセット(歴史的 LMT ゾーンなど)といったパターンで、出力文字列の互換性が保たれていることが前提になっています。
TimeWithZone#xmlschema,#to_s,TimeZone.seconds_to_utc_offsetの外部 API シグネチャは変わっていません。
そのため、通常のアプリケーションコードにとっては「速くなっただけ」で、挙動の変化は意識しなくて良い想定です。
3-2. 低レベルな差分に依存していた場合のリスク
次のようなケースでは影響が出る可能性があります:
- ActiveSupport 内部実装の細かい挙動(例:
TimeWithZone#xmlschemaがsub!を使うこと、seconds_to_utc_offsetが%フォーマットで実装されていること)に依存してメタプログラミングしているコード TimeWithZoneのtime/utcオブジェクトの一時的な状態や、中間文字列に対してフックしていたような極度に内部依存のコード
通常のライブラリ・アプリではこのような依存はまず行われないはずですが、もし内部実装を monkey-patch していた場合は、一度差分を確認したほうが安全です。
3-3. パフォーマンス影響のある箇所
影響が大きいのは:
- API レスポンスで
TimeWithZone#as_json(=内部的にxmlschema)を大量に呼ぶケース - ログ・監査・イベントトラッキングなどで
TimeWithZone#to_s,Time#to_s,DateTime#to_sなどを繰り返し呼ぶケース - タイムゾーン付きの時間表現を大量にフォーマット(
rfc2822,to_fs(:rfc822)など)しているケース
この PR により、同じマシンリソースでより多くのリクエスト/イベントを捌ける可能性があります。
- 参考情報 (あれば)
- PR: https://github.com/rails/rails/pull/58751
- 変更ファイル:
activesupport/lib/active_support/time_with_zone.rbTimeWithZone#xmlschema,#to_sの実装切り替え
activesupport/lib/active_support/values/time_zone.rbTimeZone.seconds_to_utc_offsetの実装置き換え
activesupport/test/core_ext/time_with_zone_test.rbactivesupport/test/time_zone_test.rb- 上記変更をカバーするテスト追加(挙動互換性を担保)
#58768 [ci skip] Remove obsolete DateTime fallback notes from core extensions guide
マージ日: 2026/9/15 | 作成者: @dersam
- 概要 (1-2文で)
このPRは、Active Support Core Extensionsガイドから、すでに存在しない「Time が範囲外になったときに DateTime にフォールバックする」という挙動を説明していた古い記述を削除するものです。実装としては何も変わらず、ドキュメントの整合性を取るための変更です。
- 変更内容の詳細
- 対象ファイル:
guides/source/active_support_core_extensions.md - 変更内容: 2つの段落(合計6行)が削除されただけで、新規追加はありません。
- 削除された内容の趣旨:
Timeオブジェクトに対して#change,#since,#agoなどを使った結果が、OSやRubyのTimeの表現可能範囲を超えた場合に、DateTimeにフォールバックすることを「保証する」ような説明がガイド内に残っていた。- しかし、実装上そのフォールバックは既に廃止されており、ガイドの記述が現実と食い違っていたため、その説明が削除された。
PR本文中で触れられている実装の変遷(ドキュメントには書かれていないが、このPRの背景として重要):
Time#change- 2012-07 以前:
Time.utc_time/Time.local_timeに委譲しており、それらはTime.time_with_datetime_fallbackを経由してDateTimeフォールバックを行う挙動を持っていた。 - 2012-07 以降:
Time.utc_time/Time.local_timeを使わず、直接Timeを構築するように変更され、DateTimeフォールバックは事実上なくなった。
- 2012-07 以前:
Time#since/Time#ago- 過去: 内部で
rescueしてto_datetimeにフォールバックするコードが存在していた。 - 2024-07 (#52370): キャッチする例外を
TypeErrorに絞り込み。 - 2025-10: フォールバック自体が完全に削除された。
- 過去: 内部で
基盤メソッドの廃止:
Time.time_with_datetime_fallback,Time.utc_time,Time.local_timeは 2012-12 に deprecate、2013-07 に削除済み。
要するに、ガイドが「古いRails時代のフォールバック仕様」をいまだに説明していたため、その部分を消して現実の実装に合わせています。
- 影響範囲・注意点
コードへの直接の影響: なし
- このPRはドキュメントのみ変更しており、
Time/DateTimeの挙動を変えるものではありません。
- このPRはドキュメントのみ変更しており、
しかし「実質的な意味での影響」として:
- これまでガイドを読んで「範囲外 Time は自動で DateTime にフォールバックするはず」と思い込んでいた開発者は、その前提が誤りだったことが明示されることになります。
- 実装上はすでに何年も前から:
Time#change,Time#since,Time#agoなどで範囲外の時間を扱ったときに、DateTimeへの自動フォールバックは行われません。- OSやRubyの
Timeの範囲外を扱おうとすると、例外(ArgumentErrorなど)が発生するか、結果が得られないケースがあります。
実装側で注意すべき点:
- 「非常に遠い過去・未来」や、タイムゾーンに依存して
Timeが表現しきれない日時を扱いたい場合は、自分で明示的にDateTimeを使う必要があります。- 例:ruby
# Time で表現できない可能性があるような日時 dt = DateTime.new(1000, 1, 1, 0, 0, 0, "+00:00")
- 例:
- 以前の(かなり古い)Railsの挙動や、古いブログ記事・書籍を前提にして 「Time を使っておけば勝手に DateTime にフォールバックしてくれる」 と期待しているコードや設計があれば、その前提はすでに崩れているため見直しが必要です。
- 「非常に遠い過去・未来」や、タイムゾーンに依存して
- 参考情報 (あれば)
本PRで言及されているコミット:
Time#changeの挙動変更: 98b46bf5 (2012-07)Time#since/Time#agoのrescue TypeErrorへの絞り込み: efd0e6cceb (#52370, 2024-07)Time#since/Time#agoのフォールバック削除: a855714642 (2025-10)Time.time_with_datetime_fallback/Time.utc_time/Time.local_timeの deprecate: 48583f8bf7 (2012-12)- 上記メソッド群の削除: ec5e03bf67 (2013-07)
実際の挙動を確認したい場合:
- 対象Railsバージョンで
Time#change,Time#since,Time#agoを、Timeの範囲ギリギリ・範囲外になるような引数で実行し、DateTimeフォールバックが起きないこと(例外になる/失敗すること)を確認するのが最も確実です。
- 対象Railsバージョンで
#58777 File.atomic_write: close the temp file before renaming it
マージ日: 2026/9/15 | 作成者: @americodls
- 概要 (1-2文で)
File.atomic_writeが一時ファイルを「開いたままリネーム」していたため、リネーム直後に中身が空のファイルが現れる問題を修正し、「一時ファイルを close してから rename する」ようにした PR です。これにより、atomic_write の宛先ファイルは生成された瞬間から正しい内容を持つことが保証されます。
- 変更内容の詳細
何が問題だったか
過去の PR (#56368) により、File.atomic_write の内部処理は以下のような流れになっていました(簡略化):
File.atomic_write("out.txt") do |f|
f.write("hello")
# この時点では Ruby のバッファに書かれているだけ
# OS にフラッシュされるのは close 時
end
# <= このブロックを抜けるタイミングで rename が走るが、
# まだファイルは「開いたまま」つまり:
- ブロック内で
f.write("hello")しても、内容は Ruby の IO バッファ内にあるだけ。 - atomic_write は「一時ファイルを開いたまま」
File.rename(temp, dst)を呼んでいた。 - OS 側で実際にデータが書き込まれる (flush) のは、その後の
closeタイミング。 - 結果として、「宛先ファイル
dstが存在するが、中身が空 or 未確定」という瞬間が生じる。
PR 説明の hook 例:
require "active_support/core_ext/file/atomic"
File.singleton_class.prepend(Module.new do
def rename(src, dst)
p File.read(src)
super
end
end)
File.atomic_write("out.txt") { |f| f.write("hello") }
# main ブランチの挙動: rename 直前の src の中身 => ""
# この PR 後の挙動: rename 直前の src の中身 => "hello"rename 時点でまだバッファが flush されておらず、読んでも空文字列になってしまうのが現状の問題点でした。
この PR で何をしたか
PR の主旨は「一時ファイルを rename する前に close する」ことです。
- 一時ファイルへの書き込み完了後、
File.renameを呼ぶ前にcloseするように修正 File.openによるブロック終了時の close は従来通り行われるが、既に閉じられているため 2 回目の close は no-op(問題なし)- 実際の差分は
atomic.rbに 1 行追加程度と小さいが、挙動上の意味は大きい変更
テスト (activesupport/test/core_ext/file_test.rb) も追加されており、
- rename の瞬間に一時ファイルの中身が書き込まれていること
- すなわち、宛先ファイルが現れたときには完全な内容を持っていること
を検証しています。
- 影響範囲・注意点
- 対象:
- ActiveSupport の
File.atomic_writeを利用している箇所(ログローテーション、設定ファイル生成、一時ファイルを経由する保存処理など)。
- ActiveSupport の
- 影響:
- 今までは「ファイルパスが存在するが内容がまだ書き込まれていない」ごく短い時間があり得ましたが、この PR によりそのギャップがなくなります。
- 監視ツールや他プロセスが「ファイル登場直後」に中身を読むようなケースでも、空ファイルを見てしまうリスクが減ります。
- 互換性:
- API の変更はなく、挙動はより直感的・安全なものになっているため、通常は後方互換的な改善と見なせます。
- もし「rename 直後は中身がまだ空である」という異常な前提に依存したコードがあれば(まずない想定ですが)、挙動が変わります。
- 参考情報 (あれば)
- 関連 PR:
- #56368:
File.atomic_writeの挙動を変え、一時ファイルを開いたまま rename するようになった変更
- #56368:
- 実装場所:
activesupport/lib/active_support/core_ext/file/atomic.rbFile.atomic_writeユーティリティは Rails 全体で「アトミックなファイル書き込み」を行うために広く利用されています。
#58770 Add herb:check task to verify Herb compatibility
マージ日: 2026/9/15 | 作成者: @marcoroth
- 概要 (1-2文で)
このPRでは、HTML+ERBテンプレートが新しいERB実装「Herb」に対応できるか事前チェックするためのbin/rails herb:checkタスクと、その裏側のActionView::HerbCheckerが追加されています。アプリ全体のテンプレートを一括コンパイルしてエラーを検出し、ビルドを落とせるようにすることで、Herb への移行を安全に行えるようにします。
- 変更内容の詳細
2.1 追加された herb:check タスク
- 新しい Rake タスク:
actionview/lib/action_view/tasks/herb.rakeにherb:checkが追加bin/rails herb:checkで実行できる
- 挙動:
- アプリケーション全体の「HTML フォーマット + ERB ハンドラ」のテンプレートを列挙
- Herb を使ってひとつずつコンパイル
- コンパイルに失敗したテンプレートの「パス」と「エラー内容」を標準出力に表示
- エラーが一つでもあれば非ゼロステータスで終了(CIのゲートに使える)
イメージ的には bin/rails zeitwerk:check と同じ「事前健全性チェック」版の Herb 版です。
使い方イメージ(想定)
# ローカルでチェック
bin/rails herb:check
# CI のジョブでビルドを落とす用途で
bin/rails herb:check && bin/rails testHerb をデフォルトに切り替える前に、既存テンプレートが壊れないかを洗い出す用途が主目的です。
2.2 ActionView::HerbChecker の追加
ファイル: actionview/lib/action_view/herb_checker.rb
役割:
- 全テンプレートを解決し、Herb でコンパイルして検証する専用クラス。
主なポイント:
テンプレートの列挙方法
- ファイルシステムを自前で glob するのではなく、「resolver」経由でテンプレートを列挙
- これにより:
- variant(例:
index.html+phone.erb) - locale(例:
index.en.html.erb) - engine の view_path(mount されたエンジン内の view) なども、実行時の lookup と同じルールで網羅的にチェックできる
- variant(例:
チェック対象のフィルタ
- フォーマット: HTML
- ハンドラ: ERB
- →
*.html.erb系(variants, locales つきも含む)を対象
コンパイル方法
ActionView::Template::Handlers::ERB.callを利用しつつ、以下のオプションを強制:implementation: Herb(Herb 実装を利用するよう強制)validate_ruby: true(Herb が生成した Ruby コードが構文的に正しいかもチェック)
- これにより、以下の2パターンを検出:
- Herb がテンプレート自体を「構造的に不正」と判断するケース
- Herb が生成した Ruby がパースエラーになるケース
2.3 ERB ハンドラ (ActionView::Template::Handlers::ERB) の拡張
ファイル: actionview/lib/action_view/template/handlers/erb.rb
変更点:
callメソッドのシグネチャが拡張され、以下が追加:implementation:キーワード引数(ERB 実装を差し替えるため)- 任意の
**overridesキーワード引数(engine オプションの上書き用)
- これらは内部で ERB エンジンのオプションにマージされて使われる設計
目的:
- HerbChecker から「既存のハンドラのロジック(エンコーディング処理、escape ルール、annotation preamble など)を再利用したまま、実装だけ Herb に差し替える」ことを可能にするため。
- もしこの拡張がないと、HerbChecker は ERB エンジンを直接 new して、ハンドラ内部の各種前処理を全部コピーする必要があり、重複やメンテコストが増える。
簡易イメージ:
# もとの ERB
ActionView::Template::Handlers::ERB.call(template, source)
# Herb 強制 + validate_ruby 有効化
ActionView::Template::Handlers::ERB.call(
template,
source,
implementation: ActionView::Template::HTML::Herb, # 例
validate_ruby: true
)2.4 Resolver 拡張: FileSystemResolver#all_unbound_templates
ファイル: actionview/lib/action_view/template/resolver.rb
- 新メソッド
all_unbound_templatesが追加され、:nodoc:扱い(公開APIではない、内部的なもの) - 既存の private メソッド:
template_globbuild_unbound_templateを組み合わせて、resolver が管理するすべてのテンプレートを「UnboundTemplate」として返す機能を提供
理由:
- 既存の public メソッドでは HerbChecker の用途に合わないため:
all_template_paths:- format や handler の情報が含まれておらず、HerbChecker 側で再度解析する必要がある
find_all(details...):- 「あるリクエスト条件でどのテンプレートが選ばれるか」を前提にフィルタされる
- HerbChecker は「どのリクエストからも選ばれないテンプレートも含め、とにかく全部チェックしたい」ので不適
all_unbound_templates により、resolver の持つ「テンプレートの完全リスト」を内部的に取得でき、そこから format=html, handler=erb をフィルタしてチェックする形になる。
2.5 Railtie, CHANGELOG, Tests
actionview/lib/action_view/railtie.rbherb:checkタスクを Rails のタスク一覧に統合
actionview/CHANGELOG.md- 新機能として
herb:checkの追加が記載
- 新機能として
railties/test/application/herb_check_test.rbherb:checkタスクのテストが追加- 主に以下を検証:
- 正常なテンプレートのみの場合、タスクが成功する
- Herb でエラーになるテンプレートがある場合、パス + エラー内容が出力され、タスクが失敗ステータスを返す
- variant / locale / エンジンパス等へも正しく作用する(と推測されるテスト)
- 影響範囲・注意点
3.1 影響範囲
新しい Rake タスク
herb:checkが利用可能にActionView::Template::Handlers::ERB.callの引数仕様が拡張- 既存呼び出し側はキーワード引数を使っていなければ基本的に非互換は発生しない想定
- なお Ruby のキーワード引数周りで独自にラップしているコードがある場合はシグネチャ変更の影響を受ける可能性はあるため注意
Resolver の内部 API 追加(
:nodoc:)- 通常のアプリケーションコードには影響なし
- gem / plugin が Resolver の private 部分を monkey patch している場合は注意
3.2 実運用上の注意
herb:checkは HTML+ERB テンプレートのみが対象*.text.erbや*.json.erbなどはチェックされない
- すでに Herb を本番で使っていない状態でも実行可能
- Herb へ切り替える前の「事前スキャン」として使うのが想定ユースケース
- エラー検出パターン:
- Herb 独自の構造チェックエラー(従来の Erubi では通るが Herb では NG なテンプレート)
- Herb が生成した Ruby コードの構文エラー
- CI 組み込みの推奨:
- 「Herb でコンパイルできないテンプレートが追加されたらビルドを落とす」というガードレールを容易に導入できる
- 参考情報 (あれば)
- Herb 関連PR:
- Herb の導入 PR: #58552
- Herb を HTML フォーマットのデフォルト ERB 実装にする提案 PR: #58721
- 本PRはこれに依存しておらず、先に取り込まれても動作する設計
- 類似のチェック系タスク:
bin/rails zeitwerk:check(autoloading の事前検証)- 本 PR の
herb:checkはこの思想に倣っており、「本番切り替え前に壊れたテンプレートを検知する」ための安全ネットとして位置づけられています。
#58725 Pass Trilogy's SSL configuration to the mysql and mysqldump commands
マージ日: 2026/9/15 | 作成者: @ngan
- 概要 (1-2文で)
Trilogy アダプタ使用時に、dbconsoleやdb:structure:dump/db:structure:loadが MySQL クライアント (mysql,mysqldump) へ正しく SSL オプションを渡していなかった問題を修正し、接続と同じ SSL 設定が CLI コマンドにも反映されるようにした PRです。SSL 設定のキー名の違い(ssl_cavssslcaなど)を吸収する共通マッピングを導入し、Trilogy 固有の SSL オプションにも対応しています。
- 変更内容の詳細
問題の背景
- Rails の以下の機能は、内部で
mysql/mysqldumpコマンドを呼び出します:rails dbconsoledb:structure:dump/db:structure:load(特にformat: :sql)
- これらが組み立てるコマンドライン引数は、mysql2 ドライバの SSL オプション名(
sslca,sslcapath,sslcert,sslcipher,sslkey)だけを前提にしていました。 - 一方、Trilogy ドライバはアンダースコア付きのキー名を取ります:
ssl_ca,ssl_capath,ssl_cert,ssl_cipher,ssl_key,(さらにssl_crl,ssl_crlpath)
- 結果として:
- DB 接続自体は
ssl_caなどで正しく張れるのに、 mysqldumpには--ssl-mode=verify_caだけが渡され、--ssl-caが渡されない
→ MySQL 側で「CA 証明書必須」のエラーが発生していました。
- DB 接続自体は
mysqldump: Got error: 2026: SSL connection error: CA certificate is required
if ssl-mode is VERIFY_CA or VERIFY_IDENTITY when trying to connect- さらに、
sslcaというキー名に変えても以下の問題があります:- Trilogy は
sslcaを認識しない → 接続設定としては無視される - そのため、接続はデフォルトのシステム CA ストアで検証されるだけになり、意図した CA が使われない
- Trilogy は
PR ではこの「接続時と CLI 実行時で SSL 設定が食い違う」「キー名の揺れによるサイレントな設定落ち」を解消しています。
具体的な実装変更
1) SSL オプションのマッピングを共通化 (AbstractMysqlAdapter)
- これまで:
dbconsole用 (MysqlDatabaseTasks内)structure_dump/load用
で、それぞれが 独自に mysql2 向けのオプションマッピングを持っていた。
- 変更後:
AbstractMysqlAdapterにcli_arg_mapクラス属性を定義し、CLI に渡すオプションマップを一元管理。dbconsole/structure_dumpの両方が、このcli_arg_mapを参照して引数を組み立てるように変更。
イメージとしては(実際のコードはやや異なりますが):
class AbstractMysqlAdapter
class_attribute :cli_arg_map, default: {
sslca: "--ssl-ca",
sslcapath: "--ssl-capath",
sslcert: "--ssl-cert",
sslcipher: "--ssl-cipher",
sslkey: "--ssl-key",
ssl_mode: "--ssl-mode"
}
endというマップを持ち、dbconsole / mysqldump 呼び出し時に
config[:sslca] #=> "--ssl-ca=/path/to/ca.pem" に変換
config[:ssl_mode] #=> "--ssl-mode=VERIFY_CA" に変換のように使われる、という構造になりました。
2) Trilogy アダプタでのマップ拡張 (TrilogyAdapter)
TrilogyAdapter側で、上記のcli_arg_mapを Trilogy 用に拡張しています。- Trilogy が受け付けるキー名(
ssl_ca,ssl_capath,ssl_cert,ssl_cipher,ssl_key,ssl_crl,ssl_crlpath,ssl_mode)に対して、mysql/mysqldumpのフラグをマッピング:
例(概念的なイメージ):
class TrilogyAdapter < AbstractMysqlAdapter
self.cli_arg_map = super.merge(
ssl_ca: "--ssl-ca",
ssl_capath: "--ssl-capath",
ssl_cert: "--ssl-cert",
ssl_cipher: "--ssl-cipher",
ssl_key: "--ssl-key",
ssl_crl: "--ssl-crl",
ssl_crlpath:"--ssl-crlpath"
)
endこれにより:
production:
adapter: trilogy
ssl_mode: VERIFY_CA
ssl_ca: /path/to/ca.pemのような設定をすると、
- Rails から Trilogy への接続:
ssl_mode,ssl_caで正しく SSL 設定 - Rails から
mysqldumpへの実行:--ssl-mode=VERIFY_CA --ssl-ca=/path/to/ca.pemが渡される
という 一貫した挙動になります。
さらに、Trilogy がサポートしていて mysql2 がサポートしていない:
ssl_crlssl_crlpath
についても CLI へ渡されるようになります(mysqldump 側が理解する前提)。
3) 不正確だった以前の挙動を整理
- 変更前:
- Trilogy 使用時でも、
configにsslcaなど mysql2 形式のキーがあると、それをそのまま--ssl-caなどとして CLI に渡していた。 - しかし、Trilogy 自身は
sslcaを理解しないため、接続設定としては効いておらず、「CLI にだけ効いているように見える」状態だった。
- Trilogy 使用時でも、
- 変更後:
- 「自分のドライバが知らないキー名」は CLI にも渡さないように仕様変更。
- したがって、Trilogy 環境で
sslcaを指定しても--ssl-caは付与されません。 - 作者は、これは「もともと接続にも効いていなかった設定なので、CLI にだけ残すのは不正確」という判断です。
テスト面:
- Trilogy の 2 つのテストは「
sslcaが CLI に渡る」前提で書かれていたため、新挙動に合わせて修正。 - mysql2 側のテストは、従来通りのキー名 (
sslca等) を使っており動作も変えていないためそのまま。
- 影響範囲・注意点
影響範囲
- Trilogy アダプタ + MySQL を使っている Rails アプリで、かつ:
- SSL を有効にしている (
ssl_mode設定あり) dbconsoleやdb:structure:dumpを利用している
というケースに実質的な影響があります。
- SSL を有効にしている (
- mysql2 アダプタ利用時は、マッピングの定義場所が変わっただけで、動作は従来通りです。
正しく動くようになるケース
- 典型例:
production:
adapter: trilogy
encoding: utf8mb4
host: 127.0.0.1
username: root
ssl_mode: VERIFY_CA
ssl_ca: /path/to/ca.pemこの場合:
- 変更前:
- Rails → DB: OK(Trilogy は
ssl_caを解釈) - Rails → mysqldump:
--ssl-mode=VERIFY_CAのみ → CA 未指定エラーで失敗
- Rails → DB: OK(Trilogy は
- 変更後:
- Rails → mysqldump:
--ssl-mode=VERIFY_CA --ssl-ca=/path/to/ca.pem→ 正常に dump 可能
- Rails → mysqldump:
以前の「誤った」設定が効かなくなるケース
- もし既に、Trilogy アダプタで以下のように書いていた場合:
production:
adapter: trilogy
ssl_mode: VERIFY_CA
sslca: /path/to/ca.pem- 変更前:
- Rails → DB:
sslcaは無視され、システム CA で検証されていた(サイレントに意図と違う挙動) - Rails → mysqldump:
--ssl-ca=/path/to/ca.pemが渡る(ここだけ意図通りのように見える)
- Rails → DB:
- 変更後:
- Rails → DB:(変わらず)
sslcaは無視 - Rails → mysqldump:
--ssl-caも渡されない
→ 「接続設定と CLI 設定が一致する」ようになった一方で、「たまたま CLI だけ合っていた」状態は崩れる
- Rails → DB:(変わらず)
そのため Trilogy 利用時は sslca ではなく ssl_ca を使う必要があります。
注意点
- mysql2 用のキー名と Trilogy 用のキー名は今後も異なる:
- mysql2:
sslca,sslcapath,sslcert,sslcipher,sslkey - Trilogy:
ssl_ca,ssl_capath,ssl_cert,ssl_cipher,ssl_key,ssl_crl,ssl_crlpath
- mysql2:
- 複数アダプタを切り替えるようなコードで共通 YAML を使っている場合、アダプタごとにキー名を切り替えるか、
database.ymlを分ける・ERB で分岐する、といった工夫が必要です。
- 参考情報 (あれば)
- Trilogy が内部で認識している SSL オプション一覧(
ext/trilogy-ruby/cext.cより):ssl_ca,ssl_capath,ssl_cert,ssl_cipher,ssl_crl,ssl_crlpath,ssl_key,ssl_mode
- mysql2 が
Mysql2::Client#ssl_setで読むオプション:sslkey,sslcert,sslca,sslcapath,sslcipher
- この PR での動作確認:
- MySQL 8.0.46 に対して、
ssl_mode: VERIFY_CA+ssl_caを設定した Trilogy アプリで:- 現行 main: 構造ダンプ (
format: :sql) が 2026 エラーで失敗 - この PR 適用後: 正常に成功することを確認済み
- 現行 main: 構造ダンプ (
- MySQL 8.0.46 に対して、
この PR により、「Rails の接続設定に書いた SSL オプション」が、mysql2 / Trilogy のどちらのアダプタでも、dbconsole や db:structure:dump 実行時の mysql / mysqldump に一貫して反映されるようになっています。
#58726 Filter the database password out of failed database task commands
マージ日: 2026/9/15 | 作成者: @ngan
- 概要 (1-2文で)
Rails の DB タスクが失敗した際に表示されるコマンド文字列から、--password=<値>という形のパスワードを[FILTERED]にマスクする変更です。主に MySQL 系アダプタ(mysql2 / Trilogy)のstructure_dump/structure_load実行失敗時に、CI ログや標準出力へ DB パスワードが漏洩することを防ぎます。
- 変更内容の詳細
どこが変わったか
主な変更点は ActiveRecord::Tasks::AbstractDatabaseTasks#run_cmd_error 周辺です。
従来は、DB タスクが失敗した際に実行したコマンドをそのまま表示していました:
def run_cmd_error(cmd)
msg = +"failed to execute:\n"
msg << cmd.join(" ")
raise ActiveRecord::Tasks::DatabaseError, msg
endこのため、MySQL 系アダプタが mysqldump / mysql 等へ以下のように渡している場合:
mysqldump --user=pat --password=wossname --result-file=structure.sql --no-data ...失敗時ログにも --password=wossname がそのまま出力されていました。
今回の PR では、このエラーメッセージ組み立て時にコマンド引数をフィルタし、--password=<値> をマスクするようにしています。
パスワードフィルタリングの挙動
実装は概ね次のようなイメージです(擬似コード):
def run_cmd_error(cmd)
filtered_cmd = cmd.map { |arg|
arg.sub(/\A(--password=)(.+)\z/, '\1[FILTERED]')
}
msg = +"failed to execute:\n"
msg << filtered_cmd.join(" ")
raise ActiveRecord::Tasks::DatabaseError, msg
endポイント:
- 引数のうち
--password=...にマッチするものだけを対象にし、...の部分を[FILTERED]に置き換える - フィルタ処理は MySQL 用タスク固有ではなく、
AbstractDatabaseTasks側に実装されているため、今後--password=fooを付ける別のアダプタやタスクがあっても同様に保護される - PostgreSQL はパスワードを環境変数
PGPASSWORDで渡しているため、そもそもコマンドラインにはパスワードが出ておらず、この変更の直接的な対象ではない - SQLite はパスワードを扱わないため対象外
テスト
以下の 2 つのアダプタに対して、rake タスク実行失敗時のメッセージにパスワードが含まれないことを確認するテストが追加されています。
activerecord/test/cases/adapters/mysql2/mysql2_rake_test.rbactiverecord/test/cases/adapters/trilogy/trilogy_rake_test.rb
テスト内容のイメージ:
- パスワード付きの
--password=secretを含むコマンドを実行させる - 故意にコマンドを失敗させる
- 例外メッセージに
--password=[FILTERED]が含まれていること - かつ生の
--password=secretが含まれていないこと
- 影響範囲・注意点
影響範囲
- 対象:
rake db:structure:dump/rake db:structure:loadなど、AbstractDatabaseTasks経由で外部コマンドを実行し、かつ--password=形式でパスワードを渡しているタスク- 現時点では主に MySQL 系アダプタ(
mysql2,trilogy)
- 出力:
- Rails が出力するエラーメッセージ(標準出力/標準エラー、CI ログなど)から、DB パスワードが削除されるようになる
セキュリティ面の注意点
- ログ・標準出力に対する保護のみ:
- この PR は「Rails が表示するエラーメッセージ中のパスワード」を隠すだけであり、実際に実行される OS レベルのプロセス引数は変わりません。
- したがって、コマンド実行中に
psなどでプロセス一覧を見れば、従来どおり--password=...は見えてしまいます。 - これは PR の説明にも明示されている通り、別の既知の課題として扱われており、今回の PR では対応範囲外です。
- 破壊的変更の可能性:
- エラーメッセージの文字列が変わるため、「失敗メッセージをパースしてパスワードを取得する」といった(望ましくない)用途でこのメッセージに依存していたコードがあると壊れます。
- 一般的にはエラーメッセージの文言にパース前提で依存するべきではないため、多くのアプリケーションにとっては問題にならないと考えられます。
- デバッグ時の影響:
- DB パスワードが見えなくなるため、ログだけを頼りに「どのパスワードで実行していたのか」を確認することはできなくなります。
- ただしこれはセキュリティ上は望ましい変更であり、必要があれば
database.ymlや環境変数側で確認することが推奨されます。
- 参考情報 (あれば)
- 似た思想の既存機能:
bin/rails dbconsoleは従来から、明示的にinclude_passwordを指定しない限りパスワードをコマンドラインに出さず、-pのみを使用する実装になっています。この PR はそのポリシーを rake ベースの DB タスクのエラーログにも拡張したものと言えます。
- 一般的なベストプラクティス:
- CI 環境やログ収集基盤において、パスワード等の機密情報がログに残らないようにするのはよくあるセキュリティ要件であり、この PR は Rails のデフォルト挙動をその要件に寄せる改善です。
#58714 Fix keepalive: false being ignored in database.yml
マージ日: 2026/9/15 | 作成者: @joemsak
- 概要 (1-2文で)
database.ymlでkeepalive: falseを指定しても無視され、デフォルトの 600 秒が適用されていたバグを修正した PR です。keepaliveの値が「未指定」「false」「0」などの場合の挙動を正しく区別するようにし、その動作をテストで保証しています。
- 変更内容の詳細
問題となっていた挙動
元のコードでは(概略として)次のような処理になっていました:
keepalive = configuration_hash[:keepalive] || 600Ruby では false は偽と評価されるため、
keepaliveキーが存在しない場合keepalive: nilkeepalive: false
のいずれでも、|| 600 により 600 が採用されてしまいます。
その結果、database.yml に以下のように書いても:
production:
adapter: postgresql
database: my_db
keepalive: false「keepalive を無効にしたい」という意図に反して、内部的には 600 秒の keepalive が有効になっていました。
修正内容
PR では、この「キーがない」と「明示的に false が入っている」を区別するために Hash#fetch を使うように変更しています。
イメージとしては、次のようなロジックになります(実際のコードは周辺処理がありますが意図として):
raw_keepalive = configuration_hash.fetch(:keepalive, :__not_set__)
keepalive =
case raw_keepalive
when :__not_set__, nil, true
600 # これまで通りのデフォルト挙動を維持
when 0
0 # 明示的に 0 を指定した場合は keepalive 無効
when false
false # keepalive: false を正しく「無効」として扱う
else
raw_keepalive # 数値など、現在の仕様に沿って利用
endポイント:
fetch(:keepalive, :__not_set__)を用いて、「キーが存在しない」場合を特別なシンボル(番兵値)として扱い、nilやfalseと区別している。nil/trueの場合は従来どおり 600 秒にマッピングしており、既存の挙動を壊さないようにしている。keepalive: 0については、既存のドキュメントどおり「keepalive を無効化する」意味として扱い続けている。keepalive: falseが新たに「本当に無効」として認識されるようになった。
テストの追加
activerecord/test/cases/database_configurations/hash_config_test.rb に以下のケースが追加されています(内容の要約):
keepalive: falseを指定したときに、600 秒にフォールバックせず「無効」(false/0 相当)として扱われること。keepalive: 0を指定したときにも引き続き keepalive が無効化されること。
これにより、今回の意図した挙動が将来の変更で壊れないように保証しています。
- 影響範囲・注意点
- 対象:
database.ymlでkeepaliveを設定している Active Record の利用プロジェクト(主に DB 接続の keepalive 設定を行っている環境)。 - 行動変化:
- これまで:
keepalive: false→ 実際には600秒として扱われていた(バグ)。
- 修正後:
keepalive: false→ 本当に keepalive が無効になる(0同様の扱い、もしくは明確な「オフ」)。
- これまで:
- 既存挙動を維持するもの:
keepalive未指定 → デフォルトの 600 秒。keepalive: nil/keepalive: true→ デフォルトの 600 秒(従来どおり)。keepalive: 0→ keepalive 無効(従来どおり)。
- マイグレーション上の注意:
- 以前のバグを前提に「
keepalive: falseと書いているが、実際には 600 秒で動いている」環境では、この PR 適用後に挙動が変わり、keepalive がオフになります。 - そうしたプロジェクトでは、意図が「keepalive を使いたい」のであれば
keepalive: trueや適切な秒数を明示するようにdatabase.ymlを見直す必要があります。
- 以前のバグを前提に「
- 参考情報 (あれば)
- 対応する Issue: #58713 —
keepalive: falseが無視される問題報告。 - PR: #58714
- 対象コード:
ActiveRecord::DatabaseConfigurations::HashConfig内の keepalive 設定ロジック。 - ドキュメント上の仕様:
keepalive: 0は keepalive 無効化として扱う、という従来ドキュメントと整合しています。
#58716 Bound background-maintenance checkout wait to checkout_timeout
マージ日: 2026/9/15 | 作成者: @joemsak
- 概要 (1-2文で)
Rails のコネクションプールが「バックグラウンドメンテナンス中のコネクション待ち」の際に、プール設定のcheckout_timeoutを無視して最大 100 秒まで待ってしまう不具合を修正し、checkout_timeoutの値で待ち時間を正しく制限するようにした PR です。これにより、メンテナンス中のコネクションによりスレッドが長時間ハングする問題が解消されます。
- 変更内容の詳細
背景となる挙動
ActiveRecord のコネクションプールには、バックグラウンドメンテナンス(例: 接続の再検証や再接続など)中のコネクションを扱うためのロジックがあります。その中で:
try_to_queue_for_background_connection- バックグラウンドメンテナンス中のコネクションがある状況で、他のスレッドが
checkoutしようとしたときにキューに入れて待たせるメソッド - ここが「最大 100 秒待つ」というハードコードされた上限を持っており、プールの
checkout_timeout設定を無視していた
- バックグラウンドメンテナンス中のコネクションがある状況で、他のスレッドが
このため、アプリ側で checkout_timeout = 5 秒などと設定していても、メンテナンス状況によっては最大 100 秒待たされてしまうケースがありました。
今回の修正点
1) 待ち時間の上限を checkout_timeout に合わせる
activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb の try_to_queue_for_background_connection で、
- これまでは「最大 100 秒まで待つ」という固定値ベースの計算だった
- これを「その時点で残っている
checkout_timeoutの範囲内で待つ」ように変更
イメージとしては、以下のようなことをしていると考えればよいです(擬似コード):
def try_to_queue_for_background_connection(...)
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
# 以前: hardcoded 100秒
# max_wait = 100.0
# 今回: プール設定の checkout_timeout を使う
max_wait = @checkout_timeout
# 残り時間を計算しながら wait する
while (elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) < max_wait
remaining = max_wait - elapsed
condvar.wait(mutex, remaining)
# コネクションが使えるようになったかなどをチェック...
end
# タイムアウト扱いにして呼び出し元が ConnectionTimeoutError を投げられるようにする
end実際のコードはもう少し複雑ですが、ポイントは:
- 「メンテナンス中のコネクションのために待つ時間」も、通常の
checkoutと同じcheckout_timeoutの制約下に入る - これにより、アプリ側の timeout 設定が一貫して尊重される
2) 挙動を保証するテストの追加
activerecord/test/cases/connection_pool_test.rb に新しいテストが追加されています。
テストの内容(要約):
- 1 つのコネクションを取得し、それを「バックグラウンドメンテナンス中」とみなしてプールにピン留め(開放されない状態にする)
- その状態で別スレッドから
checkoutを実行 - プールには空きがあるが、対象コネクションはメンテナンス中のため待ちに入る
- 期待する挙動:
- 実際の待ち時間は、プールの
checkout_timeout以内に収まり、 ConnectionTimeoutErrorが発生して戻ってくる- (以前のバグでは、この待ちが最大 100 秒まで伸び得た)
- 実際の待ち時間は、プールの
これにより、「バックグラウンドメンテナンスによる待ちも checkout_timeout で正しく切れる」という仕様がテストで固定されました。
3) 明示的に「触らなかった」設計判断
PR 説明にもある通り、別の論点:
プールにまだ増やせる余地があるときに、キューイングよりも新しいコネクションを開くことを優先すべきか?(#54175)
については今回は触っていません。
既存テスト test_checkout_queues_behind_maintenance_connections が「キューに並ぶ」挙動を意図した設計として保持しており、その方針は維持したまま、「待ち時間の上限だけを直す」というピンポイント修正です。
- 影響範囲・注意点
影響範囲
対象:
- ActiveRecord のコネクションプールを使っており、
- バックグラウンドメンテナンス中のコネクションが発生しうる環境
- かつ
checkout_timeoutを短めに設定しているアプリケーション
期待される変化:
- これまで:
- メンテナンスでブロックされた状況に入ると、
checkout_timeoutを超えて最大 100 秒までスレッドがブロックし続ける可能性があった
- メンテナンスでブロックされた状況に入ると、
- 修正後:
- メンテナンスが終わらない場合、
checkout_timeout経過で早めにConnectionTimeoutErrorが返ってくる - これにより、アプリ側で timeout をトリガーにしたリトライ・ロギング・エラー通知などの制御が行いやすくなる
- メンテナンスが終わらない場合、
- これまで:
注意点
例外発生のタイミングが「アプリの期待どおりの
checkout_timeout通り」に変わることで、- 100 秒以上待たされていたケースが、
- 数秒〜十数秒で例外になりやすくなります
その結果として:
- 以前は「たまたま待っている間に回復して目立たなかった」不調が、
- これからは
ConnectionTimeoutErrorとして顕在化し、ログ・監視に多く見える可能性があります
→ これは設計上「正しい」挙動ですが、監視アラートの閾値などの調整が必要になるかもしれません。
PR では「プールサイズを増やす vs キューに並ぶ」のポリシーは変更していないため、
- プールに余裕があるのに「すぐ新規接続せずに待ちに入る」状況は従来どおり存在します
- その際の「待ち時間上限」だけが正しく
checkout_timeoutに揃えられた、という理解でよいです。
- 参考情報 (あれば)
- 対応 issue:
- #58715 – 本 PR で直した「100 秒ハードコード待ち」の問題
- 関連する設計議論:
- #54175 – 「プールに余裕があるなら新しい接続を優先すべきか?」という、キューイング vs スケーリングのポリシー議論
- 関連テスト:
test_checkout_queues_behind_maintenance_connections
→ 「メンテナンス中コネクションの後ろにキューイングする」という設計を保証する既存テスト- 本 PR 追加のテスト(ファイル:
activerecord/test/cases/connection_pool_test.rb)
→ 「メンテナンス中でブロックされた場合、checkout_timeout内にConnectionTimeoutErrorになる」ことを保証するテスト
#58720 Preserve read_multi key order when local cache is active
マージ日: 2026/9/15 | 作成者: @carlosdanielpohlod
- 概要 (1-2文で)
with_local_cacheブロック内でread_multiを呼び出した際、返却されるハッシュのキー順が引数の順序どおりになるように修正した PR です。fetch_multiでは既に修正済みだった挙動をread_multiにも揃えるバグフィックスです。
- 変更内容の詳細
これまでの問題点
with_local_cache を使っているとき、read_multi は「ローカルキャッシュにヒットしたキー」と「実ストアから取ってきたキー」を単純にマージしていました。その結果、呼び出し時のキーの順序が保たれませんでした。
ActiveSupport::Cache::MemoryStore.new.with_local_cache do |cache|
cache.write("b", 1) # ローカルキャッシュに乗るケース
cache.read_multi("a", "b", "c").keys
# 期待: ["a", "b", "c"]
# 実際: ["b", "a", "c"] のように、ローカルヒットが先頭に来てしまう
end同様の問題は以前 fetch_multi で修正されていましたが (0f231b0f6e)、その修正に相当する処理が read_multi 側(より正確には read_multi_entries)には入っていませんでした。
今回の修正
ActiveSupport::Cache::Strategy::LocalCache#read_multi_entries の戻り値に対して、fetch_multi と同様に「要求されたキー順で並べ替える」処理を追加しています。
ざっくり言うと以下のようなイメージの変更です(擬似コード):
def read_multi_entries(names, **options)
# もともとやっていたこと:
# - ローカルキャッシュからのヒットを result に詰める
# - ミス分を実ストアに read_multi して merge!
result = local_hits.merge(remote_result)
# 変更点: 要求された順序に並べ替える
# (実際のコードでは slice を使っている)
result = result.slice(*names)
result
endHash#slice は「指定されたキーだけを、もともとのハッシュの順番のまま残す」メソッドなので、
- 引数で指定したキー順をそのまま保てる
- 見つからなかったキーは新規に
nilを詰めたりせず、ハッシュから欠落したままになる
という性質を持っています。
read_multi において、存在しないキーについては「キーが存在しない」という形で返す({} または subset を返す)ことが仕様上許されているため、slice による「見つからなかったキーはそもそも含めない」挙動の方が正しい、という判断です。
テストと CHANGELOG
activesupport/test/cache/behaviors/local_cache_behavior.rbにテストが追加され、with_local_cache使用時にread_multiが引数どおりの順序でキーを返すこと- ヒットしなかったキーは結果から欠落すること
が検証されています。
activesupport/CHANGELOG.mdにバグ修正として追記されています。
- 影響範囲・注意点
- 対象:
- ActiveSupport のキャッシュ (
ActiveSupport::Cache::*Store) をwith_local_cacheと併用しつつread_multiを使っているコード。
- ActiveSupport のキャッシュ (
- 影響:
read_multiが返すハッシュの「キーの並び順」が、引数で指定した順序に揃うようになります。- これまで「ローカルキャッシュにヒットしたキーが先頭に並ぶ」ことに暗黙依存していたコードがあれば、その挙動は変わります(ただし、そのような依存は本来想定されていないと考えられます)。
- 見つからないキーについては、従来どおり「キー自体が返ってこない」挙動のままです。
{ "a" => nil }のように埋められることはありません。
- 互換性:
- 順序に依存していない一般的な利用(
result["a"]のようなアクセス)には影響しません。 Hashの順序を使って後続処理をしている場合(例:read_multi(...).eachの順序を期待しているケース)は、今回から「常に引数の順序が保証される」振る舞いになります。with_local_cacheの有無で順序が変わる不一致が解消されるため、全体としては一貫性が向上します。
- 順序に依存していない一般的な利用(
- 参考情報 (あれば)
- 類似修正:
fetch_multiの順序保持対応コミット0f231b0f6e - 関連箇所:
ActiveSupport::Cache::Strategy::LocalCache#read_multi_entriesActiveSupport::Cache::Store#read_multi/#fetch_multi
- Rails のキャッシュ API 仕様として、
read_multiは「要求した数より少ないキーしか返さない」ことがある点に注意が必要です(今回の修正もその仕様に沿っています)。
#58663 LazyAttributeSet: Use a mutable indexed row for casted values
マージ日: 2026/9/15 | 作成者: @byroot
- 概要 (1–2文で)
LazyAttributeSetが属性値のキャスト済み値 (@casted_values) を保持するときに、HashではなくIndexedRowベースの「インデックス付き・可変な行」を使うようにしてメモリ効率とパフォーマンスを改善する変更です。主に Active Record のクエリ結果・関連のプリロード周りでIndexedRowの活用範囲が広がっています。
- 変更内容の詳細
背景: LazyAttributeSet と @casted_values / @values
LazyAttributeSetは Active Model/Active Record の属性値を「必要になるまでキャストしない」ための仕組みです。- 内部的には:
@values… DB から来た「生値」(文字列など)@casted_values… 型キャスト済みの値 (Integer, Time など)
@casted_valuesは初期状態では空ですが、時間が経つと最終的には@valuesと同じキー集合を持つのが前提の設計です。
従来は、@values が IndexedRow(カラム名 → インデックスの対応を持つ軽量構造)であっても、@casted_values は Hash で管理していました。
この PR では、「インデックスが同じである」という性質を利用して、@casted_values にも IndexedRow 系の構造を用いるよう最適化しています。
IndexedRow の拡張・導入内容
activemodel/lib/active_model/indexed_row.rb が大きく追加されており(+198行)、次のような機能が含まれていると考えられます:
- カラム名をキーにしたインデックスアクセス(例:
row[:id],row["name"]) - インデックスベースの配列に近い内部表現(
@values[index]のようなイメージ) - 「可変(mutable)」な行として、要素の更新・設定を許可
row[:id] = 1- 必要ならば
dupやcloneをサポート
- ActiveModel/ActiveRecord のインターフェイスに合わせた各種メソッド (
each,keys,values_at, など)
これにより、「同じカラム配列」を共有する複数の行に対して、Hash よりもメモリ効率の良い配列ベースのストレージを使えるようになります。
LazyAttributeSet での利用
PR 説明から読み取れる主な変更点:
@valuesがすでにIndexedRowの場合:@casted_valuesも同じインデックス配列を共有するIndexedRowを利用- 各属性のキャスト済み値を、インデックスを元に配列スロットに格納していく
@valuesがHashの場合:- まだ完全には
IndexedRow化されておらず、従来どおりHashベースで動くコードパスが残存 - この PR ではそこを無理に置き換えず、「可能なパスのみ最適化を有効化」という段階的な変更
- まだ完全には
Active Record 周辺の変更
activerecord/lib/active_record/result.rb (+2/-64)
ActiveRecord::Result は SELECT クエリの結果を保持するクラスで、各行を表現する内部構造が IndexedRow ベースに整理されています。
- 以前は:
- 行データに
Hashを使うコードや、Hashに変換する中間処理が多かった
- 行データに
- 変更後:
- 行の内部表現に
IndexedRowを直接利用 - 余計な
Hash生成やキー探索を削減 to_aなど「結果を配列のHashにする」インターフェイスはそのまま維持しつつ、内部はより効率化
- 行の内部表現に
activerecord/lib/active_record/associations/join_dependency.rb (+27/-11)
activerecord/lib/active_record/associations/join_dependency/join_part.rb (+2/-19)
JOINを含むクエリの実行結果から、関連オブジェクトを組み立てる処理 (JoinDependency) もIndexedRow前提で調整されています。- 各 join part が結果行のどのインデックスに対応するのかを、より直接的に扱えるような形へ変更(キー検索よりインデックスベースのアクセスを重視)。
- これにより、複雑な
includes/eager_load/preloadなどのクエリ処理でのオーバーヘッドを削減。
activesupport/lib/active_support/core_ext/enumerable.rb (+2/-6)
IndexedRow を使う列挙処理で不整合が出ないよう、Enumerable 拡張メソッド(index_by など)との互換性が確保されています。IndexedRow が Enumerable として期待通り動くように、あるいは Enumerable 側で IndexedRow をうまく扱えるように微調整している変更です。
- 影響範囲・注意点
影響範囲
- ActiveRecord のクエリ結果 (
ActiveRecord::Result) の内部表現 LazyAttributeSet(モデル属性の怠惰キャスト)の内部構造- 関連 (
has_many,belongs_toなど) の eager loading / join 処理 (JoinDependency) IndexedRowに対してEnumerable系メソッドを呼ぶコード
いずれも「内部表現の最適化」であり、通常のアプリケーションコードが利用する公開 API(User.first, user.name, user.attributes など)は、基本的には互換です。
注意点・互換性
IndexedRowを直接いじっている独自コードがある場合:- 新しいメソッド追加や振る舞いの変化があるかもしれないので、テストを確認する必要があります。
LazyAttributeSet/ActiveRecord::Resultの内部構造に依存したメタプログラミングや monkey patch をしている場合:Hash前提 (@valuesや@casted_valuesをHashと決め打ち) のコードは壊れる可能性があります。
- 一部のコードパスでは依然として
HashベースのLazyAttributeSetが生成される:- 「全てが即座に
IndexedRow化された」わけではないため、内部実装に強く依存した最適化・拡張は要注意です。
- 「全てが即座に
- 参考情報 (あれば)
- 元 PR: #58663 – LazyAttributeSet: Use a mutable indexed row for casted values
- 前提となる PR: #51744 – (先行して IndexedRow 等を導入した PR)
まとめると、この PR は Active Record/Model の「属性値の保持とキャスト」を、Hash ベースから IndexedRow ベースへ段階的に移行する一環であり、特に大量レコードや複雑な JOIN を扱うケースでのメモリ効率・速度向上が期待される変更です。
#58740 Fix docker-entrypoint to detect bin/rails server behind Thruster too
マージ日: 2026/9/15 | 作成者: @iamdipak39p
- 概要 (1-2文で)
Rails が生成する Docker 用エントリポイントスクリプト(bin/docker-entrypoint)で、bin/rails server実行時にdb:prepareを自動実行する判定ロジックを修正し、Thruster 経由 (./bin/thrust ./bin/rails server) でも正しく動くようになりました。
引数の「先頭2つ」または「末尾2つ」の固定位置チェックをやめ、引数全体から./bin/railsとserverの組み合わせを検出する方式に変更しています。
- 変更内容の詳細
何が問題だったか
- 元々の目的:
Docker / Podman などでrails serverを起動するときにbin/docker-entrypoint内でbin/rails db:prepareを自動実行したい。 - #58675 での修正:
bin/rails server -b 0.0.0.0のように追加フラグが付くとdb:prepareがスキップされる問題を、「末尾2引数」チェック → 「先頭2引数」チェックに変えることで回避していた。 - 新たに発生した問題:
Rails 8+ のデフォルトでは Thruster が入っており、Dockerfile の CMD はjsonとなるため、「先頭2引数が["./bin/thrust", "./bin/rails", "server"]./bin/rails server」という前提が崩れ、db:prepareが再び実行されなくなっていた。
今回の解決策
固定位置のチェックをやめ、引数配列全体を走査して
「./bin/rails に続いて server が来る位置がどこかに存在するか」
を調べるように変更されています。
(実際のテンプレートは ERB ですが、ロジックのイメージは以下のような Bash になります)
prepare_db=false
args=("$@")
for ((i=0; i<${#args[@]}-1; i++)); do
if [ "${args[$i]}" = "./bin/rails" ] && [ "${args[$((i+1))]}" = "server" ]; then
prepare_db=true
break
fi
done
if [ "$prepare_db" = true ]; then
./bin/rails db:prepare
fi
exec "$@"これにより、以下の全てで期待どおり db:prepare が実行されます:
- 素の Rails サーバ:bash
./bin/docker-entrypoint ./bin/rails server - フラグ付き:bash
./bin/docker-entrypoint ./bin/rails server -b 0.0.0.0 - Thruster 経由:bash
./bin/docker-entrypoint ./bin/thrust ./bin/rails server - Thruster + フラグ:bash
./bin/docker-entrypoint ./bin/thrust ./bin/rails server -b 0.0.0.0
逆に、./bin/rails runner や Rake タスクなど、server ではないコマンドでは db:prepare は走らないままです。
テストの改善
これまでのテストは、docker-entrypoint テンプレートの「ソースコード文字列のパターンマッチ」に依存していましたが、それでは実際の挙動までは保証できませんでした。
PR ではテストを次のように変更しています:
- 実際にアプリを生成し、その
bin/docker-entrypointを実行 - 以下のケースをカバー:
./bin/rails server./bin/rails server -b 0.0.0.0./bin/thrust ./bin/rails server./bin/thrust ./bin/rails server -b 0.0.0.0- ネガティブケース(
db:prepareが走らないコマンド)
これにより、テンプレートの見た目ではなく実際のシェルスクリプトとしての挙動が検証されるようになっています。
- 影響範囲・注意点
- 対象:
- この変更は「新規生成される Rails アプリの
bin/docker-entrypointテンプレート」に対するものであり、既存アプリは generator を再実行しない限り自動では変わりません。
- この変更は「新規生成される Rails アプリの
- 期待される挙動:
- Thruster 有無や追加フラグの有無に関わらず、
./bin/rails serverを実行するコンテナ起動ではdb:prepareが呼ばれます。 server以外のサブコマンドではdb:prepareは呼ばれません。
- Thruster 有無や追加フラグの有無に関わらず、
- 既存プロジェクトでの対応:
- Rails 8+ のアプリで Thruster + Docker を使っており、「コンテナ起動時に
db:prepareが動いていない」と感じている場合は、この PR と同等のロジックをbin/docker-entrypointに手動で反映することで改善できます。
- Rails 8+ のアプリで Thruster + Docker を使っており、「コンテナ起動時に
- 互換性:
- 先頭固定のチェックから、よりゆるやかな「どこに出てきても OK」なチェックに変わっただけなので、従来動いていたパターンも維持されます。
- コマンドラインに
./bin/rails serverが含まれさえすれば、前後に何が付いていても検出されます(ただし、./bin/railsとserverが非連続に分かれているケースは対象外)。
- 参考情報 (あれば)
- 元となった PR: #58675 —
db:prepareがフラグ付きサーバ起動でスキップされる問題の修正 - この PR が修正するケース: Rails 8+ でデフォルト有効な Thruster 経由のサーバ起動
- 関連ファイル:
railties/lib/rails/generators/rails/app/templates/docker-entrypoint.ttrailties/test/generators/app_generator_test.rb
#58727 Fix the debug error page for SyntaxErrors with multi-line messages
マージ日: 2026/9/15 | 作成者: @marcoroth
- 概要 (1-2文で)
開発環境のデバッグエラーページが、複数行メッセージを持つSyntaxErrorで壊れて 500 公開エラーページにフォールバックしてしまう不具合を修正したPRです。SyntaxErrorメッセージのうち実際のスタックトレース行だけを抽出するようにし、かつエラーページでのソースコード抽出時に「ディレクトリをファイルとして読む」ケースを防いでいます。
- 変更内容の詳細
背景となる挙動
- Rails のデバッグエラーページでは
ActiveSupport::SyntaxErrorProxyを使って、SyntaxErrorのメッセージ中に含まれる「path:line: message」形式の行を疑似的な backtrace として解釈し、ActionDispatch::ExceptionWrapper側でソースコードの該当行を抽出して表示しています。 - しかし SyntaxError メッセージが複数行かつ、その中に「純粋な説明テキスト行(prose)」が混ざる場合、それらもすべて「トレース行」として扱われてしまっていました。
- その結果:
- prose 行が
ActionDispatch::ExceptionWrapperに渡される Rails.rootと結合されてパス解決される- それが「ディレクトリ名」だった場合にも
File.exist?が true を返す - そのまま
File.read相当の処理を行おうとしてErrno::EISDIR(ディレクトリをファイルとして読もうとした)を発生 - エラーページ描画中に例外が再度発生するため、開発用デバッグページではなく、通常の public 500 ページが返ってしまう
- prose 行が
具体的な修正点
1. SyntaxError メッセージのパースを厳密化
対象ファイル:
activesupport/lib/active_support/syntax_error_proxy.rb(+2 / -1)
SyntaxErrorProxy#parse_message_for_trace が、メッセージ中の「どの行をトレースエントリと見なすか」という判定を変更しています。
以前:
- メッセージの「すべての行」を backtrace エントリとして扱っていた。
今回:
- 「
:<line>という行番号情報を含む行のみ」をトレースエントリとして扱うように変更。- 例:
app/views/users/index.html.erb:12: syntax error, unexpected endのような行。
- 例:
これにより、説明文だけの行(例: 「There were multiple syntax errors in your template:」のような行)は backtrace にならず、ExceptionWrapper まで流れなくなります。
2. ソース抽出時のファイル存在チェックを厳格化
対象ファイル:
actionpack/lib/action_dispatch/middleware/exception_wrapper.rb(+1 / -1)
ExceptionWrapper#source_fragment(エラー行周辺のソースコードを表示する部分)で、対象パスが本当に「ファイルかどうか」を確認するよう変更されています。
変更前:
if File.exist?(path)
# ソース読み取り処理
endこれだと「ディレクトリ」でも true になるため、その後の読み取り処理で Errno::EISDIR が発生することがありました。
変更後:
if File.file?(path)
# ソース読み取り処理
endFile.file? によって「通常ファイル」だけが対象になるため、ディレクトリを誤って読みに行くことがなくなります。
仮に SyntaxErrorProxy 側で何か取りこぼしがあっても、ここでディレクトリは弾かれます。
3. テスト追加
対象ファイル:
actionpack/test/dispatch/exception_wrapper_test.rb(+33 / 0)
バグ再現ケースをカバーするテストが追加されています。
主に次のようなパターンを検証していると考えられます(要旨):
- メッセージ中に複数行の SyntaxError が含まれるケース
- そのうち、行番号付きの行だけがトレースとして扱われること
- エラーページ生成時に
Errno::EISDIRのような新たな例外が発生しないこと
- 影響範囲・注意点
- 対象環境:
- Rails の開発環境(development)のデバッグエラーページ表示時。
- 特に、テンプレートエンジン(例: Herb)などが複数のテンプレートエラーを 1 つの
SyntaxError(かそのサブクラス)にまとめて、複数行メッセージで報告するケースに直接効いてきます。
- 利用者目線での挙動変化:
- これまで「テンプレートのSyntaxErrorが起きるとデバッグページが真っ白になって500ページになる」といった症状が出ていた場合、本PRにより正しくデバッグエラーページが表示されるようになります。
- 互換性:
SyntaxErrorメッセージのうち、:<line>形式を含まない行はもはやスタックトレースとして扱われません。そのため、- これまで「無理矢理 prose 行をトレースとして利用していた」ようなコードがあると挙動が変わる可能性はありますが、通常は想定されない使い方です。
ExceptionWrapper#source_fragmentがディレクトリに対してソース抽出を試みなくなるのは、期待される挙動により近いため、問題を生む可能性は低いと考えられます。
- パフォーマンス・セキュリティ:
- 変更は条件分岐の改善レベルで、パフォーマンス影響は無視できる程度です。
- ディレクトリをファイルとして読む試行がなくなるため、エラー時の挙動がより安定・安全になります。
- 参考情報 (あれば)
- このPRは
SyntaxErrorメッセージを「構造化されていないテキスト」として無理矢理パースしている現状への、部分的な対処になっています。- 説明文中でも触れられている通り、特にビューのコンパイル時エラーに対しては、マルチラインメッセージではなく「構造化されたエラー情報(複数箇所の位置情報など)」を渡せる仕組みがあれば、より堅牢な実装にできる余地があります。
- 関連PR:
- 作成者は #58721 の作業中に今回の問題に遭遇したと記載しており、ビュー/テンプレート周りのエラー処理改善の一環として出てきた修正と思われます。
#58764 Make ActiveJob Ractor-safe
マージ日: 2026/9/15 | 作成者: @etiennebarrie
- 概要 (1-2文で)
このPRは、Active Job を Ractor(Ruby 3 の並列実行機構)内から安全に enqueue できるようにするための対応です。主にクラスレベルの設定の持ち方を Ractor 共有可能な形に改め、ractorize!実行時に queue adapter が shareable になるようにしています。
- 変更内容の詳細
2-1. クラスレベル設定の扱い方の変更
目的:cattr_accessor / mattr_accessor / 一部の class_attribute は内部的に Ractor 非共有なオブジェクトや書き込み可能な構造を持つことがあり、そのままでは Ractor 間で共有できません。
→ Active Job の設定や内部状態を、Ractor の「shareable」要件を満たす形で保持するように変更しています。
ActiveJob::Logging (activejob/lib/active_job/logging.rb)
- ロギング関連の設定を、モジュールのシングルトンクラス上のインスタンス変数で持つように変更。
- これにより、Ractor 間で共有されるデフォルト設定が Ractor-safe になります。
イメージとしては、次のような方向性です(擬似コード):
module ActiveJob
module Logging
class << self
attr_accessor :logger # ここに shareable な logger を置く
end
end
end以前は cattr_accessor / mattr_accessor でクラス変数を共有していた箇所を、
- 「全クラスで共有するだけでよい設定」はモジュールシングルトンのインスタンス変数へ
- 「サブクラスごとにオーバーライド可能であるべき設定」は
class_attributeを維持しつつ、「デフォルト値」を shareable に
という整理にしています。
ActiveJob::QueueName (activejob/lib/active_job/queue_name.rb)
- queue 名の prefix/suffix や命名規則まわりのクラスレベル設定の持ち方を変更。
class_attributeは残しつつも、デフォルト値が Ractor 共有可能になるように定義されています。
たとえば、MyJob.queue_name = "low" のようにサブクラスごとに queue 名を変更できる API は維持しながら、
その「初期値」や内部の共有データ構造が Ractor-safe になるようにされています。
ActiveJob::QueuePriority (activejob/lib/active_job/queue_priority.rb)
- queue の優先度関連のクラスレベル設定についても同様に整理。
priorityのデフォルトや、priority 付与のロジックで利用されるデータ構造が shareable になるように変更。
2-2. Ractor 対応のための ractorize! の強化
Rails アプリケーション側 (railties/lib/rails/application.rb)
ractorize!メソッド内で すべての Active Job queue adapter を Ractor shareable にする 処理を追加。- 例: Sidekiq, Async, Inline などの adapter インスタンス / 設定を「freeze する」あるいは「shareable な形」に揃える処理を行っている想定です。
これにより、以下のように Ractor 内から MyJob.perform_later を実行しても安全に動作することを目指しています。
Rails.application.ractorize!
r = Ractor.new do
MyJob.perform_later(123)
end
r.take- ポイントは「enqueue 側」の Ractor 対応であり、ジョブ実行側が Ractor 内で動くこととは別問題です。
- このPRでは「enqueue できるようにする」ことにフォーカスしています。
2-3. テストの追加・更新
Active Job 関連テスト
activejob/test/cases/logging_test.rbactivejob/test/cases/queue_naming_test.rbactivejob/test/cases/queue_priority_test.rb
で、クラスレベル設定の API が既存どおり利用可能か、かつ Ractor を意識した初期化・共有の仕方が正しいかを確認するテストが追加されています。
Rails Ractor テスト (railties/test/application/ractors_test.rb)
Rails.application.ractorize!呼び出し後に、Active Job の queue adapter が問題なく Ractor から利用できることを確認するテストを追加。- 具体的には、Ractor 内からジョブを enqueue してエラーにならないことを確認しているようなテストです。
- 影響範囲・注意点
Active Job / Rails のRactor対応を使わない既存アプリ
- 公開API (
perform_later, queue 名のカスタマイズ、priority など) は変更されていないため、通常は互換性問題は少ないと考えられます。 - ただし、内部的に
ActiveJob::LoggingやActiveJob::QueueName等の「クラス変数」を直接参照しているようなメタプログラミングをしていた場合は挙動が変わる可能性があります。
- 公開API (
クラスレベル設定まわりの挙動
- 「サブクラスごとに別の設定を持てるか」「親クラスのデフォルトを変更すると子クラスにどう作用するか」といった
class_attribute特有の継承挙動は維持されています。 - ただし、デフォルト値が shareable になるよう整理されているため、デフォルト値に対して破壊的変更を加えるようなコード(例:
SomeJob.queue_name_prefix << "_extra"のようにミュータブルなオブジェクトを直接変更)は、Ractor利用下では望ましくありません。- その場合は新しいオブジェクトを再代入する形(
SomeJob.queue_name_prefix = SomeJob.queue_name_prefix + "_extra")にするほうが安全です。
- その場合は新しいオブジェクトを再代入する形(
- 「サブクラスごとに別の設定を持てるか」「親クラスのデフォルトを変更すると子クラスにどう作用するか」といった
Ractor を使う場合の前提
Rails.application.ractorize!を呼び出してから Ractor を起動し、その中で Active Job を扱う設計が前提になります。- アプリ独自の queue adapter や middleware を定義している場合、それらのオブジェクトも Ractor shareable になっているか(freezeされているか / Ractor 共有要件を満たしているか)を確認する必要があります。
- 参考情報 (あれば)
- Ruby 3 Ractor の shareable 要件
Ractor.shareable?でオブジェクトが共有可能か判定可能。- 共有する構造は基本的に
freezeされている必要がある(ただし一部例外あり)。
- Active Job のクラスレベル設定 API
queue_as,self.queue_name=,queue_adapter=,queue_priority=など- これらは引き続き利用できますが、Ractor対応を意識するなら「デフォルト値をミュータブルなまま共有しない」設計がより重要になります。
- Ractor 対応中の Rails 全体の流れ
ractorize!は Rails 7以降で少しずつ整備されてきた Ractor 対応の一環で、このPRは「Active Job enqueue 周り」をその流れに揃えるものと言えます。
#58765 [ci skip] Correct the Time.rfc3339 documentation example
マージ日: 2026/9/15 | 作成者: @dersam
概要 (1-2文で)
Time.rfc3339 のドキュメントに記載されていた使用例が、実際の戻り値と一致していなかったため、それを正しい例に修正した PR です。コードの挙動そのものは変わらず、コメント/ドキュメントの整合性を取るだけの変更です。変更内容の詳細
- 対象ファイル:
activesupport/lib/active_support/core_ext/time/calculations.rb - 変更内容:
Time.rfc3339メソッドのドキュメントコメント内にあるサンプルコードの出力例を 1 行だけ修正。
PR 説明にある通り、実際には:
Time.rfc3339('1999-12-31T14:00:00-10:00')
# => 1999-12-31 14:00:00 -1000という結果になりますが、ドキュメント内のサンプルがこの実際の出力と食い違っていました。
今回の修正では、この「入力した RFC3339 文字列に対応する Time オブジェクトの出力表示」を、実行結果と一致する形に直しています。
実装ロジック(パース処理やタイムゾーン計算)はまったく変更されていません。あくまで「コメント上の例示の戻り値」を 1 行書き換えただけです。
- 影響範囲・注意点
- 影響範囲:
- ランタイムの挙動(Time.rfc3339 の動き)には一切変更なし。
- ドキュメント生成(RDoc / API ドキュメント閲覧時)の表示が正しくなるだけです。
- 注意点:
- これまで古いドキュメント例を見てテストや他のドキュメントを書いていた場合、その例が実際の挙動と食い違っている可能性があります。
→ 自前ドキュメントや README などに Time.rfc3339 の出力例をコピペしている場合は、実行結果と合っているかを確認するとよいです。
- これまで古いドキュメント例を見てテストや他のドキュメントを書いていた場合、その例が実際の挙動と食い違っている可能性があります。
- 参考情報 (あれば)
- Ruby 標準ライブラリの Time と RFC3339:
Time.rfc3339(str)は RFC3339 形式 (YYYY-MM-DDTHH:MM:SS±HH:MMなど) の文字列を Time オブジェクトに変換するユーティリティです。- 例:
Time.rfc3339("2024-01-01T00:00:00Z") #=> 2024-01-01 00:00:00 UTC
- Rails 側では ActiveSupport が Time 拡張を提供していますが、この PR はその拡張ロジックではなく、あくまでその「説明用コメント」の修正のみです。
#58759 Enable Layout/BlockAlignment
マージ日: 2026/9/13 | 作成者: @kamipo
- 概要 (1-2文で)
Layout/BlockAlignmentCop を RuboCop 設定で有効化し、それにより検出されるブロックのインデントずれを Rails 本体・テストコード・ガイド内で一括修正した PR です。
従来はLayout/IndentationWidthでは検知できなかった「ブロック開始行に対する相対インデントのずれ」を検出・是正できるようにしています。
- 変更内容の詳細
2-1. RuboCop 設定の変更
.rubocop.yml に以下のような形で Layout/BlockAlignment Cop が有効化されています(厳密なオプションまではログから分かりませんが、趣旨としては「Rails コードベース全体でブロックのインデント整合性をチェックする」方向です)。
Layout/BlockAlignment:
Enabled: true
# (必要に応じて EnforcedStyle: start_of_line などが設定されている可能性あり)説明文にある通り:
Layout/IndentationWidthmeasures a block body against itsendrather than the line that opens the block...
従来の Layout/IndentationWidth は「end に対して何段下がっているか」しか見ないため、例えば以下のような「ブロック開始行ごと全体がずれている」ケースを検出できません。
# NG 例(従来の Layout/IndentationWidth では検出されないケース)
users.each do |user|
do_something(user)
enddo / end の相対関係としてはインデントは合っているため、IndentationWidth 的には問題なしと判定されます。しかし本来は users.each do |user| そのものが不要にインデントされているため、Layout/BlockAlignment を有効にして「ブロック開始位置に対する整合性」をチェックすることで、この種のミスインデントを検出します。
2-2. 各ファイルでのインデント修正
有効化に伴い、既存コードで Layout/BlockAlignment に違反していた箇所が、ほぼすべて「インデント修正のみ」で整形されています。主な対象:
actionpack/test/controller/routing_test.rbactionview/lib/action_view/helpers/form_helper.rbactionview/test/template/form_options_helper_test.rbactivemodel/test/cases/validations/comparison_validation_test.rbactiverecord/test/...(MySQL2 / Trilogy の rake テスト、association callbacks テスト、SQLite 用の schema、サポートツール)activestorage/test/log_subscriber_test.rbactivesupport/test/error_reporter_test.rbguides/source/testing.md(ガイド内のコードサンプル)railties/test/...(multi_dbs 関連 rake テスト、Action Text ジェネレーターテスト)
それぞれで、例えば:
# 修正前(イメージ)
assert_difference "User.count", 1 do
create_user
end
# 修正後(イメージ)
assert_difference "User.count", 1 do
create_user
endのように、メソッド呼び出し・ブロック開始行・ブロック内部・end の位置関係が、Rubocop の規約に沿った「左寄せ + 2スペース / 2スペース相対インデント」などに揃えられています。
Ruby の構文やロジックそのものは変えておらず、追加・削除行数(+92/-88)も、ほぼ空白・インデントの差分と考えられます。
- 影響範囲・注意点
ランタイム挙動には影響なし
すべてレイアウト(インデント)変更のみであり、実行コードの意味・テスト内容・テストカバレッジに論理的な変更はありません。今後のコントリビュート時のスタイルチェック
Layout/BlockAlignmentが有効になったことで、以下のような「ブロック開始行の位置がずれている」コードは CI の RuboCop で弾かれるようになります:rubysome_scope do do_something endRails 本体にパッチを送る場合や、この
.rubocop.ymlを参考に自プロジェクトに適用する場合は、エディタやフォーマッタの設定を RuboCop に合わせておく必要があります。既存プロジェクトへの適用時の注意
同じ Cop を自前の Rails アプリで有効化すると、これまで通っていたスタイルチェックが突然大量にエラーを出す可能性があります。
段階的導入をする場合は:- まず既存コードを一括自動修正(
rubocop -A) - CI で新規/変更行にのみ RuboCop をかける運用
などを検討するとよいです。
- まず既存コードを一括自動修正(
- 参考情報 (あれば)
- 関連コミット: PR 説明にある
21c926cbca6d96c33db26b17b68e9e5f3c9553c1のフォローアップとして、この PR が出されています。このコミットで修正しきれなかった、あるいは検出できなかったミスインデントを補完している位置づけです。 - RuboCop ドキュメント(
Layout/BlockAlignment)- https://docs.rubocop.org/rubocop/cops_layout.html#layoutblockalignment
ここに Cop の詳細仕様(EnforcedStyleなど)が記載されているため、同様のスタイルを自プロジェクトで徹底したい場合の参考になります。
- https://docs.rubocop.org/rubocop/cops_layout.html#layoutblockalignment
#58377 Introduce config.active_storage.draw_direct_upload_route
マージ日: 2026/9/13 | 作成者: @codergeek121
- 概要 (1-2文で)
Rails の Active Storage にconfig.active_storage.draw_direct_upload_routeという新しい設定が追加され、Active Storage 自体は有効のまま「直接アップロード用のルート(/rails/active_storage/direct_uploads)」だけを無効化できるようになりました。これに合わせて Action Text 側の JavaScript・ヘルパーもこの設定を尊重するように更新され、ガイドやテストが整備されています。
- 変更内容の詳細
新しい設定オプション
Active Storage に次の設定が追加されています:
# config/application.rb など
config.active_storage.draw_direct_upload_route = true # デフォルトtrue(既定値): 従来どおりdirect_uploadsのルートを生成するfalse: Active Storage の他のルート(blobs, representations, variants など)は残したまま、直接アップロード用のルートだけを生成しない
変更箇所としては:
activestorage/lib/active_storage/engine.rbactivestorage/config/routes.rb
でこの設定が参照され、ルーティング定義に条件分岐が入っています。
(イメージ):
# activestorage/config/routes.rb(概念的なイメージ)
if Rails.application.config.active_storage.draw_direct_upload_route
direct_uploads # ここで direct_uploads のルートを定義
end
# その他の Active Storage ルートはそのまま
# blobs, disk, representations, variants などこれにより、config.active_storage.routes_prefix ごと全部オフにするのではなく、直接アップロード機能だけピンポイントに無効化できます。
Action Text 側の対応
Action Text の JavaScript およびタグヘルパーが、この新しい設定を考慮するよう修正されています。
主な変更ファイル:
actiontext/app/assets/javascripts/actiontext.jsactiontext/app/assets/javascripts/actiontext.esm.jsactiontext/app/javascript/actiontext/index.jsactiontext/app/helpers/action_text/tag_helper.rb
想定される対応内容:
- Trix のエディタ内で画像などのファイルをドラッグ&ドロップ/貼り付けしたとき、
- 直接アップロードルートが有効 (
draw_direct_upload_route = true) なら、これまで通りdirect_uploadsエンドポイントに対して XHR でアップロード - 無効 (
false) の場合は、そのルートを前提とした動きをしない(あるいは適切にフォールバック)
- 直接アップロードルートが有効 (
ActionText::TagHelper 内では、おそらくフォームヘルパーが hidden フィールドや direct upload 用の属性を埋め込む際に、設定をチェックするようになっています。例としては次のような感じの分岐が追加されていると考えられます:
if Rails.application.config.active_storage.draw_direct_upload_route
# direct upload 用の data-属性やエンドポイント URL を埋め込む
else
# direct upload に依存しない描画、あるいは何もしない
endテストとドキュメント
activestorage/CHANGELOG.md
新しい設定についてエントリが追加。guides/source/configuring.md
Active Storage の設定セクションにconfig.active_storage.draw_direct_upload_routeの記述が追加され、使い方や意図(攻撃面積の縮小など)が説明されています。テスト関連:
railties/test/application/configuration_test.rb:
設定のデフォルト値や設定反映のテストが追加。actiontext/test/system/trix_editor_test.rb,actiontext/test/template/form_helper_test.rb,actiontext/test/test_helper.rb:
direct upload ルート ON/OFF の挙動を確認するシステムテスト・ヘルパーテストが追加。actiontext/test/dummy/app/views/messages/_form.html.erb:
テスト用フォームが新挙動に沿うよう更新。
- 影響範囲・注意点
影響範囲
- Active Storage を使っている全アプリに新しい設定項目が増えますが、デフォルトは
trueなので、何もしなければ互換性は維持されます。 - Action Text + Trix で画像などのファイルアップロードをしているアプリ:
draw_direct_upload_route = falseにした場合、従来の direct upload のフローはそのままでは動かなくなります。- 代替手段(例えばコントローラ経由のアップロードやカスタムエンドポイント)など、自前のアップロード処理を用意する必要があります。
注意点
設定を false にすると direct upload URL が 404 になる
例:/rails/active_storage/direct_uploadsが定義されなくなるため、フロントエンド側でこのエンドポイントを前提にしている場合はエラーになります。
→ Action Text(Trix)の「画像ドラッグ&ドロップで即時アップロード」なども影響を受けます。「Active Storage 全体を無効にする」設定とは別物
- 既存の
config.active_storage.routes_prefixやエンジンマウントの有無とは別に、direct uploads のみを切るためのスイッチです。 - 既存のアップロード済みファイルの配信(
/rails/active_storage/blobs/...など)は引き続き利用可能です。
- 既存の
セキュリティ・攻撃面積の観点
- 外部から叩けるエンドポイントを一つ減らせるため、環境によってはセキュリティポリシーに沿いやすくなります。
- ただし、クライアントからのファイルアップロードの要件がある場合は、何らかのルートは必要なので、自前の実装を用意して運用・監査することになります。
- 参考情報 (あれば)
- この PR で解決している Issue: #58369
→ 「Active Storage のルートは使いたいが、direct upload エンドポイントだけをオフにしたい」という要望に対応。 - 追加設定(ガイドに記載):ruby
# config/environments/production.rb などでの例 Rails.application.configure do # 直接アップロードのルートを定義しない config.active_storage.draw_direct_upload_route = false end - 関連ドキュメント:
guides/source/configuring.mdの Active Storage セクション(draw_direct_upload_routeの説明が追加済み)
#58555 Fix PostgreSQL primary key introspection for covering indexes
マージ日: 2026/9/13 | 作成者: @amaksimo
- 概要 (1-2文で)
PostgreSQLで「カバリングインデックス付きの主キー」がある場合に、Active Record が主キー列を誤って認識してしまう不具合を修正するPRです。pg_index.indkeyではなく、主キー制約の正しい列リストであるpg_constraint.conkeyを用いることで、主キー列とINCLUDEされた列を区別できるようにしています。
- 変更内容の詳細
何が問題だったか
PostgreSQLでは、主キーインデックスに対して以下のように「INCLUDE句」で非キー列を含めることができます(カバリングインデックス):
CREATE TABLE users (
tenant_id bigint,
id bigint,
name text,
PRIMARY KEY (tenant_id, id) INCLUDE (name)
);このとき:
- 真の主キー列:
tenant_id,id - インデックスには含まれるが主キーではない列:
name(INCLUDE)
Rails(Active Record)はこれまで主キー列の introspection(メタデータからの取得)に pg_index.indkey を使っており、このカラムには「キー列 + INCLUDE列」が一緒に入っています。そのため、name まで主キー構成要素と誤認し、以下のような問題が起きていました:
- Bulk upsert(
insert_all,upsert_allなど)の内部ロジックが、
「主キー列は更新しない = 読み取り専用」と扱う前提で動く - しかしINCLUDE列まで主キーだと誤認 → 本来更新されるべき列を「書き換え不可」と見なして update から除外
- 特に Aurora DSQL ではデフォルトで主キーが covering index になるため、問題が再現しやすい
何をどう直したか
PRでは、主キー列の取得元を pg_index.indkey から pg_constraint.conkey に変更しています。
pg_constraint.conkey- 主キー制約に紐づく「正しい順序付きの主キー列IDの配列」
- INCLUDE列は含まれない
- 変更後は、この
conkeyを用いて主キー列を決定するようにしつつ、- 既存の「バッチでメタデータを読む最適化」
- search_path(スキーマ検索パス)考慮 を維持しています。
テストとして、複合主キー + INCLUDE列を持つテーブルを使った回帰テストが追加されています。
- 修正前: introspection結果:
["tenant_id", "id", "name"] - 修正後: introspection結果:
["tenant_id", "id"]
この差が、そのままバグ修正の要点です。
変更ファイル概要
activerecord/CHANGELOG.md- PostgreSQLアダプタの挙動変更として、CHANGELOGを追記
activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb- 主キー列取得クエリを
pg_constraint.conkeyベースに変更(+6 / -6)
- 主キー列取得クエリを
activerecord/test/cases/adapters/postgresql/schema_test.rb- カバリング複合主キーの回帰テストを追加(+15)
PostgreSQLスキーマ関連テストは 85 runs, 318 assertions ですべて成功。
- 影響範囲・注意点
対象:
- PostgreSQLを使うRailsアプリ
- 特に
INCLUDE付き主キーインデックス(カバリング primary key)を持つテーブル - Aurora DSQL利用時はデフォルト設定で影響を強く受ける可能性が高い
挙動の変化:
primary_keyや schema introspection を通じて「主キー列」と認識されるカラム集合が、より正確になり、INCLUDE列は除外されるようになります。- これにより、
insert_all,upsert_allなどの bulk upsert で、INCLUDE列が「主キーだから更新しない」と誤判定される問題が解消されます。 - 既存アプリで、誤って「INCLUDE列も主キー」と認識されている前提のロジックを書いていた場合は、挙動が変わる可能性がありますが、それは本来のPostgreSQLの意味的には誤りを前提としていたコードです。
マイグレーションや設定変更の必要性:
- DBスキーマ自体を変更する必要はなく、Rails側の introspection ロジックのみの修正です。
- Postgres / Aurora 側の
INCLUDE付き主キー定義は、そのままで正しく扱われるようになります。
- 参考情報 (あれば)
- 該当Issue: Fixes #58556
- 関連するPostgreSQLカタログ:
pg_index.indkey… インデックスに含まれるすべての列(キー + INCLUDE)pg_constraint.conkey… 制約(ここではPRIMARY KEY)のキー列のみの配列
- 背景技術キーワード:
- covering index / covering primary key
- bulk upsert (
insert_all,upsert_all) - Aurora DSQL のデフォルトインデックス戦略
#58754 to_markdown code/pre content escapes its delimiter (stored XSS)
マージ日: 2026/9/12 | 作成者: @flavorjones
- 概要 (1-2文で)
Action Text のto_markdown変換処理において、<code>/<pre>内のテキストが Markdown のデリミタ(バッククォートやフェンス)から「はみ出す」ことで、javascript:リンクなどを Markdown ソースとしてそのまま出力できてしまう stored XSS 脆弱性を修正した PRです。
デリミタが必ず有効な形で Markdown レンダラに渡されるよう、code/preの扱いとコンテナ要素の行構成ロジックが見直されています。
- 変更内容の詳細
問題の背景
ActionText::MarkdownConversionでは、SKIP_ESCAPING_PARENTSにcode/preが含まれており、その子テキストは Markdown 用のエスケープを行わずに出力される。- 通常は
#visit_code/#visit_preがバッククォート(インラインコード)or フェンスコードブロック(```)で包むことで安全性を担保しているが、その「デリミタが有効に機能しているか」を検証していなかった。 - Markdown の仕様や kramdown/Redcarpet の挙動との組み合わせで、以下のようなケースでデリミタが壊れ、コンテンツが素の Markdown として解釈されてしまうパスが7つあった。
代表例:
<code>内で空行(Trix では<br><br>)を挟むとコードスパンが途中で途切れるhtml<code>a<br><br>[click](javascript:alert(1))</code>が Markdown では
md`a [click](javascript:alert(1))`のように解釈され、空行で段落が分かれるため、後半が「外側の」Markdown として処理される。
<pre>が見出しやsummary、trなど「1行レイアウトの親要素」の中にあると、フェンス ``` が行頭に来ず、コードブロックとして開かないhtml<h1><pre>[click](javascript:alert(1))</pre></h1>が以前は
md# ``` [click](javascript:alert(1))となり、行頭でフェンスが始まらないためコードブロックとして認識されず、内部が素の Markdown として扱われる。コンテナ (
divなど) とpreの間の空行処理の問題で、フェンスが前段落に「くっつく」html<div>before</div><div><pre>[click](javascript:alert(1))</pre>after</div>が以前は
mdbefore``` [click](javascript:alert(1))after
のようになり、「before」と ``` が同じ行に出てしまい、フェンスが正しく始まらない。<pre>がtr直下、ol内など、Markdown のリストやテーブルのレイアウトルールと衝突するケースコード内容が空白から始まる場合、kramdown が 1本のバッククォートではコードスパンを開始しない問題
CR (
\r) が含まれる場合、Ruby のString#linesと Markdown の行終端ルールのズレでインデント処理が破綻する問題
これらにより、#visit_a が javascript: を拒否するにもかかわらず、[click](javascript:alert(1)) のような「Markdownとしてのリンク構文」をコードブロック外のプレーンテキストとして出力させることができ、to_markdown の結果を保存・後レンダリングするアプリケーションでは stored XSS になり得ます。
主な対策・実装変更
1. #inline_code の強化
#inline_code の責務が拡張されています。
- コードスパン内の行末を事前に正規化し、Markdown が内部の改行をスペースに潰す仕様に合わせて「空行が段落を終わらせない」ようにする。
- 行末をすべて「スペース扱い」になるように潰すことで、途中に
<br><br>(空行)相当があっても、結果として 1つのインラインコードスパンの中に収まる。
- 行末をすべて「スペース扱い」になるように潰すことで、途中に
- コンテンツが空白から始まる場合には、バッククォートを1本ではなく2本(``)に拡張して囲む。
- kramdown は「先頭が空白のコード」を1本バッククォートでは開始しないため、デリミタの幅を広げることで必ずコードスパンとして認識されるようにする。
これにより、<code> 内の内容がどのような改行・空白で始まっていても「有効な」コードスパンとして Markdown レンダラに渡ることが保証されます。
2. #visit_pre の挙動変更
<pre> 要素は、コンテキストによって出力形式が分岐するようになりました。
SINGLE_LINE_ANCESTORSに含まれる祖先(インライン要素、見出し要素、summary,tr,td,thなど「子を1行に並べる」要素)の配下にある<pre>は、これまでのようなフェンスコードブロックではなく「インラインコードスパン」として出力する。- また、CR (
\r) を LF (\n) に正規化してから処理し、String#linesと Markdown の行処理のズレによるインデント崩れを防ぐ。
例:
<h1><pre>[click](javascript:alert(1))</pre></h1>以前:
# ```
[click](javascript:alert(1))
修正後:
```md
# `[click](javascript:alert(1))`これにより、ヘッダ行の途中からフェンスが始まるといった「壊れたフェンス」が出力されなくなります。
3. コンテナ要素の共通処理 #visit__container
div, li, td, th, thead, tbody および「専用 visitor を持たない要素」は「透明なコンテナ」として共通の #visit__container を使うように変更されています。
#visit__containerは「自分の子にブロック要素が含まれているか」を見て、自身をブロックとして扱うかを決める。#join_childrenは、ブロック要素とその前のテキストの間を「1つの改行」ではなく「空行(2つの改行)」で区切るようになった。- これは redcarpet が「段落の途中でフェンスが出てきてもブロックを開始しない」という仕様を持つためで、フェンスで始まるコードブロックを「段落と段落の間」に強制的に置くため。
例:
<div>before</div><div><pre>[click](javascript:alert(1))</pre>after</div>以前:
before```
[click](javascript:alert(1))after
修正後:
```md
before[click](javascript:alert(1))
afterこれにより、フェンス ``` が必ず行頭かつ独立したブロックとして認識され、内部が「生の Markdown」として評価される余地が大きく減ります。
4. リスト(ol)・テーブル行(tr)・ブロック引用などのレイアウト修正
#format_list_item- 箇条書きの継続行(1行目以降)のインデントを「マーカー幅」に揃えるよう変更。
- 数字付きリスト
1.の場合は 3 文字(1.)分のスペースをインデントとして付与。 - 箇条書き
-の場合は 2 文字(-)分のスペース。
- 数字付きリスト
- これにより、
<pre>などのブロック要素を含むリスト項目でも Markdown 的に正しいレイアウトになり、項目が途中で切れてフェンスが外側に出てしまう問題を抑止。
- 箇条書きの継続行(1行目以降)のインデントを「マーカー幅」に揃えるよう変更。
#visit_blockquote- 行ごとのインデント処理(
>付与)の前提として、CR/LF 正規化などを含めた「1行単位」の処理が安全に行われるよう調整。
- 行ごとのインデント処理(
<tr>直下の<pre>(<td>ではなくtr > pre)という HTML4/テンプレートで許される非標準構造に対しても、1行レイアウト問題が起きないようSINGLE_LINE_ANCESTORS/ コンテナロジックでカバー。
5. <a> + <pre> 組合せの挙動変更(テスト更新)
<a href="..."><pre>...</pre></a>の場合、以前は「フェンスで囲まれたコードブロック」がリンクテキストとして扱われていたが、今回の変更で「リンクテキスト内のインラインコード」に変わった。
例:
<a href="https://example.com"><pre>puts 1</pre></a>以前:
[``` puts 1 ```](https://example.com)修正後:
[`puts 1`](https://example.com)意味的には「コードをリンクテキストにしている」点で同等だが、フェンスブロックではなくインラインコードにすることでレイアウトと安全性が安定する。
- 影響範囲・注意点
- 影響対象:
- Action Text の
to_markdownを利用して「リッチテキスト → Markdown」を保存し、後から Markdown をレンダリングするアプリケーション。 - 特にユーザ投稿コンテンツを想定している場合、これまで
to_markdownを通しても XSS を完全に防げていなかった可能性がある。
- Action Text の
- 互換性への影響:
to_markdown出力のフォーマットが、主に以下のケースで 変化 します。<pre>がヘッダやsummary、テーブル行など 1 行レイアウトの親に入っている場合、フェンスコードブロックではなくインラインコードに変わる。<div>中の<pre>などで、前のテキストとの間に空行が挿入されるため、Markdown 上は段落が分かれた形になる。<a><pre>...</pre></a>がブロックコードではなくインラインコードリンクになる。- リストや引用でのインデントや改行の扱いが変更され、レンダリング結果が若干変わる可能性がある。
- ただし、これらは「本来 Markdown 的に正しい形」への修正であり、多くのケースでは視覚的・意味的には自然な変更です。
- セキュリティ観点:
- この PR は stored XSS を防ぐためのセキュリティ修正であり、挙動の微調整より安全性が優先されています。
- ユーザ生成コンテンツを
to_markdown経由で保存している場合は、できるだけ早いアップデートが推奨されます。
- バージョン・バックポート:
#to_markdownはv8.1.0タグには存在しないため、公式には「過去バージョンへのバックポート対象はない」とされています。- 独自に
ActionText::MarkdownConversionをコピー・フォークして使っている場合は、同様の修正を適用すべきです。
- 参考情報 (あれば)
- PR 本文で参照されている HackerOne レポート:
- https://hackerone.com/reports/3994016 (報告者: seoafoz)
- 過去の類似問題 (
SKIP_ESCAPING_PARENTSを前提にした設計が破れた最初のケース):<action-text-markdown>マーカータグに関する修正コミット:2191ca0d
- 使用されている Markdown 実装の挙動前提:
- kramdown: インラインコード開始のバッククォートと先頭空白の扱い
- redcarpet: 段落中に現れるフェンスの扱い(行頭でフェンスが始まらないとコードブロックとして認識しない)
#58753 Action View test stubs Rails.root and breaks main
マージ日: 2026/9/12 | 作成者: @flavorjones
- 概要 (1-2文で)
このPRは、Action View のテストでRails.rootを stub しようとして失敗していた問題を修正し、mainブランチのテストを再び通るようにするものです。Rails.rootへの依存を排除し、すでに導入済みのStructuredEventSubscriber.rails_rootを用いる形にテストを整理しています。
- 変更内容の詳細(あればサンプルコードも含めて)
問題の背景
ある過去のコミット (#58261, 16ad4bb6) を取り込んだ際に、
actionview/test/template/structured_event_subscriber_test.rbのテストがRails.rootを stub しようとして以下のエラーが発生していました:textNameError: undefined method 'root' for class 'Rails'Action View のテスト環境には
Rails.rootが定義されていないにもかかわらず、rubyRails.stub(:root, ...) do ... endのようなコードが残っていたため、
Minitest::Mock#stubが「存在しないメソッドは stub できない」として落ちていました。その後の別コミット (85127cd9) で、
Rails.root依存をやめてStructuredEventSubscriber.rails_rootを使うようにテストを書き換えたのですが、マージの過程で「Rails.stub(:root, ...)のラッパーが 1 か所だけ復活してしまった状態になっていました。
このPRでの修正内容
- 問題となっていたテスト
test_render_notifications_are_silenced_outside_debug_modeから、Rails.stub(:root, ...)のラッパーを削除しています。 - このテストファイルではすでに
setupでrails_rootをフィクスチャディレクトリに向けており、その前提でテストが動作するように設計されています。そのため、Rails.rootの stub は不要です。
イメージとしては、以下のような変更です(正確なコードではなく説明用の擬似コード):
# 変更前(イメージ)
def setup
@subscriber = StructuredEventSubscriber.new
@subscriber.rails_root = FIXTURE_PATH
end
def test_render_notifications_are_silenced_outside_debug_mode
Rails.stub(:root, FIXTURE_PATH) do
# テスト本体
end
end
# 変更後(イメージ)
def setup
@subscriber = StructuredEventSubscriber.new
@subscriber.rails_root = FIXTURE_PATH
end
def test_render_notifications_are_silenced_outside_debug_mode
# Rails.stub(:root, ...) を使わず、setup で設定済みの rails_root を利用
# テスト本体のみを実行
endテストロジックの保持確認
- PR説明にある通り、
ActionView::StructuredEventSubscriber::Start#silenced?を削除してもこのテストは失敗することが確認されています。 - これは、テストが単に「何かが動けばOK」という状態ではなく、
silenced?の動作(デバッグモード外で通知が抑制されるか)を正しく検証していることを意味します。 - よって、今回の変更は単にテスト環境のセットアップ方法(
Rails.rootを stub しない)を修正しただけで、テストが担保している挙動は維持されています。
- 影響範囲・注意点
影響範囲
- 変更は
actionview/test/template/structured_event_subscriber_test.rbのみで、本番コードには手を入れていません。 - 影響は Action View のテストスイートに限定されます。
mainブランチで落ちていたテストが通るようになる修正であり、実行時の挙動に変更はありません。
- 変更は
注意点
- テスト環境・ユニットテストで
Rails.stub(:root, ...)のように「テストランタイムでは存在しないメソッド」を stub しようとすると、今回と同様にMinitest::Mock#stubが例外を投げます。 - Rails 以外でも「フレームワークが本番では提供するが、テスト環境では提供しない定数/メソッド」に対して stub を行うと同種の問題が起こり得るため、テスト側で専用のアクセサ(今回の
StructuredEventSubscriber.rails_rootなど)を用意する方針が安全です。
- テスト環境・ユニットテストで
- 参考情報 (あれば)
- 関連PR:
- #58261:
StructuredEventSubscriber周辺の変更を導入したPR - 85127cd9:
Rails.rootの stub をやめてStructuredEventSubscriber.rails_rootに切り替えたコミット
- #58261:
- 技術的ポイント:
Minitest::Mock#stubは「レシーバが実際に持っているメソッド」しか stub できない。- テスト容易性のために、グローバルな
Rails.root依存を避け、クラス単位のrails_rootアクセサを設けるのは良い設計パターン。
#58743 Boot when ruby-vips cannot load its libraries
マージ日: 2026/9/12 | 作成者: @flavorjones
- 概要 (1-2文で)
ruby-vips gem がインストールされているものの、ネイティブライブラリ libvips が存在しない環境でも Rails アプリがブートできるように、Active Storage の vips 周りの例外処理と読み込み順序が修正されています。これにより、これまで初期化時にアプリごと落ちていたケースが、ブートは成功しつつログで適切に警告される挙動になります。
- 変更内容の詳細
問題の背景
構成が以下のようなケースでアプリが起動不能になっていました:
ruby-vipsgem は入っている- しかしシステムに
libvips(および glib) が入っていない
このとき:
ruby-vipsは内部でglib→libvipsの順でネイティブライブラリをロードするglibロードに失敗し、LoadErrorのメッセージにはglib-2.0が含まれるActive Storage 側のコードは、以下のように
LoadErrorメッセージをチェックしていました:rubyrescue LoadError => error ActiveStorage::VIPS_AVAILABLE = false # :nodoc: raise error unless error.message.match?(/libvips|ruby-vips/)メッセージに
libvipsもruby-vipsも含まれない (glib-2.0だけ) ためraiseされ、ブートが中断されていました。さらに
ActiveStorage::Transformers::Vipsが無条件にimage_processing/vipsを require しており、その中で再度ruby-vipsが require される構造になっていました。image_processing2.0.0 / 2.0.1 は、そのLoadErrorをImageProcessing::Errorに包んで投げ直す挙動ActiveStorage::Engine側はLoadErrorしか rescue していないため、このエラーは捕捉されず、やはりブート失敗- Rails リポジトリ自身の lockfile も 2.0.2 未満を指しており、この問題の影響を受ける状態でした
今回の修正方針
ポイントは:
- 「ruby-vips を require したときの
LoadErrorは、理由やメッセージ内容に関係なく ruby-vips 不利用として扱う」 - 「一度 ruby-vips を利用不可と判定したら、
image_processing/vipsを追加で require しようとしない」 - 「
image_processinggem 自体が無い場合と、ruby-vips/libvips が無い場合をきちんと区別しつつ、どちらでもアプリはブートできるようにする」
コードレベルでの変更点
1) activestorage/lib/active_storage/vips.rb
- これまでの「メッセージ内容でフィルタしていた
LoadError再スロー」をやめ、ruby-vips の require から発生するLoadErrorはすべて「利用不可」として扱うように整理。 - 結果として、glib が原因の
LoadErrorでも、libvips 不在でも、メッセージ内容に依らずActiveStorage::VIPS_AVAILABLE = falseとして処理され、アプリは落とさない。
(差分は小さいですが、例外メッセージのパターンマッチ削除が主要なポイントです)
2) activestorage/lib/active_storage/transformers/vips.rb
以前は:
rubyrequire "image_processing/vips"を無条件に行っていましたが、今回:
- まず
image_processingそのものを require - ruby-vips が利用不可と判定された場合は、
image_processing/vipsは require しない - vips 利用不能時は、
Transformers::Vips側で 自前でLoadErrorを raise するように変更
- まず
これにより:
image_processinggem 自体がない場合:require "image_processing"がLoadErrorを投げ、それをActiveStorage::Engine側が検知し、従来通り「variant processor が無い」扱いでブートを継続- ruby-vips/libvips がない場合:
image_processingは存在するが vips は使えないため、Transformers::VipsがLoadError(「vips は利用できない」旨)を発生させ、それを元に「VIPS プロセッサは利用不可」という状態を報告
3) エラーメッセージの改善
libvips がインストールされていない状態で ruby-vips がある場合、アプリはこれまで初期化中に落ちていましたが、今後はブートに成功し、ログに次のようなメッセージが出るようになります:
Using vips to process variants requires the libvips library. Please install libvips using the instructions on the libvips website.開発者はこれを見て libvips のインストール手順に進めます。
4) テストの追加 (railties/test/application/active_storage/engine_integration_test.rb)
test_does_not_require_ruby_vips_again_after_it_fails_to_loadが追加。- ruby-vips のスタブを用意し、「2回目に require されたら
RuntimeErrorを投げる」ようにしておき、 - vips のロードに一度失敗した後、再度 ruby-vips を require しにいかないことを保証しています。
- ruby-vips のスタブを用意し、「2回目に require されたら
- これにより、
image_processingのバージョンに依存せず、「二重ロードしない」という振る舞いをテストで担保しています。
5) その他
activestorage/CHANGELOG.mdに今回の挙動変更を追記。- #58734 で導入された
ActiveStorage.require_securable_vips!との関係:- 「セキュアでない ruby-vips」は、image_processing の有無にかかわらず
:vipsプロセッサ利用時に例外を投げる、という既存仕様を維持 - そのチェック呼び出し位置は、新しい「利用可否チェック」より前に残されており、セキュリティ関連の挙動は変わらないようになっています。
- 「セキュアでない ruby-vips」は、image_processing の有無にかかわらず
- 影響範囲・注意点
影響を受けるアプリ:
ruby-vipsgem を Gemfile に含めている- しかし、実行環境に
libvips(および必要な glib など) がない - (
image_processingのバージョンが 2.0.0 / 2.0.1 など、古いものでも今回の修正でブートできるようになる)
挙動の変化:
- これまで: 上記構成だと Rails 起動時に
LoadError/ImageProcessing::Error経由でアプリ全体が落ちる - これから: アプリはブートに成功し、ログに「libvips をインストールしてほしい」というメッセージが出る。VIPS ベースの variant 処理は利用不可だが、他の機能は動く
- これまで: 上記構成だと Rails 起動時に
開発者が見るべきポイント:
- CI や本番環境で「突然 Active Storage の vips 変換が使えなくなった」場合、ログに libvips についての警告が出ていないか確認する
- libvips を導入できない環境であっても、いきなりブートが止まらなくなったため、問題に気付きづらくなる可能性がある
→ その場合は、config.active_storage.variant_processorを:mini_magickなど vips 以外に切り替えることも検討するとよいです。
互換性:
- API 仕様が大きく変わったわけではなく、主にエラー処理と require 戦略の改善なので、多くのアプリはコード変更なしに恩恵を受けます。
- ただし、内部的な例外クラスやメッセージに依存していたテストやコードがある場合は、ログや例外メッセージの変化の影響を受ける可能性があります。
- 参考情報 (あれば)
- 該当 Issue: Fixes #58723 — 「ruby-vips はあるが libvips が無いとアプリが起動しない」問題の修正
- 関連 PR: #58734 —
ActiveStorage.require_securable_vips!の導入 / 移動 - ライブラリ関連:
- ruby-vips: https://github.com/libvips/ruby-vips
- libvips: https://libvips.github.io/libvips/
- image_processing: https://github.com/janko/image_processing
この変更により、「ruby-vips / libvips 周辺の環境不備」があってもアプリ全体のブートは維持され、問題をログベースで発見しやすくなっています。
#58261 Restore silenced? on the Action View render Start subscriber
マージ日: 2026/9/12 | 作成者: @nuclearspike
- 概要 (1-2文で)
Action View の構造化イベント用Startサブスクライバにsilenced?を復活させ、デバッグモードがオフのときにはrender_template.action_view/render_layout.action_viewの通知処理自体をスキップできるようにした PR です。これにより、本番相当環境 (debug_mode: false) でのレンダリング時のオブジェクト生成とオーバーヘッドが大きく削減されます。
- 変更内容の詳細
背景
- 以前の PR (#50751)
- 従来の (ログベースの)
render_template.action_view/render_layout.action_viewサブスクライバにsilenced?が追加され、- ログレベルが debug より上のときは
ActiveSupport::Notifications側が「そもそも通知を投げない」ように最適化されていました(subscribe_log_level最適化に乗る)。
- 従来の (ログベースの)
- 構造化イベント対応 (#55690)
- Action View 向けに新しい
Startクラスが導入され、同じレンダーイベントにサブスクライブ。 - しかしこの
Startにはsilenced?が定義されておらず、ActiveSupport::Notifications::Fanoutからは「常に有効なサブスクライバ」とみなされていました。 - 結果として:
- debug モードがオフでも
listening?が常に true - 毎回
Handleが生成される Start#startも毎回呼ばれ、from_rails_rootを含む文字列処理などのデバッグ用 kwargs を構築- その後で
ActiveSupport.event_reporter.debugが「debug モードでないので破棄」という一段下の層で捨てる
→ 本来スキップできる処理が毎レンダリングで走ってしまっていた。
- debug モードがオフでも
- Action View 向けに新しい
今回の修正
actionview/lib/action_view/structured_event_subscriber.rb にて:
- 構造化イベント用
Startクラス(ActionView::StructuredEventSubscriber内)のサブスクライバにsilenced?を追加。 - 実装は「イベントレポーターが debug モードでなければサイレント(true を返す)」という形で、#50751 と同じポリシーを踏襲しています。
概念的なイメージ:
class Start
# 既存: start(event) などで debug 用の kwargs を構築して emit している
# 今回追加されたイメージ
def silenced?
!ActiveSupport.event_reporter.debug_mode
end
endこれにより:
debug_mode == falseのときActiveSupport::Notificationsはこのサブスクライバを「サイレント」と認識し、render_template.action_view/render_layout.action_viewイベントに対してはlistening?が falseHandleの生成も行わないStart#start自体が呼ばれない
→ 通知レイヤで早期にショートサーキットする。
debug_mode == trueのとき- 挙動は以前と完全に同じで、構造化イベントは従来通り発火・処理される。
テストの追加
actionview/test/template/structured_event_subscriber_test.rb にテストが追加:
with_debug_event_reportingなどを使い、debug_mode をオン・オフして挙動を検証。- ポイント:
- debug_mode: false
ActiveSupport::Notificationsのlistening?が false になり、render イベントに対して通知されていないことを確認。- 実際にテンプレートを render しても、イベントレポーターには何もレポートされないことを確認。
- debug_mode: true
listening?が true に戻り、イベントが再び発火することを確認。
- debug_mode: false
- 既存の構造化イベントテストは変更なしで通過(デフォルトは debug_mode: true になるようテスト側で制御済み)。
ベンチマーク結果
debug_mode: false(= 本番相当: config.log_level = :info)時:
- オブジェクト数(1 render あたり)
- minimal template: 60 → 37 個
- template + 25 partial collection: 324 → 301 個
benchmark-ips(Ruby 3.3.11, arm64-darwin)- minimal template: 107.3k → 158.2k i/s (約 1.47x)
- minimal template (YJIT): 162.4k → 256.2k i/s (約 1.58x)
- template + 25 partials: 18.6k → 20.0k i/s (~1.08x)
- 同 (YJIT): 31.1k → 33.3k i/s (~1.07x)
単純なレンダリングほど、不要な通知処理の削減効果が顕著に出ています。
- 影響範囲・注意点
対象:
- Action View の構造化イベント (
ActionView::StructuredEventSubscriber) を通じたrender_template.action_view/render_layout.action_viewの Start イベント。 ActiveSupport.event_reporter.debug_modeが false の環境(多くの Rails アプリの本番等)。
- Action View の構造化イベント (
影響:
- debug_mode: false
- これらのレンダーイベントは「構造化イベントとしては発火しない」挙動になります。
- ただしこれは元々 #50751 時点で想定されていた仕様であり、今回の PR で従来ログベースの仕組みと整合が取られた形です。
- その代わり、レンダリングごとのオブジェクト生成・CPU コストが削減されます。
- debug_mode: true
- これまでと挙動は同一。開発環境などでのレンダリング・イベント観測は変わりません。
- debug_mode: false
注意点:
- 本番で構造化イベント (
Startイベント) を常に拾いたい、かつ debug_mode を false にしているような独自運用をしている場合、- この PR によりイベントが飛ばなくなります。
- その場合は
ActiveSupport.event_reporter.debug_mode = true相当の設定にする必要があります(通常の Rails のブートロジックではconfig.log_levelに応じて debug_mode を設定)。
ActiveSupport::Notifications.subscribe("render_template.action_view")を独自に利用しているコードで、構造化イベント経由ではなく直接サブスクライブしている場合は、この PR の影響は受けません(このsilenced?は構造化イベントサブスクライバ固有)。
- 本番で構造化イベント (
- 参考情報 (あれば)
- 関連 PR
- #50751:
silenced?を Action View の render ログサブスクライバに導入し、log_level に応じて通知を抑制。 - #55690: 構造化イベント対応で
ActionView::StructuredEventSubscriber::Startを導入。
- #50751:
- 実運用上のポイント
- Rails 標準では、本番環境で
config.log_level = :infoがデフォルトであり、この場合debug_mode = falseとなるため、本 PR の恩恵(オーバーヘッド削減)を自動的に受けられます。 - 開発環境では
config.log_level = :debugであり、従来通り詳細な構造化イベントを取得できます。
- Rails 標準では、本番環境で
#58734 Boot with an unsecurable libvips unless the processor uses it
マージ日: 2026/9/12 | 作成者: @flavorjones
- 概要 (1-2文で)
Active Storage が libvips/ruby-vips のセキュリティ設定 (Vips.block_untrusted(true)) を「常にブート時に行う」一方で、「実際に libvips を使う場合にだけエラーを投げる」ように挙動を変更し、:mini_magick利用アプリが古い libvips 環境でもブートできるようにした PR です。
これにより、vips を使わないアプリは libvips が「unsecurable(安全に設定できない)」であっても起動可能となり、vips を使うアプリだけが安全でない環境を検出して落ちるようになります。
- 変更内容の詳細
背景の整理
- 8.1.3.1 以降、
active_storage/vipsをブート時に読み込む際に:- ruby-vips はインストールされているが
- libvips または ruby-vips のバージョンが古くて「untrusted loader のブロック機能」を使えない
という環境だと、その場で例外を投げていました。
- このチェックは「ruby-vips が入っている」だけで発動しており、「:vips を使うかどうか」とは無関係でした。
- そのため、アプリ側で
config.active_storage.variant_processor = :mini_magickを指定していても、- gem として ruby-vips が入っている
- しかし libvips 側が古くてセキュア設定が効かない というだけで Rails 自体がブートできなくなっていました (#58394)。
この PR はその問題を解消しつつ、「vips を使うアプリについては従来どおり安全性を担保する」という落としどころを取っています。
振る舞いの方針変更
説明から読み取れる挙動の変化は次のとおりです。
以前の挙動(8.1.3.1~この PR 以前)
- ブート時に
active_storage/vipsを読み込むタイミングで:- ruby-vips を
requireして Vips.block_untrusted(true)を呼び出す
- ruby-vips を
- ここで「untrusted loader をブロックできない(unsecurable)」と判定された場合は、その時点で例外を投げてアプリのブート自体が失敗。
- このチェックは variant_processor が
:vipsかどうかに関係なく働いていた。
この PR の挙動
- 依然として「全プロセッサでブート時に ruby-vips を読み込み」「
Vips.block_untrusted(true)を呼ぶ」点は維持。- つまり、ruby-vips が入っている環境では、原則としてブート早期に libvips をセキュアモードにしようと試みる。
- ただし、「セキュア設定に失敗したこと自体ではブート時に例外を投げない」ように変更。
- 実際にエラーとして扱うのは、「libvips を使うタイミング」に限定:
- Vips Transformer がロードされるとき(画像変換に vips を使おうとした時)
- Vips Analyzer が画像を読むとき(メタ情報解析に vips を使おうとした時)
variant_processor = :vipsなアプリの場合:- 依然として「起動時に問題を検出して落ちる」性質は維持
- ただしその例外は engine のロード時ではなく
after_initializeフェーズで発火するように場所がシフト
要するに:
- ruby-vips がインストールされているだけの環境で
- Active Storage 自体は
Vips.block_untrusted(true)を呼ぶが - unsecurable でもブートは通す
- Active Storage 自体は
- 実際に vips を使おうとした瞬間だけ、「セキュリティ上危険なバージョンなので使えない」という例外を投げる。
コードレベルの変更点(概要)
正確なコードは省略しますが、役割ごとに見ると:
activestorage/lib/active_storage/vips.rb- ブート時に ruby-vips を
requireし、Vips.block_untrusted(true)を呼ぶロジックは維持しつつ、 - 「untrusted をブロックできない状態」を記録しておく方向の実装に寄せ、
- 実際に使う場面(transformer / analyzer)でその状態を見て例外を投げるように整理されていると考えられます。
- ブート時に ruby-vips を
activestorage/lib/active_storage/transformers/vips.rb- Vips transformer をロードするときに、「この環境の libvips は安全に設定されているか?」をチェックし、
- unsecurable ならここで例外を投げるようなガードが追加(+1行)。
activestorage/lib/active_storage/analyzer/image_analyzer/vips.rb- 画像を解析する(メタ情報を読む)タイミングで同様のチェックを行い、
- unsecurable なら例外を投げるガードが追加(+2行)。
テスト類
activestorage/test/analyzer/image_analyzer/vips_test.rb- analyzer 経由での挙動(unsecurable 環境での例外発生)をカバーするテストを追加。
railties/test/application/active_storage/engine_integration_test.rb- ruby-vips 2.1.4(古め)+
variant_processor = :mini_magickという環境で、- 8.1.3.1 / main だとブート失敗
- このブランチではブート成功
を再現する統合テストを追加。
- ruby-vips 2.1.4(古め)+
activestorage/CHANGELOG.md- 上記の挙動変更が CHANGELOG に記載され、挙動が変わることが明示化されています。
- 影響範囲・注意点
影響範囲
ruby-vips をインストールしているが、Active Storage では vips を使っていないアプリ
- 例:
config.active_storage.variant_processor = :mini_magick - 以前は:
- libvips/ruby-vips が古くて
Vips.block_untrusted(true)が有効にできないと、ブートが失敗。
- libvips/ruby-vips が古くて
- この PR 以降:
- そうした環境でも ブートは成功 する。
- Active Storage 内で vips は実際には使われないので、unsecurable であっても実害はない。
- 例:
variant_processor = :vipsなどで、実際に vips を使うアプリ- セキュリティ要件は維持される:
- libvips が安全に設定できない(unsecurable)場合は、依然としてエラーになる。
- エラー発生タイミング:
- 旧: engine ロード時
- 新:
after_initialize(ブート完了直後)や実際に transformer/analyzer が使われる時点
- 実質的には「vips を使うアプリは、これまで通り unsecurable 環境では起動できない or 起動直後に落ちる」挙動は変わらないが、エラー発生箇所が少し後ろにずれる。
- セキュリティ要件は維持される:
注意点 / マイグレーション観点
vips を使っているアプリ:
- 古い libvips / ruby-vips を使っている場合は今後も必ずアップデートが必要です。
- 対応していないバージョンだと
Vips.block_untrusted(true)相当の安全設定ができないため、Active Storage 側が利用を拒否します。
- 対応していないバージョンだと
- 本番環境で vips を利用する場合は:
- どのバージョンの libvips / ruby-vips が入っているか
Vips.block_untrusted(true)が正しく機能しているか
を確認しておくべきです。
- 古い libvips / ruby-vips を使っている場合は今後も必ずアップデートが必要です。
vips を使っていないが ruby-vips が Gemfile に入っているアプリ:
- 今回の変更によりブートエラーは解消されますが、
- 将来的に誤って
variant_processor = :vipsに変更したり、どこかで vips を使うコードを追加した場合に、そこで初めて unsecurable エラーが顕在化します。 - セキュリティ面を考えると、使っていない gem(ruby-vips)を残しておく意味はあまりないので、不要であれば外しておくのが安全です。
- 参考情報 (あれば)
- 関連 Issue:
- #58394 – 古い ruby-vips / libvips 環境で
:mini_magick使用時にアプリがブートしない問題
- #58394 – 古い ruby-vips / libvips 環境で
- 代替案 PR:
- #58406 –
require自体をブート時から外してしまう案(結果的に、この PR では「ブート時 require は維持しつつ、エラー発火タイミングだけを後ろにずらす」形で採用されている)
- #58406 –
- 再現条件(テストにも記載):
- ruby-vips 2.1.4 を利用
config.active_storage.variant_processor = :mini_magick- Rails 8.1.3.1 / main → ブート失敗
- 本 PR 適用ブランチ → ブート成功
#58747 Make finder_needs_type_condition? ractor safe
マージ日: 2026/9/11 | 作成者: @gmcgibbon
- 概要 (1-2文で)
ActiveRecord::Base.finder_needs_type_condition?の実装を、マルチ Ractor(特に main 以外の Ractor)でも正しく動作するように変更し、メモ化の計算を常に main Ractor に委譲するようにした PRです。これにより、Ractor 環境下でも継承を伴う finder の挙動が安全・一貫して利用できます。
- 変更内容の詳細
背景と問題点
ActiveRecord::Base.finder_needs_type_condition?は、STI(Single Table Inheritance)などでクエリにtype条件が必要かどうかを判定するメソッドです。- 元実装では、その判定結果をメモ化して再利用していましたが、その計算処理が main Ractor 以外で呼ばれると正常に動かない(Ractor 安全でない)箇所がありました。
- 想定される問題:
- Ractor 間で共有しにくい状態(クラス変数やグローバルな設定)に依存している
- Ractor 非共有オブジェクトへのアクセスや、Ractor 非対応なキャッシュ手法を使っている など
- 想定される問題:
対応内容
この PR では、判定値の計算とメモ化を必ず main Ractor 上で行うように変更しています。
イメージとしては以下のような構造になります(実際のコードはニュアンスレベルですが、概ねこうした方向の修正です):
def self.finder_needs_type_condition?
# 既にメモ化済みならそのまま返す(Ractor 共有可能な形で保持)
return @finder_needs_type_condition if instance_variable_defined?(:@finder_needs_type_condition)
if Ractor.current == Ractor.main
# main Ractor 上なら、その場で計算してメモ化
@finder_needs_type_condition = calculate_finder_needs_type_condition
else
# main 以外の Ractor では main に処理を委譲して結果を取得
Ractor.main.send(:calculate_finder_needs_type_condition_for, self)
end
end実際には Rails 内部の Ractor 対応用ユーティリティを使っている可能性がありますが、本質は「非 main Ractor では自前で計算せず、main に問い合わせる」という点です。
テストの追加内容
activerecord/test/cases/inheritance_test.rb に以下のような観点のテストが追加されています(行数から推測できる範囲):
finder_needs_type_condition?が Ractor 使用時でも例外を出さず実行できること- main Ractor / 非 main Ractor から呼んだ場合でも、一貫した結果が得られること
- 一度計算された結果がメモ化され、以降の Ractor からの呼び出しでも同じ値を使えること
これにより、Ractor 環境でも継承関連の finder ロジックが壊れていないことを確認しています。
- 影響範囲・注意点
影響範囲
- 影響を受けるのは主に Ractor を利用している / 将来利用する予定がある Rails アプリケーション です。
- 単一スレッド・シンプルなマルチスレッド環境(Ractor 未使用)の場合は、ほぼ挙動は変わりません。
ActiveRecord::Base.finder_needs_type_condition?を直接参照しているコード(独自のメタプログラミングや拡張で使っている場合など)は、Ractor 環境でも安全に動くようになります。
パフォーマンス面
- 判定結果はメモ化されるため、「初回だけ main Ractor に問い合わせ・計算が発生し、その後はキャッシュされた値を返す」という挙動になります。
- そのため、通常運用でのパフォーマンスへの影響はごく限定的と考えてよいです。
- Ractor を数多く立ち上げるような構成でも、メモ化のために計算回数は少なく抑えられます。
注意点
- Ractor 周りの挙動に依存した独自実装(例:
finder_needs_type_condition?のメモ化状態を直接操作するような monkey patch)がある場合は、main への委譲という前提を考慮する必要があります。 - この PR は主に「破綻するケースをなくす」ための修正であり、
finder_needs_type_condition?のロジック自体(「どういう条件で true/false か」)は変わっていません。
- 参考情報 (あれば)
- 類似の修正: https://github.com/rails/rails/pull/58399
- 同様に Ractor 安全性を高めるための main Ractor プロキシング対応が行われています。
- 対象メソッドの定義場所:
activerecord/lib/active_record/inheritance.rb
STI や継承関連のロジックがまとまっているファイルです。
- Ractor について(Ruby 本体のドキュメント):
- https://docs.ruby-lang.org/ja/latest/doc/ractor_md.html
Ractor 間のオブジェクト共有制約やメッセージパッシングの前提を理解しておくと、この種の修正の意図が掴みやすくなります。
- https://docs.ruby-lang.org/ja/latest/doc/ractor_md.html
#58710 Share the time zone caches with Ractor workers
マージ日: 2026/9/11 | 作成者: @etiennebarrie
- 概要 (1-2文で)
Ractor 化された Rails アプリケーションでもActiveSupport::TimeZoneを安全に共有・参照できるように、タイムゾーン関連キャッシュを eager-load し、Ractor 共有可能な構造(凍結された Hash)に変更した PR です。これにより Ractor ワーカーからもタイムゾーン情報にアクセス可能になりますが、Ractor 化時のブートに ~200ms のオーバーヘッドが追加されます。
- 変更内容の詳細
背景
- 既存 PR #58067 で
ActiveSupport::TimeZoneオブジェクト自体は Ractor shareable になっていた。 - しかし、タイムゾーンを管理するレジストリが
Concurrent::Mapで実装されており、Concurrent::Map自体が Ractor shareable ではないため、Ractor 内からレジストリに安全にアクセスできなかった。 - Ractor 化を行う
Rails::Application#ractorize!のタイミングで、「Ractor から読める形」にレジストリを固めておきたい、というのがこの PR の目的。
主な変更点
1) TimeZone のキャッシュ構造の変更 (time_zone.rb)
これまで:
- タイムゾーンのレジストリ/キャッシュに
Concurrent::Mapを使用。 - 遅延ロード(必要になったタイミングで TZInfo から読み込む)前提。
- タイムゾーンのレジストリ/キャッシュに
これから:
- Ractor 化されたアプリケーション向けに、全タイムゾーンを事前に eager-load し、
- レジストリを 凍結された通常の Hash に変換して保持。
- Hash + freeze によって Ractor shareable となり、Ractor ワーカーから読み取り専用で安全に参照可能。
具体的には次のような動きになります(擬似コードイメージ):
ruby# Ractor 化前: Concurrent::Map ベースの遅延ロードキャッシュ ZONES = Concurrent::Map.new def self.find_tzinfo(name) ZONES.compute_if_absent(name) { TZInfo::Timezone.get(name) } end # Ractor 化時: 全 TZInfo タイムゾーンを eager-load し、Hash に変換して freeze def self.prepare_ractor_safe_cache! all_tzinfos = TZInfo::Timezone.all # 全タイムゾーンを列挙 @zones = all_tzinfos.to_h { |tz| [tz.identifier, ActiveSupport::TimeZone.new(...)] }.freeze end def self.[](name) @zones[name] # 以後は凍結 Hash から読み取りのみ end※上はイメージであり実際のコードとは異なりますが、「Concurrent::Map → freeze された Hash」「遅延ロード → eager-load」の方向性がポイントです。
国コードなどに紐付くタイムゾーン一覧(countries)についても同様に eager-load し、Ractor shareable なキャッシュ構造に変更しています。
2) Ractor 化フローへの組み込み (Rails::Application#ractorize!)
railties/lib/rails/application.rbに 1 行追加されており、ractorize!実行時に、TimeZone キャッシュの eager-load & 凍結処理を呼び出すようになっています。
- これにより、「通常の(非 Ractor)Rails アプリ」ではこのオーバーヘッドは発生せず、
ractorize!を使うアプリだけ が追加の初期化コスト (~200ms) を負担する設計になっています。
3) テストの追加 (time_zone_test.rb)
ActiveSupport::TimeZoneのキャッシュが Ractor からアクセス可能であること、もしくは Ractor 対応の初期化が正しく機能していることを検証するテストが追加されています。- 典型的には以下のような観点が含まれていると考えられます:
- Ractor 起動後に
TimeZone["UTC"]などが正常に取得できる。 - Ractor 化前後で返されるオブジェクトが shareable / frozen である。
- 既存の API (
TimeZone[],TimeZone.all,TimeZone.country_zonesなど) の挙動が変わらない。
- Ractor 起動後に
- 影響範囲・注意点
影響範囲
対象:
Rails::Application#ractorize!を利用した、Ractor ベースの並列処理を行う Rails アプリケーション。ActiveSupport::TimeZoneと TZInfo を利用するコード全般(特に Ractor 内での利用)。
非対象:
- Ractor を使わない通常の Rails アプリは、基本的に挙動/パフォーマンスへの影響は限定的です(eager-load は Ractor 化のときのみ)。
実務上のポイント
- Ractor 化アプリでは:
- ブート時に 全タイムゾーン + 国別タイムゾーン一覧を読み込むため、約 200ms 程度の追加コスト が発生します。
- 代わりに、Ractor ワーカー側で
Time.zone,Time.find_zone,ActiveSupport::TimeZone[...]などを安全に利用可能になります。
- キャッシュが「凍結された Hash」で固定されるため:
- Ractor 化後にタイムゾーンレジストリを動的に書き換えるようなユースケースは想定されていません(もともとそのような使い方は推奨されていません)。
- TZInfo のタイムゾーン定義に依存するため:
- タイムゾーンデータの更新は、従来同様、TZInfo と tzdata のアップデートに依存します。
- 本 PR は「キャッシュ構造と読み込みタイミングの変更」であり、タイムゾーンの意味的な定義・解釈には影響しません。
- 参考情報 (あれば)
- 対象 PR:
- Share the time zone caches with Ractor workers (#58710)
https://github.com/rails/rails/pull/58710
- Share the time zone caches with Ractor workers (#58710)
- 関連 PR:
- Make
ActiveSupport::TimeZoneobjects themselves shareable (#58067)
https://github.com/rails/rails/pull/58067
- Make
- 関連ドキュメント / 概念:
- Ruby Ractor: https://docs.ruby-lang.org/en/master/doc/ractor_md.html
- Rails
ActiveSupport::TimeZone: https://api.rubyonrails.org/classes/ActiveSupport/TimeZone.html
#53403 Include default headers in ActionController::Live responses.
マージ日: 2026/9/11 | 作成者: @afn
- 概要 (1-2文で)
ActionController::Live でストリーミングレスポンスを返す際にも、通常のコントローラレスポンスと同じ「デフォルトヘッダ」が確実に付与されるようにした修正です。これにより、Live レスポンスだけヘッダが欠けるという不整合バグ (#53402) が解消されました。
- 変更内容の詳細
背景 / 問題点
Rails の標準レスポンスは ActionDispatch::Response.create を経由して生成され、その中でデフォルトヘッダ(例: Content-Type、Cache-Control など環境による)がマージされます。
response = ActionDispatch::Response.create
# ↑ .create の中で default_headers がマージされる一方、ActionController::Live はストリーミングのために独自の Live::Response を使っており、make_response! 内で Live::Response.new を直接呼んでいました。このため、create 経由の「デフォルトヘッダのマージ」がスキップされ、Live レスポンスにだけデフォルトヘッダが載らない状態になっていました。
具体的な修正内容
主な変更は以下の1点です。
actionpack/lib/action_controller/metal/live.rbmake_response!内でのレスポンス生成をLive::Response.newからLive::Response.createに変更。
イメージとしては以下のような変更です(擬似コード):
# 変更前
def make_response!(request)
@response = Live::Response.new
end
# 変更後
def make_response!(request)
@response = Live::Response.create
endLive::Response は ActionDispatch::Response を継承しており、create を使うことで、通常レスポンスと同様に default_headers がマージされるようになります。
テストの追加
actionpack/test/controller/integration_test.rbにテストが追加・更新され、- ActionController::Live を使うコントローラのレスポンスヘッダに、通常レスポンスと同じデフォルトヘッダが含まれること
を検証しています。
- ActionController::Live を使うコントローラのレスポンスヘッダに、通常レスポンスと同じデフォルトヘッダが含まれること
CHANGELOG
actionpack/CHANGELOG.mdにこのバグ修正が追記されています。- ActionController::Live のレスポンスでもデフォルトヘッダが含まれるようになった旨が記されています。
- 影響範囲・注意点
影響範囲
ActionController::Liveを利用したストリーミングレスポンス(例: SSE、チャンク配信など)に対して、Rails が設定するデフォルトヘッダが新たに付与されるようになります。- 通常の非 Live コントローラは従来通りで、挙動の変化はありません。
想定される変更点・注意事項
- これまで「Live レスポンスにだけ特定ヘッダが無い」ことを前提にしていた場合(ワークアラウンド・ハック等)、挙動が変わる可能性があります。
- 逆に、今まで「普通のコントローラと同じヘッダが付いているはず」と思って Live を使っていた場合、今回の修正で期待どおりの動作になります。
- デフォルトヘッダの上書きはこれまで通り可能で、
response.headers[...] = ...で変更できます。
互換性
- バグ修正の類であり、Rails が元々想定していた挙動(Live でも default_headers が乗る)に揃える変更です。
- 通常は後方互換性に問題はなく、むしろ不整合がなくなる方向の修正です。
- 参考情報 (あれば)
- 対応 Issue: Fixes #53402
「ActionController::Live のレスポンスに default_headers が適用されない」という報告に対する修正。 - コードを読む際の関連クラス:
ActionDispatch::ResponseActionDispatch::Response.createActionController::LiveActionController::Live::Response
#58735 Move timestamp_attributes_for into SchemaContext
マージ日: 2026/9/11 | 作成者: @hmcguire-shopify
- 概要 (1-2文で)
created_at/updated_atなどのタイムスタンプ属性の決定ロジックを、遅延評価から「スキーマ読み込み時に確定する」形に変更し、その処理場所をSchemaContextに移動した PRです。これにより Ractor 環境でも、メイン Ractor でスキーマがロードされていれば他 Ractor から安全にタイムスタンプ属性を利用できるようになります。
- 変更内容の詳細
2-1. 主な設計変更ポイント
これまで
timestamp_attributes_for_create/timestamp_attributes_for_updateは「必要になったときに」計算する遅延評価だった。- スキーマキャッシュのリロード時には、
reload_from_schema_cacheフックでこれらの値のリセット/再計算の面倒を見る必要があった。 - 遅延評価 + グローバルな状態は、Ractor のような並列・分離環境との相性が悪い(スレッド安全性・Ractor 間での共有など)。
これから
- 「スキーマがロードされたタイミング」で
timestamp_attributes_for_*を計算し、SchemaContext内に閉じ込める設計に変更。 - スキーマをリロードすると
SchemaContextインスタンス自体を捨てて作り直すため、個別にリセット用のフックを用意する必要がなくなる。 timestamp_attributes_for_create/timestamp_attributes_for_updateが public メソッド化 され、SchemaContext内部からも利用される。
- 「スキーマがロードされたタイミング」で
2-2. コードレベルでのイメージ
PR 内容から推測される設計イメージです(実際のコードとは多少異なる可能性があります):
# 例: activerecord/lib/active_record/model_schema/schema_context.rb
class ActiveRecord::ModelSchema::SchemaContext
def initialize(model)
@model = model
# スキーマロード時にタイムスタンプ属性を確定させる
@timestamp_attributes_for_create = model.timestamp_attributes_for_create
@timestamp_attributes_for_update = model.timestamp_attributes_for_update
end
attr_reader :timestamp_attributes_for_create,
:timestamp_attributes_for_update
end# 例: activerecord/lib/active_record/timestamp.rb
module ActiveRecord::Timestamp
# 以前は private に近い扱いだったものが public に
def timestamp_attributes_for_create
# created_at, created_on などを返す
end
def timestamp_attributes_for_update
# updated_at, updated_on などを返す
end
endポイント:
- 以前は各モデルクラスが必要になったタイミングで
timestamp_attributes_for_*を評価しキャッシュしていたのに対し、 - これからは「
SchemaContext生成時 (= スキーマロード時) に一度だけ評価して保存し、以後はそれを参照する」形になっています。
2-3. Ractor 対応の観点
- Ractor では「オブジェクトの共有」や「ミューテーション」が厳しく制限されているため、グローバルな遅延評価キャッシュのような仕組みは扱いが難しいです。
- この PR では、「スキーマロード時に必要な情報を全部
SchemaContextに閉じ込める」ことで、- メイン Ractor 上でスキーマをロード → 他の Ractor ではその確定済みデータを参照するだけ
- というパターンを取りやすくしています。
- 結果として、「Ractor を使う場合は、メイン Ractor 上でスキーマをロードしておけば、タイムスタンプ属性の解決で問題が起きにくい」という状態になります。
- 影響範囲・注意点
3-1. 公開 API と可視性の変化
timestamp_attributes_for_createtimestamp_attributes_for_update
が public メソッド になりました。
- もともと
ActiveRecord::Base/ActiveRecord::Timestampの内部ロジックとして使われていたメソッドですが、これを直接叩いているアプリ/ライブラリがある場合:- 以前よりも「公式に使ってよい」雰囲気の API に近づいたと言えます。
- とはいえ、まだ「完全に安定を保証された public API」という位置付けかは PR だけでは判断できないため、積極的依存は慎重に。
3-2. スキーマリロード周りの挙動
- これまで:
reload_from_schema_cacheフックで、タイムスタンプ関連のキャッシュのリセットをしていた。 - これから: スキーマをリロードすると
SchemaContextが作り直されるため、その中に保持していたタイムスタンプ属性も自動的に再計算される。 - カスタムで
reload_from_schema_cache周りをフックしていたり、タイムスタンプ用のキャッシュに直接触っていたようなメタプログラミングがある場合、そのロジックが不要もしくは動作しなくなる可能性があります。
3-3. Ractor を使うアプリでの注意
- メイン Ractor 以外から Active Record のモデルを使う前に、
- メイン Ractor でスキーマがロードされていること
- すなわち該当モデルのスキーマが確定済みであること
- を前提とした設計になっています。
- Ractor 内で初めてモデルを触るようなコードパスがあると、スキーマロードとタイムスタンプ属性の確定がどの Ractor で行われるかに依存して挙動が変わる可能性があるため、できるだけ「アプリ起動時のメイン Ractor でまとめてスキーマをロードする」運用が望ましいです。
- 参考情報 (あれば)
関連しそうなキーワード / コード:
ActiveRecord::ModelSchema::SchemaContextActiveRecord::TimestampActiveRecord::Base.timestamp_attributes_for_createActiveRecord::Base.timestamp_attributes_for_updatereload_from_schema_cache- Ruby Ractors と「事前計算されたイミュータブルなコンテキストオブジェクト」を共有する設計パターン
類似の変更は、マルチスレッド / Ractor 対応の一環として、他の ActiveRecord のメタデータ(カラム情報、タイプマッピングなど)にも波及していく可能性が高いです。
#58668 Introduce CoW when adding Active Record reflections
マージ日: 2026/9/10 | 作成者: @Edouard-chin
- 概要 (1–2文で)
- Active Record のリフレクション(関連付け情報)を Copy-on-Write (CoW) / deep freeze することで、Ractor 内から
Post.find,Post.whereなどを安全に実行できるようにする変更です。 - リフレクションキャッシュを凍結するために、各種メモ化を事前に評価しつつ、Ractor セーフな形でキャッシュを扱うように内部実装が見直されています。
- 変更内容の詳細
背景と課題
- Ractor はオブジェクト共有に厳しい制約があり、「凍結された共有可能なオブジェクト」以外は Ractor 間で共有できません。
- Active Record のリフレクション(
has_many,belongs_toなどの関連定義を表すオブジェクト)や、それを保持するキャッシュが「凍結されていない」ため、Ractor 内でPost.find,Post.whereを呼ぶと、リフレクションキャッシュを触るタイミングで Ractor の安全性要件に違反してしまっていました。 - この PR は、リフレクションとそのキャッシュを「deep freeze」かつ「Copy-on-Write 可能」な形に変更することで、Ractor からの利用を可能にするものです。
主な修正ポイント
1) Reflection オブジェクトの deep freeze
activerecord/lib/active_record/reflection.rb が大きく修正されており、リフレクションオブジェクトに含まれる内部状態を再帰的に凍結しています。
典型的には以下のようなものが対象になります:
- 関連名、クラス名などの文字列/シンボル
- オプションハッシュ (
foreign_key,class_nameなど) through_reflectionやsource_reflectionなど、複合的なリフレクション構造
これらを「deep freeze」することで、Ractor 間で安全に共有可能なオブジェクトになります。
※ PR の説明にあるとおり、多数の lazily memoized な値(初回アクセス時に計算・キャッシュされる値)があり、それらが「未計算のまま」 freeze されてしまうと、後から書き込みが発生してしまうため、freeze 前に eager load / 事前計算するようになっています。
2) Aggregated Reflection の freeze
- 単一の関連だけでなく、集約されたリフレクション(モデルが持つ全関連をまとめた構造)も deep freeze されます。
- これにより「モデルのリフレクション定義全体」が、Ractor から安全に読み取り専用でアクセスできるようになります。
3) メモ化の場所の移動
説明にある通り、1つのメモ化が別メソッドに移動しています。
典型的なパターンとしては:
- 以前は「利用側のメソッド」を呼んだときに初めてメモ化していたものを、
- freeze 処理の中、あるいはリフレクション初期化の別のタイミングで事前にメモ化するように変更
する、という形です。
理由:
- freeze 後にメモ化のための書き込みが発生すると Ractor セーフでなくなるため、
- freeze 前にすべての必要な memoized 値を構築しておく必要があります。
そのため「どこで初回計算を行うか」という責務の場所が変わっています。
4) Copy-on-Write (CoW) 的なキャッシュ扱い
PR タイトルにある CoW は、一般に以下のようなパターンを指します:
- 「共有される読み取り専用オブジェクト」(凍結されたリフレクションキャッシュ)をベースに、
- 必要に応じて、変更がある場合だけ、新しいキャッシュをコピーして生成する
Rails の内部では、リフレクション定義は通常アプリケーション起動後はほぼ不変のため、「一度 deep freeze したオブジェクトを読み取り専用で共有する」形がメインです。この PR によって、そうした「共有読み取り専用キャッシュ」が Ractor セーフに扱えるようになります。
5) Application 初期化/Ractor テスト周りの対応
railties/lib/rails/application.rbに、リフレクションを freeze / Ractor セーフにするための初期化処理が追加されている可能性が高いです(変更行数から推定)。- 例:
config.eager_load!またはアプリケーション起動時に、モデルのリフレクションを eager load してから freeze する、など。
- 例:
railties/test/application/ractors_test.rbに、Ractor 環境でPost.findなどが正常に動作するかを検証するテストが追加されています。- 「Ractor 内から Active Record クエリを実行し、例外なく動作する」ことを確認するテストになっていると考えられます。
6) その他の細かい変更
activerecord/lib/active_record/associations/builder/association.rb- 関連ビルダがリフレクション作成時に、freeze / CoW 方針に沿う形でオブジェクトを構築するよう微調整されています。
activerecord/lib/active_record/model_schema.rb- スキーマやカラム情報のメモ化周りとリフレクション初期化の順序調整、あるいは freeze との整合性を取るためのコードが追加されている可能性があります。
activerecord/test/cases/reflection_test.rb- リフレクションオブジェクトが deep freeze されていること、
- freeze 後も API 的に期待通りに動作すること、
- Ractor セーフティに関する前提条件が満たされること を確認するテストが多数追加されています。
- 影響範囲・注意点
影響範囲
- Active Record のリフレクション API 全般
- 例:
Post.reflect_on_association(:comments),Post._reflectionsなど。
- 例:
- Rails アプリケーションの起動フロー
- 初期化時にリフレクションを eager load / freeze する処理が入ることで、起動時のメモリレイアウトやタイミングが少し変わる可能性があります。
- Ractor を利用している/今後利用する予定のアプリケーション
- この変更により、基本的な Active Record 操作 (
find,whereなど) が Ractor 内で安全に使えるようになるため、Ractor ベースの並列処理を実用しやすくなります。
- この変更により、基本的な Active Record 操作 (
互換性・注意点
- リフレクションや内部キャッシュを直接いじっているアプリケーション/ライブラリは要注意です。
- 例:
model._reflections[:assoc].instance_variable_set(...)のような「凍結されたオブジェクトへの書き込み」を行っていると、今後はFrozenErrorが発生する可能性が高いです。 - カスタムプラグインやメタプログラミングでリフレクションを後から書き換えている場合、破壊的変更になり得ます。
- 例:
- lazily memoized なフィールドが「freeze 前に必ず初期化される」前提に変わっているため、
- 内部的な初期化タイミングに依存した hack をしているコードは挙動が変わる可能性があります。
- Ractor を使用する場合:
- モデル定義やリフレクションを「Ractor を起動する前に」確定させておく必要があります。
- Ractor 起動後に新しい関連を定義・変更するような動的変更は、freeze 済みキャッシュとの整合性の観点で問題を起こす可能性があります(これは一般的な Ractor の制約と整合的です)。
- 参考情報 (あれば)
- Ractor とオブジェクト共有のルール:
- オブジェクトを他の Ractor と共有するには
shareable?である必要があり、典型的には「完全に凍結されていること」が要件になります。
- オブジェクトを他の Ractor と共有するには
- 過去の Rails における Ractor 対応:
- この PR 以前にも、各種キャッシュ(例: Arel, スキーマ情報など)を freeze / Ractor セーフにする対応があり、本 PR は「リフレクション周り」という、Ractor 対応の最後に近い大物領域の1つをカバーしていると考えられます。
- 実運用上のポイント:
- Ractor を使った並列処理(例えばマルチ Ractor Web サーバやバッチ処理)を計画している場合、この変更が入っている Rails バージョンに上げることで、
ActiveRecord::Base周りの基本操作を比較的安全に活用しやすくなります。 - ただし、アプリ独自のグローバル状態やキャッシュも Ractor セーフにする必要があるため、アプリ全体としての設計見直しは依然として必要です。
- Ractor を使った並列処理(例えばマルチ Ractor Web サーバやバッチ処理)を計画している場合、この変更が入っている Rails バージョンに上げることで、
#58711 Don't write to the reloader class when there is nothing to reload
マージ日: 2026/9/10 | 作成者: @etiennebarrie
- 概要 (1-2文で)
Rails のリローダ(ActiveSupport::Reloader)が「実際にはリロードしない」設定の場合に、不要なインスタンス変数の書き込みを行わないようにし、Ractor(マルチスレッド/並列実行機構)から安全に利用できるようにした変更です。特に Active Job を Ractor セーフにする際に問題となる、「Ractor からの書き込み禁止」に抵触しないよう動作が調整されています。
- 変更内容の詳細
背景
active_job.set_reloader_hook初期化処理では、ジョブ実行時にリロード用フックを仕込むためActiveSupport::Reloader.wrapが呼ばれる。wrapの中では最終的にreloader.run!が呼ばれ、そのrun!がcheck!を呼ぶ。check!は内部で「リロードが必要かどうか」を判定し、その状態をモジュール内のインスタンス変数にキャッシュするような処理をしていた。- しかし Ruby の Ractor では「他 Ractor 由来のオブジェクトへの書き込み」が制限されるため、Ractor 内から
check!がインスタンス変数を書き換えるとエラーになる可能性がある。 - デフォルト設定(「決してリロードしない」)であっても、
||=により無条件にインスタンス変数への書き込みが発生していたため、Ractor からの実行で問題になる。
本 PR は「そもそもリロードしない設定であれば、インスタンス変数を書き換えない」ようにして、Ractor から呼ばれた場合でも書き込みを避けるようにするものです。
具体的な変更点
※ PR 本文と diff 概要からの推測を含みますが、挙動レベルでは以下のような変更です。
1. ActiveSupport::Reloader のロジック変更
これまで、リロード状態を管理するインスタンス変数に対して
@something ||= ...のような書き方をしていた部分があり、
「結果が『リロード不要・常に何もしない』であっても毎回(少なくとも初回は)インスタンス変数に書き込む」挙動になっていた。この PR では、以下のように分岐が追加・変更されていると考えられます:
- 「リロードが絶対に発生しない(例:
check!の結果が常に false / ‘never reload’)」とわかる場合:- インスタンス変数を書き込まず、即リターンする。
- 実際にリロードの可能性がある場合のみ、インスタンス変数に状態をキャッシュする(必要なときだけ書き込む)。
これにより、
- 通常プロセス(非 Ractor)では挙動はほぼ変わらず(キャッシュは必要な場合だけ書かれる)。
- Ractor ワーカーが「一度もリロードを必要としない」構成であれば、
check!が呼ばれてもインスタンス変数書き込みを行わないため、Ractor 制約にひっかからない。
- 「リロードが絶対に発生しない(例:
2. ActiveSupport::ExecutionWrapper の微修正 (+1行)
ExecutionWrapperはReloaderと組み合わせて、リクエストやジョブごとの実行コンテキストを管理するクラスです。ここに 1 行追加されているため、
Reloaderが「何もしない」ケースを考慮した条件分岐か、Ractor 実行時に余計なフックを入れないためのガードが入った可能性が高いです。例としては以下のようなイメージです(擬似コード):
rubyif reloader && reloader.active? reloader.run! endのような「有効なリローダがあるときだけ実行する」ガードが強化された形かもしれません。
3. Rails::Application::Finisher の修正 (-2行)
- アプリケーション起動時にリローダをセットアップする箇所から、不要なフック/設定が削られた可能性があります。
- ここでの変更は、ごく小さなクリーンアップで、実質的には挙動に大きな影響はないと考えられます(
Reloaderの新しい振る舞いに合わせて初期化コードを簡素化した程度)。
4. テストの追加 (activesupport/test/reloader_test.rb +21行)
「リロードが発生しない設定の場合に、インスタンス変数への書き込みが行われない」ことを検証するテストが追加されたと考えられます。
例として、以下のようなテストが考えられます(概念的なサンプル):
rubydef test_never_reload_does_not_write_state reloader = ActiveSupport::Reloader.new(check: -> { :never }) # Ractor 相当の制約下でも例外にならないことを確認 assert_nothing_raised do reloader.check! end # 内部状態が変化していない / 書き込みが行われないことを確認 # (具体的にはインスタンス変数の存在チェックやフラグ値など) end
- 影響範囲・注意点
ActiveSupport::Reloader を独自に拡張している場合
check!/run!の呼ばれ方や、内部状態のキャッシュタイミングに依存したコードを書いていると、挙動が変わる可能性があります。- 特に「デフォルトでは必ずインスタンス変数がセットされていること」を前提にしているようなメタ的なコードがあると影響を受けます。
Ractor を使っている / 使う予定がある Rails アプリやライブラリ
- この PR により、「リロードが不要な Ractor ワーカー」であれば、Active Job などを安全に使いやすくなります。
- ただし、Ractor 内で「実際にリロードが必要」な構成(コード変更を検知して再読み込みするようなもの)は、依然として注意が必要です。Reloader 自体が Ractor 完全対応になったというより、「リロード不要パスでは書き込みしない」ようにした改善です。
本番環境(通常は class caching / eager load on)
- 本番モードではほとんどのアプリが「リロードしない」設定のため、この変更により若干の安全性(Ractor 目線)と、ごく僅かなパフォーマンス(無駄なインスタンス変数書き込みが減る)改善が見込めます。
- ただし、体感できるほどの差ではなく、主目的は正しさと Ractor 互換性です。
- 参考情報 (あれば)
- 対象 PR: https://github.com/rails/rails/pull/58711
- 関連するコンポーネント:
ActiveSupport::ReloaderActiveSupport::ExecutionWrapperRails::Application::Finisher- Active Job の reloader hook (
active_job.set_reloader_hook)
- Ruby Ractor の制約:
- 他 Ractor 所有オブジェクトのインスタンス変数書き込み禁止
- 共有されるオブジェクトは「イミュータブルもしくは Ractor シェア可能」な構造でなければならない
開発者目線では、「Reloader/ExecutionWrapper を Ractor から使う際に、リロード不要なら書き込みが発生しない」という保証が追加されたと理解しておくとよいです。
#58729 Clarify article casing in guide headings
マージ日: 2026/9/10 | 作成者: @p8
- 概要 (1-2文で)
Railsガイドの執筆ガイドラインにおいて、見出し(heading)内の英語の冠詞("the"、"a"、"an")は常に小文字で書く、というルールを明確化したPRです。あわせて「Internal articles」という、文法用語として不正確な表現を修正しています。
- 変更内容の詳細
対象ファイル:
guides/source/ruby_on_rails_guides_guidelines.md
行レベルでは +2 / -2 の非常に小さな修正で、内容としては以下のようなドキュメント内の文言変更が行われています。
主なポイント:
- 見出しのタイトルケースに関するガイドラインの表現調整
- 「Internal articles」というあいまい/誤った用語の削除または言い換え
- "the"、"a"、"an" は見出し中であっても常に小文字にすることを明示
イメージとしては、ガイドラインの一節が次のような方針になっていると考えられます(擬似例):
### Headings
- Use title case for headings.
- Articles ("the", "a", "an") are always lowercase, even in titles.これにより、Railsガイドの見出しを書く際には、次のようなスタイルが推奨されます。
- 良い例:
Using the Asset Pipeline - 悪い例:
Using The Asset Pipeline("The" を大文字にしない方針)
- 影響範囲・注意点
- 影響範囲は Rails 本体の挙動や API ではなく、「Rails Guides の執筆者向けガイドライン」のみです。
- 既存のガイド文書の見出しスタイルとの整合性をとる必要があるため、今後ガイドを更新・追加する際は、見出し内の "the"、"a"、"an" を小文字で統一することが推奨されます。
- 文法用語として誤解を招く「Internal articles」という表現がなくなったことで、英語ネイティブ/非ネイティブ問わず、ルールの理解がしやすくなります。
- コード、テスト、挙動には一切影響がなく、本番環境へのデプロイ可否判断にも影響しない「ドキュメントのみ」の変更です。
- 参考情報 (あれば)
- 一般的な英語のタイトルケース・スタイルガイド(参考として)
- Chicago Manual of Style などでは、冠詞 ("a"、"an"、"the") は通常小文字とするルールが採用されています。
- Railsガイド執筆時には、
ruby_on_rails_guides_guidelines.md全体を確認し、見出しだけでなく用語統一やコード例のスタイルもあわせて従うとよいです。
#58719 Speed up dispatch of BroadcastLogger
マージ日: 2026/9/10 | 作成者: @jhawthorn
- 概要 (1-2文で)
BroadcastLogger が、特にlogger.debugのような「実際にはログが出ない」ケースでより高速に動作するように最適化されました。Ruby 4.0+ の引数転送最適化を活かしつつ、不要なオブジェクト生成(配列・Proc など)を削減する変更です。
- 変更内容の詳細
背景
- Rails ではデフォルトの logger として
ActiveSupport::BroadcastLoggerが使われ、通常は「実ログを書き出す logger」が 1 個ぶら下がる構成になっています。 - I/O を伴う「実ログ出力」はどうしても遅いですが、
logger.levelが info 以上などでlogger.debugが「何もしない」パスはアプリ全体で非常に多く呼ばれ得るため、ここをいかに速くするかが重要になります。
主な最適化ポイント
単一 logger への委譲を
sendではなくメソッド直接呼び出し+引数転送に変更これまで:
- メソッド名をシンボルにして
sendしたり - 可変長引数を配列に詰めたり
- kwargs をハッシュ化したり
といったオーバーヘッドがありました。
変更後:
- Ruby 4.0+ で最適化されている「引数転送(
def foo(...); bar(...); endのような形)」を用いて、対象 logger のメソッドを「ほぼそのまま」呼び出すようにして、配列やハッシュの中間生成、sendによるメソッドルックアップをスキップしています。
イメージ(疑似コードレベル):
ruby# 以前のイメージ def add(*args, **kwargs, &block) @loggers.each do |logger| logger.send(:add, *args, **kwargs, &block) end end # 変更後のイメージ(単一 logger のとき) def add(...) @loggers.first.add(...) end※実際の実装は BroadcastLogger の内部のメソッド全般(
debug,info,addなど)に対して同様のパターンが適用されている、と考えてよいです。- メソッド名をシンボルにして
複数 logger + ブロック付き呼び出し時の block 呼び出し回数制御の最適化
- BroadcastLogger は
logger.debug { expensive_message }のようにブロックを渡したとき、- 各 logger ごとに
debugが有効かどうか確認し、 - 少なくとも 1 つの logger がブロックを使うなら、そのブロックは 最大 1 回だけ 実行したい
(高コストな文字列生成や計算を複数回行わないため)。
- 各 logger ごとに
- 従来実装では、この「一度だけ評価して結果を使いまわす」ために、
Procを生成したりmapで配列を作ったりといったオーバーヘッドがありました。
変更後:
- 明示的に
Procを生成せず、通常のブロックとローカル変数によるメモ化ロジックに変更。 mapによる中間配列を作成せず、単純なループで logger ごとに処理するように変更。
擬似コードイメージ:
ruby# 以前のイメージ(概念的に) memo_proc = Proc.new do @memo ||= block.call end @loggers.map do |logger| logger.debug { memo_proc.call } end # 変更後のイメージ value = UNDEFINED = Object.new @loggers.each do |logger| logger.debug do if value.equal?(UNDEFINED) value = block.call end value end endこれにより、
Procオブジェクト 1 個分mapによる配列 1 個分
の不要なアロケーションが削減されます。
- BroadcastLogger は
「何もしない」ケースの高速化
特に以下のケースが高速化されます:
ruby# 典型的なパターン: Rails.logger.level = Logger::INFO # 大量に呼ばれる Rails.logger.debug("some debug message") # -> 実際には何も出ない- ログレベルが原因で実出力がスキップされる場合、block の評価すら行われないケースでは、今回の最適化でほぼ「条件分岐+軽いメソッド呼び出し」程度のコストに抑えられるようになります。
- 影響範囲・注意点
- 公開 API の挙動は変わらない想定
ActiveSupport::BroadcastLoggerの外部に見えるインターフェイス(メソッド名、引数の受け取り方、ログ出力の結果)は変わりません。- 変更はあくまで内部実装(メソッドの委譲方法とブロックの扱い方)の最適化です。
- ブロック付きログの意味論は維持
- 少なくとも 1 つの logger がそのログレベルを有効にしている場合にのみブロックが評価される、
- かつ、複数 logger がいてもブロックは最大 1 回しか実行されない、
という従来の保証は保たれています。
- 複数 logger を使う場合の挙動
- logger が増えても、ブロック評価は依然として 1 回までで、今回の変更によりオーバーヘッドも減っています。
- ただし logger の数が増えれば、その分のメソッド呼び出し回数自体は増える点は変わりません(BroadcastLogger の性質上当然の範囲)。
- Ruby 4.0+ 最適化前提の部分がある
- 説明文から、Ruby 4.0 以降の引数転送の最適化を期待して実装されていることがわかります。
- Rails のサポート Ruby バージョン範囲内では互換性を保つように書かれているはずですが、最大の効果は Ruby 4.0+ で出ると考えられます。
- 参考情報 (あれば)
- 対象ファイル:
activesupport/lib/active_support/broadcast_logger.rb- BroadcastLogger のメソッド委譲ロジック(
add,debug,infoなど)とブロック処理部分が主に変更されているはずです。
- BroadcastLogger のメソッド委譲ロジック(
- 類似の最適化パターン:
- 単純なラッパー/デコレータクラスで
def foo(...); inner.foo(...); endという形の転送を使うと、Ruby 4.0+ で非常に最適化されるので、他のラッパークラスやミドルウェア実装でも応用可能です。
- 単純なラッパー/デコレータクラスで
- 実運用での期待効果:
- 大量の
logger.debug/logger.info呼び出しがあるアプリケーションで、ログレベルが高めに設定されているときの CPU 利用量低下・スループット向上が期待できます。
- 大量の
#58722 [ci-skip][doc] Fix Action Cable guide for connection callbacks
マージ日: 2026/9/10 | 作成者: @hachi8833
- 概要 (1-2文で)
Action Cable の「connection callbacks」に関するドキュメントの説明が実装と逆の意味になっていたため、「クライアントへの送信時」ではなく「クライアントからの受信時」にコールバックが呼ばれることを正しく反映する修正が行われました。併せてActionCable::Connection::Callbacksのコメントも同様に修正されています。
- 変更内容の詳細
ドキュメントの修正
Action Cable ガイドの該当箇所が次のように修正されています(英語原文):
[`ActionCable::Connection::Callbacks`][] provides callback hooks that are
-invoked when sending commands to the client, such as when subscribing,
+invoked when receiving commands from the client, such as when subscribing,
unsubscribing, or performing an action:ポイント:
- これまで「クライアントにコマンドを送信するときに呼ばれる」と説明されていたが、実際には
- クライアントからの subscribe / unsubscribe / perform などのコマンドを受信したとき にコールバックが実行される。
- 「sending → receiving」「to the client → from the client」という方向の誤りを修正しただけで、API 仕様自体の変更はない。
実装との対応づけ
PR では、実際にコールバックが呼ばれている箇所として ActionCable::Connection::Base#handle_channel_command が示されています:
def handle_channel_command(payload)
run_callbacks :command do
subscriptions.execute_command payload
end
endここで:
payloadはクライアントから送られてきたコマンド(subscribe / unsubscribe / perform など)の内容。run_callbacks :commandがActionCable::Connection::Callbacksで定義された:commandコールバックを実行する。- つまり、「クライアントからチャンネルに対するコマンドを受け取ったときにコールバックが走る」という動きになっており、今回のドキュメント修正内容と整合している。
actioncable/lib/action_cable/connection/callbacks.rb のコメントもガイドと同趣旨で「receiving commands from the client」という表現に合わせて修正されています(コードロジックの変更はなし)。
- 影響範囲・注意点
- 影響するのは ドキュメントとコメントのみ であり、Action Cable の挙動・API は一切変わりません。
- これまでドキュメントを読んで「サーバ側からクライアントへ送るタイミングでコールバックが走る」と誤解してコールバックを設計していた場合、実際の挙動と合っていない可能性があります。
:commandコールバックは「クライアントから来るコマンドを処理する直前/直後のフック」として使用するのが正しい前提です。
- 7.1〜8.1 の安定ブランチのガイドにも同じ誤りがあるため、これらのブランチにもバックポートされる予定です:
- 8-1-stable
- 8-0-stable
- 7-2-stable
- 7-1-stable
実際のアプリケーションコードに対して:
- もし
ActionCable::Connection::Callbacksを用いてset_callback :command, ...などを定義している場合、そのコールバックは「クライアントからのコマンド受信時」に動いていることを前提として確認してください。 - 送信時(
broadcastなど)にフックをかけたい場合は、別途適切なレイヤー(チャンネルクラス、ブロードキャスト処理など)にコールバックやラッパーを実装する必要があります。
- 参考情報 (あれば)
- 該当ガイド(修正対象セクション)
https://guides.rubyonrails.org/v8.0/action_cable_overview.html#connection-callbacks - コールバックが呼ばれている実装箇所
https://github.com/rails/rails/blob/52fa23ce8e1d39ff281bf300867e9ba7c7d66111/actioncable/lib/action_cable/connection/base.rb#L106 - 日本語版ガイドでの関連 Issue
https://github.com/yasslab/railsguides.jp/issues/1972
#58717 [ci skip] Use supported positional enum form in Calculations guide example
マージ日: 2026/9/9 | 作成者: @dersam
- 概要 (1-2文で)
Active Record Querying ガイド内のenumのサンプルコードが、現在の Rails で非対応な書き方になっていたため、サポートされている「位置引数形式(positional enum form)」に修正したドキュメント専用の変更です。コード本体には変更はなく、ガイドの記述のみが更新されています。
- 変更内容の詳細
- 対象ファイル:
guides/source/active_record_querying.md - 変更内容は 1 行の置き換えのみで、Active Record の「Calculations」セクション中の
enum宣言の例が修正されています。
従来のガイド例(イメージ):
# こういった形式の enum 例が載っていた
enum status: [:draft, :published, :archived]
# もしくは
enum status: ["draft", "published", "archived"]このような「status: [...]」という宣言は、現在の Rails が要求する「位置引数形式(positional enum form)」とは互換性がなくなっている旨が PR 説明から読み取れます。
修正後のガイド例(イメージ):
# サポートされている positional enum form に合わせた形へ変更
enum :status, [:draft, :published, :archived]
# あるいは
enum :status, { draft: 0, published: 1, archived: 2 }要するに、enum status: [...] のようなキーワード引数ベースの書き方から、enum :status, [...] のように第一引数でカラム名を指定する「位置引数形式」に変更した、という内容です。
- 影響範囲・注意点
- この PR は ガイド文書のみの変更 であり、Rails の動作や API 仕様自体を変更するものではありません。
- ただし、これまでガイドの古いサンプルをそのままコピーして使っていた場合:
enum status: [:draft, :published]のような記述は、現在の Rails では非推奨または動作しないケースがあるため、- ガイドと同様に
enum :status, [:draft, :published]もしくはenum :status, { draft: 0, published: 1 }のような「位置引数形式」に移行する必要があります。
- 特に Rails 7.1 以降では enum の内部実装・形式に変更が入っており、enum 定義のシンタックスやマッピング方法が厳密になっているため、公式ガイドの最新版に合わせた書き方を採用するのが安全です。
- 参考情報 (あれば)
- Rails ガイド: Active Record Querying – Calculations セクション
https://guides.rubyonrails.org/active_record_querying.html#calculations - Rails ガイド: Active Record Enum
https://guides.rubyonrails.org/active_record_querying.html#enums(またはActive Record Basicsガイド内の Enum 節) enumの推奨書き方(例):rubyclass Post < ApplicationRecord enum :status, { draft: 0, published: 1, archived: 2 } end
#58337 Add alternative text to Action Text attachments
マージ日: 2026/9/9 | 作成者: @lylo
- 概要 (1-2文で)
Action Text の添付画像にalt(代替テキスト)属性を正式に持たせ、保存〜レンダリングまでラウンドトリップできるようにした PR です。これにより、スクリーンリーダー向けの説明文をキャプションとは独立して扱え、WCAG 1.1.1 対応もしやすくなります。
- 変更内容の詳細
2-1. これまでの問題点
Action Text には
alt属性の概念がなく、画像添付はキャプション(caption)のみサポートされていました。デフォルトの blob 部分テンプレートは以下のようになっており、
altは一切渡されていませんでした。erb<%= image_tag blob.representation(resize_to_limit: ...) %>image_tagは Rails 5.1 以降、デフォルトで alt を自動生成しません。actiontext/配下にはaltに関するコードが一切なく、結果として Action Text の画像はすべてalt属性なしで描画されていました(コンテンツ画像としては WCAG 1.1.1 違反)。エディタ側(例: Basecamp の Lexxy)はすでに
altを HTML に含めて Rails に送信しているにもかかわらず、Action Text がレンダリング時にその情報を捨てていました。render_attachments内でwith_full_attributes→process_attributesという流れの中で、ATTRIBUTESにないキーは削除されるため。
結果として:
- 画面読上げ用の説明を付けたくても、サーバ側で消えてしまう。
- API/Micropub などからプログラム的に投稿を作成する場合も、説明文を「キャプションとして表示する」か「捨てる」かの二択しかない。
- ワークアラウンドとしては凍結された
ATTRIBUTES定数を initializer で reopen するしかなく、非推奨かつ壊れやすい方法になっていました。
2-2. 今回の追加・変更点
1) 添付属性に alt を正式追加
ActionText::Attachment::ATTRIBUTESにaltを追加しました。- これにより、
altは:- エディタから送られてきた HTML → Action Text の内部表現への取り込み
- DB 等への保存
- レンダリング前の
with_full_attributes/process_attributes経由の再構築
の全行程で保持されます(=ラウンドトリップ可能に)。
- これにより、
ActionText::Attachment#altメソッドが#captionと並ぶ形で追加されています。- Ruby コードからも
attachment.altで代替テキストにアクセス可能です。
- Ruby コードからも
2) Blob 部分テンプレートで alt を image_tag に渡す
actiontext/app/views/active_storage/blobs/_blob.html.erbが変更され、altがimage_tagに渡されるようになりました。変更イメージ(擬似コード):
erb<%= image_tag blob.representation(resize_to_limit: ...), alt: attachment.alt %>altを持たない添付については従来どおりalt属性自体を出力しません。altを空文字(alt="")で出力しないのは、#30096 で議論された「誤った装飾扱いを避ける」方針に沿ったものです。altがない画像は監査ツールで検出でき、開発者が明示的に対応できます。
3) プログラムからの利用例
ActionText::Attachment.from_attachableでaltを直接指定できるようになりました。rubyActionText::Attachment.from_attachable( blob, alt: "A racecar on a track", # 画面読上げ用の説明 caption: "Vroom vroom" # ページ上に表示されるキャプション )altとcaptionは完全に独立:- どちらか片方のみ / 両方 / どちらもなし、いずれのケースも許容されます。
4) テスト・ドキュメント
actiontext/test/unit/attachment_test.rbalt属性の存在、captionとの独立性、with_full_attributes通過後も値が保持されることをテスト。
actiontext/test/integration/controller_render_test.rb- 実際のレンダリング結果として:
altが設定されている場合はalt="..."が出力されること- 設定されていない場合は
alt属性自体が出力されないこと を確認。
- 実際のレンダリング結果として:
actiontext/CHANGELOG.mdに機能追加として追記。
5) 対象外: TrixAttachment
ActionText::TrixAttachmentは非推奨であるため、この PR ではあえて何も触れていません。- 今後も新しいパス(Lexxy 等)での利用が前提です。
- 影響範囲・注意点
3-1. 影響範囲
Action Text をそのまま使っていて、blob 部分テンプレートを上書きしていないアプリ
- 今後、エディタまたはサーバ側から
altを設定すれば、そのまま<img alt="...">として出力されます。 - 既存コンテンツで
altを設定していなければ、出力はほぼこれまで通り(alt属性なし)です。
- 今後、エディタまたはサーバ側から
Lexxy など、
altをすでに送っているエディタ- これまでは Action Text 側で
altを破棄していましたが、この PR 以降は保持され、View に渡されます。 - ただし Lexxy 自体は現状、
altTextをファイル名から自動生成しており UI で編集できないため、そのままだと「ファイル名を読ませるだけ」の状態です。- PR ではこの挙動を変えていないため、エディタ側の改善(UI 追加やデフォルト値の変更)が今後の課題になります。
- これまでは Action Text 側で
API や Micropub 経由で投稿を受けるアプリ
- 画像の説明(代替テキスト)とキャプションを別々に受け取り、そのまま別フィールドとして保存・表示できる土台が整います。
3-2. 注意点
altがなければ何も推測しない- ファイル名から
altを推論するような処理は行いません(#30096 の方針を維持)。 - 代替テキストが必要な画像には、アプリケーションまたはエディタ側で明示的に
altを設定する必要があります。
- ファイル名から
alt=""は自動では付かないalt未指定時にalt=""を出してしまうと「装飾画像」と解釈されるため、本 PR では一切出力しません。- 装飾画像として扱いたいケースでは、アプリケーション側で明示的に
alt: ""を設定する、あるいは独自テンプレートでの制御を検討してください。
既存の initializer ハックとの二重管理に注意
- すでに initializer で
ActionText::Attachment::ATTRIBUTESを reopen して:altを足している場合、今回の変更によりそのハックは不要になります。 - 将来的な衝突や予期せぬ挙動を避けるため、そのような initializer は削除または見直しを推奨します。
- すでに initializer で
カスタム blob 部分テンプレートを使っている場合
actiontext/app/views/active_storage/blobs/_blob.html.erbをアプリ側でオーバーライドしている場合は、altを受け取ってimage_tagに渡すコードを自分で追加する必要があります。例:
erb<%= image_tag blob.representation(resize_to_limit: [1024, 768]), alt: attachment.alt %>
- 参考情報 (あれば)
- 関連 Issue / PR
- #54179: 追加の添付属性用フックを求めるディスカッション
- #30096: ファイル名由来
altテキストの削除(誤誘導となる代替テキストを避ける趣旨)
- アクセシビリティ基準
- WCAG 2.1 1.1.1 非テキストコンテンツ: コンテンツ画像には適切な代替テキストが求められる
- 対応の指針
- この PR は「
altを運べるようにする」ことが主眼であり、「良い代替テキストをどう入力・管理するか」はエディタやアプリケーション側の実装に委ねられています。
エディタ側での UI 追加・API 仕様の更新・既存コンテンツ移行などは別途検討が必要です。
- この PR は「
#58718 Remove the dead racc compile task from Action Pack
マージ日: 2026/9/9 | 作成者: @dersam
- 概要 (1-2文で)
Action Pack のRakefileから、既に存在しない.y文法ファイル向けの racc コンパイルタスク(compile)が削除されました。これにより、何もしていない「死んだ」タスクが整理され、Rake タスク定義が実態に揃えられます。
- 変更内容の詳細
- 対象ファイル:
actionpack/Rakefile - 変更内容: 6行削除(追加なし)
削除されたのは、lib/action_dispatch/journey/parser.rb を racc で生成することを想定したルールと、それに紐づく compile タスクです。
以前は以下のような前提がありました:
lib/action_dispatch/journey/parser.y(racc 用の文法ファイル) からlib/action_dispatch/journey/parser.rbを生成する Rake ルール・タスクが存在
しかし、その .y ファイルは 2024 年 8 月の PR (#52610) で既に削除済みです。
それにもかかわらず Rake 側の「コンパイルルール」と compile タスクだけが残っていたため、bundle exec rake compile を実行しても:
$ bundle exec rake compile --trace
** Invoke compile (first_time)
** Invoke lib/action_dispatch/journey/parser.rb (first_time, not_needed)
** Execute compileのように、parser.rb がすでにディスク上に存在するため「not_needed」と判定され、実際には何も生成・更新されないタスクになっていました。
この PR では、その「実質 no-op となっている racc コンパイルルールと compile タスク」が丸ごと削除されています。
- 影響範囲・注意点
- 影響範囲:
- Action Pack の開発用 Rake タスク (
actionpack/Rakefile) のみ - gem 利用者のアプリケーションに対するランタイム挙動や public API には影響しません。
- Action Pack の開発用 Rake タスク (
- 注意点:
- これまで Action Pack 開発時に
bundle exec rake compileを habit で叩いていた場合、該当タスクが存在しなくなる、または何も行わない/他の compile タスク構成に委ねられる可能性があります。 lib/action_dispatch/journey/parser.rbは、もはや Rake 経由で生成される想定ではなく、すでにリポジトリにコミット済みの Ruby ファイルとして扱われます(.yからの再生成フローは削除済み)。- 独自に
.yを復活させてローカルで racc 生成していたような特殊な開発フローがあれば、今後は自前で Rake タスクやスクリプトを用意する必要があります。
- これまで Action Pack 開発時に
- 参考情報 (あれば)
- この PR で対象となった
.y削除の元 PR:- rails/rails#52610 — Journey parser の
.y文法ファイル削除
- rails/rails#52610 — Journey parser の
- 関連ファイル:
lib/action_dispatch/journey/parser.rb— もはや生成物ではなく、ソースとして扱われるパーサ実装ファイル
- racc / Rake ルールの挙動:
- Rake は「ターゲットファイルが既に存在し、かつ依存元(ここでは
.y)が存在しない場合、そのタスクは実行不要 (not_needed)」と判断するため、この種の「片側だけ残った生成タスク」は自然と no-op になります。本 PR はその死んだルールを正式に削除した整理 PR と言えます。
- Rake は「ターゲットファイルが既に存在し、かつ依存元(ここでは
#58712 Remove the deprecated guides:generate:kindle task
マージ日: 2026/9/9 | 作成者: @dersam
- 概要 (1-2文で)
Rails のガイド生成用 Rake タスクguides:generate:kindleが、非推奨期間を経て完全に削除されました。今後は e-reader 向けフォーマットとしてguides:generate:epubのみが正式にサポートされます。
- 変更内容の詳細
Rake タスク周り
guides/Rakefileから、以下の非推奨タスクが削除されました:guides:generate:kindle
このタスクは Rails 7.2 で削除予定とされており、現在の RAILS_VERSION が 8.2.0.alpha に上がったタイミングで、予定どおりコードベースから取り除かれています。
元々 guides:generate:kindle は Kindle 用フォーマット生成を行うためのタスクでしたが、機能としては事実上メンテされておらず、ガイドも含めて廃止のアナウンスがされていました。
これに対して、EPUB 生成用のタスク:
bundle exec rake guides:generate:epubは引き続きサポートされ、今後も e-reader 用フォーマットを作る際はこちらを利用することが推奨されます。
ガイドライン文書の更新
guides/source/ruby_on_rails_guides_guidelines.mdにおいて、ガイド生成に関する記述が Kindle タスク削除に合わせて更新されています。- 具体的には、Kindle 関連の言及が削除または修正され、EPUB を利用する形に統一されています。
- 執筆者・メンテナ向けガイドラインが、現状のサポートされているタスク (
guides:generate:epub等) に即した内容になりました。
- 影響範囲・注意点
影響を受けるのは、Rails のガイド(ドキュメント)を自前でビルドしている開発者・ドキュメントメンテナにほぼ限定されます。
具体的な注意点:
これまで以下のようなコマンドを使っていた場合:
bashbundle exec rake guides:generate:kindleRails 8.2.0.alpha 以降ではこのタスクは存在しないため、実行すると「タスクが見つからない」エラーになります。
置き換えは基本的に EPUB 形式の利用になります:
bashbundle exec rake guides:generate:epubKindle 端末で閲覧したい場合は、EPUB をベースに:
- Kindle が対応するバージョンであれば EPUB をそのまま利用する
- あるいは Calibre などサードパーティツールで EPUB → MOBI/KFX へ変換する といったワークフローに切り替える必要があります。
Rails アプリケーション本体の機能や、一般的なアプリケーション開発には影響はありません。
これはあくまで Rails プロジェクト内の「ガイド生成用ツールチェイン」の整理です。
- 参考情報 (あれば)
- この PR:
- Rails GitHub:
rails/railsリポジトリの PR #58712
「Remove the deprecated guides:generate:kindle task」
- Rails GitHub:
- 関連タスク:
- 公式に残る e-reader 向けタスク:
guides:generate:epub
- 公式に残る e-reader 向けタスク:
- バージョンポリシー上の位置づけ:
guides:generate:kindleは Rails 7.2 で削除予定とされており、実際の削除は Rails 8.2.0.alpha のタイミングで実施されたことになります。
#58707 [ci skip] Correct the prepared statements opt-out config key in guides
マージ日: 2026/9/9 | 作成者: @dersam
- 概要 (1-2文で)
Railsガイド内で、Prepared Statements を無効化する設定キー名が誤記されていたため、正しいキー名に修正したドキュメントのみの変更です。アプリケーションコードや挙動そのものには影響しません。
- 変更内容の詳細
ガイド「Configuring Rails Applications」(guides/source/configuring.md) の中で、Active Record の Prepared Statements を無効化する設定キーが誤って記載されていました。
誤り(修正前):
config.active_record.disable_preprared_statments = true正(修正後):
config.active_record.disable_prepared_statements = truedisable_preprared_statments という誤綴りが、コードベースで実際に使われている disable_prepared_statements に修正されています。
PRタイトルに [ci skip] が含まれていることからも分かるとおり、テストやコードは変更されておらず、ドキュメントのみの修正です。
- 影響範囲・注意点
影響範囲
- Rails ガイドを参考に設定を書いた開発者が、誤ったキー名を使っている可能性があります。
- Rails 本体の挙動には変更なし(すでに
config.active_record.disable_prepared_statementsが正式なキーとして実装済み)。
注意点(自プロジェクトで確認すべきこと)
config/application.rbやconfig/environments/*.rbで、次のように書いていないか確認してください:ruby# 間違い: config.active_record.disable_preprared_statments = true- 正しくは以下の通りです:ruby
# 正しい: config.active_record.disable_prepared_statements = true - 誤ったキー名を使っている場合、その設定は一切効いておらず、Prepared Statements が期待通りに無効化されていなかった可能性があります。
Prepared Statements を明示的に無効にしたい場合は、必ず disable_prepared_statements を使用してください。
- 参考情報 (あれば)
- 該当の実際の設定オプション(参考イメージ):ruby
# config/environments/production.rb など Rails.application.configure do # Prepared Statements を無効化 config.active_record.disable_prepared_statements = true end - この設定は、DBごとの Prepared Statements の挙動やパフォーマンス・キャッシュ戦略を調整したい場合に用いられます。
- PR自体はドキュメント修正のみのため、Railsのバージョンアップ時にコード変更は不要ですが、既存設定の誤記には注意が必要です。
#58708 [ci skip] Fix MissingAttributeError reference in querying guide
マージ日: 2026/9/9 | 作成者: @dersam
- 概要 (1-2文で)
このPRは、Active Record クエリガイド内の誤ったクラス名ActiveRecord::MissingAttributeErrorを、正しいActiveModel::MissingAttributeErrorへ修正するドキュメント更新です。コード自体の挙動には一切変更がなく、ガイドの技術的正確性を高めるための修正となります。
- 変更内容の詳細
- 対象ファイル:
guides/source/active_record_querying.md - 変更内容:
- 説明文中で参照されていた例外クラス名を、diffに変更。
- ActiveRecord::MissingAttributeError + ActiveModel::MissingAttributeError
- 説明文中で参照されていた例外クラス名を、
Rails では、「存在しない属性にアクセスした際」に発生する例外は ActiveModel::MissingAttributeError であり、ActiveRecord::MissingAttributeError というクラスは存在しません。
そのため、クエリガイドの説明(例: select で列を限定したあとに未取得のカラムにアクセスしたときに起きるエラーの説明など)で、正しく ActiveModel::MissingAttributeError を指すように修正しています。
想定される文脈の例(※イメージ)は以下のようなものです:
user = User.select(:id).first
user.name
# => ActiveModel::MissingAttributeError が発生するこのとき、ガイドが誤って ActiveRecord::MissingAttributeError と書いていたのを修正した、という内容です。
- 影響範囲・注意点
- 影響範囲
- Rails の挙動・API・エラークラス構造には一切変更なし。
- Rails Guides(ドキュメント)だけの修正であり、実アプリケーションコードには影響ありません。
- 注意点
- これまで
ActiveRecord::MissingAttributeErrorというクラスがあるかのようにガイドを読んで認識していた場合は、実際にはActiveModel::MissingAttributeErrorが使われている点を再認識する必要があります。 - もしアプリ側の rescue などで仮に
ActiveRecord::MissingAttributeErrorを記述していた場合、そのコードは現状そもそも動作していない(例外クラスが存在しない)ので、正しくはActiveModel::MissingAttributeErrorを rescue する必要があります。
- これまで
- 参考情報 (あれば)
- 該当例外クラス:
ActiveModel::MissingAttributeError- Active Model で未定義・未ロードの属性にアクセスした際に発生する例外。
- 関連ドキュメント:
- Rails Guides: Active Record Querying(クエリインターフェイスガイド)
- API ドキュメント:
ActiveModel::MissingAttributeError(同名クラスが ActiveRecord 名前空間には存在しないことに注意)
#58695 Allow exception backtraces from Ractor workers
マージ日: 2026/9/8 | 作成者: @etiennebarrie
- 概要 (1-2文で)
このPRは、Ractor ワーカー内でリクエスト処理中に例外が発生した場合でも、通常のマルチスレッド環境と同様に Rails が例外およびバックトレースを正しくログ・レポートできるようにする対応です。Ractor が要求する「shareable(共有可能)」制約を満たすように、エラーレポータ・ロガー・バックトレースクリーナなどの実装が調整されています。
- 変更内容の詳細
※PR本文に具体的な差分は書かれていませんが、説明文と変更ファイルから読み取れる範囲で整理しています。
2-1. Ractor で例外ログが出ない原因の整理
作者が挙げている問題点は以下です。
エラーレポータが非 shareable な値を参照していた
Rails::VERSION::STRINGRails.envこれらが Ractor 間で共有できないオブジェクト(例: 可変文字列など)となっていて、Ractor 内で利用するとエラーになりうる状態でした。
ロガーが
BroadcastLoggerでラップされており shareable でない- Rails の
Rails.loggerはしばしばActiveSupport::BroadcastLogger(複数のログ出力先にブロードキャストするラッパー)で包まれています。 BroadcastLogger自体が Ractor-shareable ではない構造を持っており、そのまま Ractor 内で使えないため、Ractor ワーカー内で例外が発生しても適切にログ出力できませんでした。
- Rails の
Event Reporter は Ractor セーフだが、LogSubscriber に購読されていなかった
- 別 PR (#58599) で Event Reporter 自体は Ractor セーフに作り直されていました。
- しかし Ractor ごとに新しいインスタンスを作る構造になっており、LogSubscriber(
ActionController::LogSubscriberなど)に紐付けられていなかったため、イベントベースのログが届かない問題がありました。
バックトレースクリーナが非 shareable な文字列を参照していた
RbConfig::CONFIG["rubylibdir"]Rails::BacktraceCleaner.rootこれらが unfrozen な文字列であり、Ractor-shareable でなかったため、Ractor 内でバックトレースをクリーンアップしようとすると問題が生じる可能性がありました。
これらが組み合わさり、「Ractor ワーカーでエラーは起きているが、ログがまともに出ない/バックトレースが取れない」という状態になっていました。
2-2. 主な対策内容
(A) Rails 環境情報・バージョン情報の shareable 化
対象:
railties/lib/rails/gem_version.rbrailties/test/version_test.rbactivesupport/lib/active_support.rb
想定される修正方針:
Rails::VERSION::STRINGが生成時点でfreezeされるようにする、もしくは Ractor から参照されるバージョン情報を shareable なオブジェクトに変更。Rails.envについても、内部的に Ractor-shareable な値だけを参照・返すように調整(少なくとも、エラーレポータから参照される部分が shareable になるよう整理)。- 例: 環境文字列を
freezeしておく、あるいは Ractor 内では primitive な値やコピーを渡す、など。
- 例: 環境文字列を
これにより、Ractor 内のエラーレポートのフォーマット時に Rails::VERSION::STRING や Rails.env を安全に利用できるようになります。
(B) Logger / BroadcastLogger 周りの Ractor 対応
対象:
activesupport/lib/active_support.rb- 可能性として
Rails.application初期化 (railties/lib/rails/application.rb) も関与
修正意図:
- 直接
BroadcastLoggerを Ractor に渡さず、Ractor 内でも使える形にする。 - 具体的には:
- Ractor 用に shareable な logger を別途用意する
- もしくは BroadcastLogger の構造を Ractor-shareable に近づける(内部配列の freeze など)
- あるいは Ractor 内のロギング経路専用の Event Reporter / ログサブスクライバを経由してメインプロセス側にログを流す
PR 説明からは、「logger が BroadcastLogger でラップされていて shareable でなかった問題を解消し、Ractor ワーカーからもちゃんとログを流せるようにした」と読み取れます。
(C) Event Reporter と LogSubscriber の接続
対象:
activesupport/test/event_reporter_test.rbactivesupport/test/ractors_test.rb
前提:
- #58599 で「Ractor セーフな Event Reporter」が導入済み。
- しかし、Ractor ごとに新しいインスタンスを作るだけで、
ActiveSupport::Notifications的な仕組みに繋がっていなかった。
今回の修正意図:
- Ractor ローカルに作られた Event Reporter インスタンスを、既存の LogSubscriber 達(
ActionView::LogSubscriberやActionController::LogSubscriberなど)にきちんと紐付ける。 - これにより、Ractor 内で発火したイベントが通常と同様にログに反映されるようになる。
テスト (event_reporter_test.rb, ractors_test.rb) からは、「Ractor 環境でイベント発火 → ログや通知が届くか」を検証するケースが追加されていると推測できます。
(D) バックトレースクリーナの Ractor 対応
対象:
activesupport/lib/active_support/backtrace_cleaner.rbrailties/lib/rails/backtrace_cleaner.rbrailties/test/backtrace_cleaner_test.rb
問題点:
RbConfig::CONFIG["rubylibdir"]が unfrozen string で、そのままだと Ractor-shareable ではない。Rails::BacktraceCleaner.rootも unfrozen string だった。
修正内容(推測ベースですが方向性はほぼ確実):
- 取得したパス文字列に対して
freezeを行う、またはあらかじめ frozen な定数に格納する。 BacktraceCleanerの内部で保持している root や除外パス配列も Ractor-shareable になるように freeze する。
これにより、Ractor 内から Rails.backtrace_cleaner.clean(exception.backtrace) のような処理を行っても、Ractor の shareability ルールに違反しなくなり、正常にバックトレースを整形してログに出せるようになります。
2-3. 動作例:Ractor 環境でのエラー発生ログ
PR 説明にあるサンプルアプリでの起動・挙動:
# Ractor 向け環境で Rails サーバを起動
$ bin/rails server -e ractor & until nc -z localhost 3000 2>/dev/null; do :; done
=> Booting Cougar
=> Rails 8.2.0.alpha application starting in ractor http://0.0.0.0:3000
config.ru:10: warning: Ractor support in Rails is experimental and subject to change.
Cougar listening on 0.0.0.0:3000 with 8 workers正常系:
$ curl http://127.0.0.1:3000/up &>/dev/null
Started GET "/up" for 127.0.0.1 at ...
Processing by Rails::HealthController#show as */*
Completed 200 OK in 2ms (Views: 0.8ms | GC: 0.0ms)例外発生時:
$ curl http://127.0.0.1:3000/500 &>/dev/null
Started GET "/500" for 127.0.0.1 at ...
Processing by BoomController#show as */*
Completed 500 Internal Server Error in 1ms (GC: 0.0ms)
RuntimeError (boom):
app/controllers/boom_controller.rb:3:in 'BoomController#show'このように、Ractor ワーカーで動いている Cougar (HTTP サーバ) のワーカー内で例外が起きた場合でも、通常の Rails サーバと同じ形式でステータス行・例外クラス・メッセージ・バックトレースがログに出るようになったことが確認されています。
- 影響範囲・注意点
3-1. 影響範囲
Ractor 対応を使う Rails アプリ
config.ru等で Ractor 対応のサーバ(Cougar など) +RAILS_ENV=ractorのような環境を使っている場合、例外時のログ挙動が改善されます。- エラーレポート、イベントベースのログ(
Processing by ...,Completed ...など)、バックトレース整形が Ractor ワーカーからも正常に動くようになります。
標準的な (非 Ractor) Rails アプリ
- 基本的に振る舞いは変わらない想定です。
- ただし、バージョン情報や env、バックトレースクリーナ内部の文字列が freeze される/shareable を意識した形に変更されているため、「意図せずこれらを破壊的に書き換えていたようなコード」があれば影響を受ける可能性はあります(もともとそうした書き換えは非推奨なので、表向きには後方互換性ありと考えてよい範囲)。
3-2. 注意点
Ractor サポート自体はまだ Experimental
- ログにも明示されている通り、Rails の Ractor 対応は「experimental and subject to change」です。
- 内部 API や shareable オブジェクトの扱いは今後も変わる可能性があるため、本番運用では慎重な検証が必要です。
独自 Logger / BacktraceCleaner 拡張との相性
- 独自に
Rails.loggerをラップしたり、Rails.backtrace_cleanerに独自のフィルタや root を設定している場合、それらが Ractor-shareable でないオブジェクトを内部保持していると、Ractor 環境でエラーになる可能性があります。 - 将来的に Ractor 対応を検討している場合は、「ログ周り・エラーレポート周りが Ractor-shareable になっているか」を意識しておくとよいです。
- 独自に
- 参考情報 (あれば)
- この PR 本体:
- rails/rails#58695 — Allow exception backtraces from Ractor workers
- 関連 PR:
- rails/rails#58599 — Event Reporter を Ractor セーフにした変更(今回の前提になっている)
- サンプルアプリのコミット:
- https://github.com/etiennebarrie/rails-app/commit/db51ca33b4702112bc04fb118c94c35d0083a44f
- Ractor 環境 (
-e ractor) を追加した検証用 Rails アプリの例
- Ractor 環境 (
- https://github.com/etiennebarrie/rails-app/commit/db51ca33b4702112bc04fb118c94c35d0083a44f
- Ractor の shareable ルール(Ruby ドキュメント):
- https://docs.ruby-lang.org/en/master/doc/ractor_md.html (Ractor と shareable オブジェクトの制約について)
この PR により、「Ractor ワーカーで動かしても、本番と同じように例外ログ・バックトレースが見えるか?」という、運用上かなり重要なポイントが解決されているため、将来的に Ractor ベースのスケール戦略を検討している場合はチェックしておく価値があります。
#58702 Remove the dead encryption performance reject clause from the Active Record Rakefile
マージ日: 2026/9/8 | 作成者: @dersam
- 概要 (1-2文で)
Active Record の Rakefile に残っていた「暗号化パフォーマンス関連テストを除外するための条件分岐」が、既に存在しないテストパスを対象としており不要になっていたため削除されました。テストタスクのフィルタ条件を整理するだけの変更で、機能面の挙動には影響しません。
- 変更内容の詳細
対象: activerecord/Rakefile の「アダプタごとのテストタスク(per-adapter test task)」定義部分。
元々、このタスクではテストファイルのパスをフィルタリングする際に、「暗号化パフォーマンス関連のテスト」を除外するための条件が含まれていました。しかし、その対象パスに一致するテストファイルは 2024年2月以降すでに存在しておらず、「死んだコード(dead code)」になっていました。
この PR では、その不要な reject/除外条件の記述が削除されています。
イメージとしては、以下のようなコードから:
paths = Dir["test/cases/**/*_test.rb"]
# かつて存在した暗号化パフォーマンステストを除外
paths.reject! { |path| path.include?("encryption/performance") }
task "db:adapter:test" do
ruby "-Itest", *paths
endといった reject 部分を取り除いて、単にパスを集約して実行するだけの形に戻した、という内容です(実際のコードはこれと完全には一致しませんが、概念的にはこのような変更です)。
関連 PR (#58697) で既に同種のクリーンアップが行われており、今回は Active Record の Rakefile 側に残っていた最後の一箇所を整理しています。
- 影響範囲・注意点
影響範囲
- Active Record の「アダプタ別テストタスク」のテストファイル選択ロジックのみ。
- 実際には、除外対象のパスに一致するテストファイルがすでに存在しないため、テスト実行結果・対象テストセットに実質的な変化はありません。
- アプリケーションコードや本番環境の挙動には一切影響しません。
注意点
- 将来的に「encryption/performance」系のテストを同じパスで復活させる場合、今回削除したような特別扱い(除外処理)はもう存在しないため、そのまま per-adapter テストに含まれることになります。
- CI や独自のテストラッパーで
activerecord/Rakefileの挙動に依存しているケースでも、この変更による差分は事実上発生しないはずです。
- 参考情報 (あれば)
- 関連 PR: #58697
暗号化関連テストまわりの整理に関する追加コンテキストが示されています。 - 対象 PR: #58702
「Remove the dead encryption performance reject clause from the Active Record Rakefile」
#58697 Remove stale Active Record encryption performance tasks
マージ日: 2026/9/8 | 作成者: @dersam
- 概要 (1-2文で)
Active Record の「暗号化のパフォーマンステスト」用 Rake タスクおよび関連ドキュメント・コメントが、実際には何も実行していない「空タスク」になっていたため、それらを削除する PR です。機能追加・仕様変更ではなく、不要になったテスト関連のコードとドキュメントのクリーンアップです。
- 変更内容の詳細
削除されたものの内容
この PR は、以下の3種類の「古いテスト関連リソース」を削除しています。
暗号化パフォーマンス用 Rake タスク (activerecord/Rakefile)
test:encryption:performance:<adapter>形式のタスクが定義されていました。- しかし、先行 PR #50986 で該当するパフォーマンステスト自体が削除済みで、
タスクのファイルリストは常に空 → 実行しても何も起こらない状態になっていました。 - この PR で、これらの空タスク定義を丸ごと削除しています。
典型的には以下のようなタスクが存在していたと考えられます(あくまでイメージ):
rubynamespace :test do namespace :encryption do namespace :performance do task :postgresql do # 実際にはファイルリストが空で何も実行されない状態になっていた end end end end上記のようなタスク定義が Rakefile から削除されたイメージです。
テスト実行方法のドキュメント (activerecord/RUNNING_UNIT_TESTS.rdoc)
test:encryption:performance系タスクの実行例や説明が RDoc に残っていました。- テスト本体もタスクも実質的に存在しないため、これらのコマンド例は「実行しても何もしない」状態だったので、該当部分の記述を削除しています。
- これにより、ドキュメントと実際のタスク定義の不整合が解消されます。
暗号化関連のコメント (extended_deterministic_queries.rb)
ActiveRecord::Encryption::ExtendedDeterministicQueries内に、削除されたテストへの言及がコメントとして残っていました。- 既に存在しないテストを参照していたため、そのコメントを削除し、コードコメントと実際のテスト群を整合させています。
- 影響範囲・注意点
rake test:encryption:performance:*を使っていた場合- これらのタスクは元々「何も実行していない」ため、実質的な挙動は以前からゼロでしたが、
今後は タスク自体が存在しなくなる ため、CI やスクリプトでこれらを呼んでいるとエラーになります。 - その場合は、タスク呼び出しを削除するか、自前のパフォーマンステストタスクへ置き換える必要があります。
- これらのタスクは元々「何も実行していない」ため、実質的な挙動は以前からゼロでしたが、
アプリケーションの実行時挙動
- Active Record のランタイム挙動(暗号化機能・クエリ挙動など)へは一切影響しません。
- 変更はテスト用タスク・ドキュメント・コメントの削除のみです。
Rails コントリビューション時
activerecordのテスト実行方法を確認する際、古い暗号化パフォーマンステスト向けの説明はもう存在しません。- 代わりに、現行のテストコマンドのみがドキュメントに残る形になるため、新規コントリビューターにとって分かりやすくなります。
- 参考情報 (あれば)
- 元になった削除 PR: rails/rails#50986
- 暗号化パフォーマンステスト本体を削除した PR。
- 当該 PR: rails/rails#58697 – Remove stale Active Record encryption performance tasks
- 今回はその後始末として、残っていたタスク・ドキュメント・コメントを整理するクリーンアップです。
#58693 Exclude generated error pages from GitHub language stats
マージ日: 2026/9/8 | 作成者: @irinanazarova
- 概要 (1-2文で)
Rails 8 以降で生成される新規アプリが GitHub 上で「HTML リポジトリ」と判定されてしまう問題を解消するため、rails newが作成する静的エラーページを GitHub Linguist の「generated ファイル」として扱う設定を.gitattributesに自動生成するようにした PR です。これにより、新しい Rails アプリは正しく「Ruby リポジトリ」として集計されるようになります。
- 変更内容の詳細
何をしたか
rails new 実行時に生成される .gitattributes テンプレート (railties/lib/rails/generators/rails/app/templates/gitattributes.tt) に、以下の5つの静的エラーページを linguist-generated としてマークする設定が追加されました。
対象ファイル:
public/400.htmlpublic/404.htmlpublic/406.htmlpublic/422.htmlpublic/500.html
追加される .gitattributes の記述イメージ:
# Exclude schema dumps and error pages from language statistics
db/schema.rb linguist-generated
public/400.html linguist-generated
public/404.html linguist-generated
public/406.html linguist-generated
public/422.html linguist-generated
public/500.html linguist-generated既存の db/schema.rb linguist-generated と同じ場所に、これら5行が追加される形です。
背景・理由
- GitHub の言語判定 (Linguist) は「バイト数の多い言語」をリポジトリのメイン言語として扱います。
- Rails 8 以降の新規アプリでは、最初にコミットする段階では Ruby コードよりも
public/xxx.htmlの静的 HTML の方がバイト数が多くなるケースが多く、その結果「このリポジトリの言語は HTML」と判定されていました。 - これにより、GitHub の統計に基づく
- 「新規 Ruby / Rails プロジェクト数」
- 「Ruby 開発者数」
などの指標が過小評価されていました。
- 静的エラーページはジェネレータで自動生成されるボイラープレートであり、アプリケーション本体とは言い難いため、
linguist-generatedとするのは妥当、という判断です。
Linguist の linguist-generated は以下のような意味を持ちます。
- 言語統計から除外される
- diff ではデフォルトで折りたたまれる (schema.rb と同様の扱い)
テスト・ドキュメント
railties/test/generators/app_generator_test.rbに、生成される.gitattributesにこれらの行が含まれていることを確認するテストが追加されています。railties/CHANGELOG.mdに、この挙動変更に関するエントリが追記されています。
- 影響範囲・注意点
影響範囲
- 対象: Rails 8 以降で
rails newコマンドにより新規作成されるアプリケーション - 影響内容:
- デフォルト生成される
.gitattributesに、上記5つのpublic/*.htmlがlinguist-generatedとして含まれる - その結果、新規 Rails アプリの GitHub リポジトリは、初期状態から「Ruby リポジトリ」として判定されやすくなる
- デフォルト生成される
- 既存アプリには自動で変更は入りませんが、既存プロジェクトでも
.gitattributesを手動で同様に編集すれば、同じ効果が得られます。
注意点
- GitHub の表示・統計に関する変更のみであり、Rails アプリケーションのランタイム挙動・生成ファイルの内容そのものには一切影響しません。
public/400.htmlなどをアプリ独自に編集していても、引き続きlinguist-generated扱いのままになるため、- 「HTML も主要言語としてカウントして欲しい」
- 「これらのエラーページをアプリケーションコードとして統計に含めたい」
といったニーズがある場合は、各開発者・プロジェクトで.gitattributesから該当行を削除/変更してください。
- Linguist 側の仕様に依存するため、将来 Linguist の挙動が大きく変わった場合は、期待する結果が変わる可能性があります。
- 参考情報 (あれば)
- PR で示されている再現リポジトリ:
- 修正前 (HTML 判定):
https://github.com/irinanazarova/rails-new .gitattributesに5行を追加しただけの修正版 (Ruby 判定):
https://github.com/irinanazarova/rails-new-amended
- 修正前 (HTML 判定):
- GitHub Linguist ドキュメント (generated ファイルの扱い):
https://github.com/github-linguist/linguist#generated-code - 類似の既存設定:
db/schema.rb linguist-generatedがすでに Rails 標準で設定されており、今回の変更はそれと同じ思想で HTML エラーページも除外対象にしたものです。
#58690 Update json to 3.0
マージ日: 2026/9/8 | 作成者: @byroot
- 概要 (1-2文で)
このPRは Rails の依存関係として利用しているjsongem を 3.0 系に更新するものです。ただし、resque→multi_json→jsonという依存関係の互換性問題により、一部の処理がエラーになるため、その対応が前提条件になっています。
- 変更内容の詳細
変更のポイント
Gemfilejsonのバージョン指定を3.0系に更新(~> 3.0あるいは同等の指定に変更されたと考えられる)。
Gemfile.lockjsonの実際のインストールバージョンが3.0.0に更新。- それに伴い依存解決結果(依存関係ツリーやサブ依存のバージョン)が数行変化。
コードとしては gem のバージョンアップのみなので、アプリケーションコード(Ruby / Rails コード)は一切変更されていません。
何が問題になっているか
PR 説明にあるスタックトレースから、json-3.0.0 と multi_json-1.21.1 のインターフェイス不整合が発生しています:
ArgumentError: wrong number of arguments (given 2, expected 1)
/usr/local/bundle/gems/json-3.0.0/lib/json/common.rb:296:in 'JSON.parse'
/usr/local/bundle/gems/multi_json-1.21.1/lib/multi_json/adapters/json_gem.rb:47:in 'MultiJSON::Adapters::JsonGem#load'
/usr/local/bundle/gems/multi_json-1.21.1/lib/multi_json/adapter.rb:100:in 'MultiJSON::Adapter.load'
/usr/local/bundle/gems/multi_json-1.21.1/lib/multi_json.rb:182:in 'MultiJSON.parse'
/usr/local/bundle/gems/resque-3.0.2/lib/resque.rb:57:in 'Resque#decode'
/usr/local/bundle/gems/resque-3.0.2/lib/resque/job.rb:49:in 'Resque::Job.decode'ここから読み取れること:
multi_jsonのJsonGemアダプタがJSON.parseを次のように呼んでいると考えられます(イメージ):ruby# 古い multi_json 側のイメージ JSON.parse(string, symbolize_names: true) # または JSON.parse(string, options_hash)一方、
json-3.0.0側ではJSON.parseのメソッドシグネチャが変更され、「1 引数しか受け取らない」(or 2 引数の形が互換でなくなった) ため、ArgumentError: wrong number of arguments (given 2, expected 1)になっています。その結果、
multi_json.parseを経由して JSON をパースしているresqueが落ちています。
PR 本文にもある通り、これは multi_json の更新で対処される予定で、該当 PR:
https://github.com/sferik/multi_json/pull/67
- 影響範囲・注意点
影響範囲
json直接利用部分- アプリケーション内で
JSON.parse/JSON.generate/JSON.dump/JSON.loadを直接呼んでいる部分は、json-3.0における API 変更の影響を受ける可能性があります。 - 特に、「第 2 引数でオプションを渡していた部分」は要確認です。
例: 旧来の書き方(2 引数):
rubyJSON.parse(json_string, symbolize_names: true)json-3.0がこれを受け付けない場合、以下のようにブロックやキーワード引数の公式なインターフェイスへ書き換えが必要になるケースがあります(※具体的な新シグネチャは json gem のリリースノート要確認):rubyJSON.parse(json_string, { symbolize_names: true })あるいは、
3.0でサポートされる正式な形へ合わせる必要があります。- アプリケーション内で
multi_jsonを経由した利用箇所resqueを含め、multi_jsonを通じて JSON を扱っているコードは、multi_jsonがjson-3.0対応版にアップデートされるまで例外が出る可能性があります。- PR 本文の通り、現時点では
resque -> multi_jsonのチェーンがjson-3.0と非互換で、テストや実行時にジョブのデコード処理 (Resque::Job.decode) が失敗します。
ジョブキュー・バックグラウンド処理
- Resque を使っている環境では、ジョブキューの読み取り・復元時の JSON デコードが落ちるため、実質的にジョブが動かない状態になり得ます。
- 本 PR は「
multi_jsonの修正リリースが出るまでブロックされている」と明示されているので、正常稼働を前提とする本番環境にそのまま適用するのは危険です。
注意点・対応方針
この PR を取り込む前に:
multi_jsonのjson-3.0対応版がリリースされているか確認し、そのバージョンを利用できるようにする。resqueがそのmulti_jsonバージョンを許可しているか(gemspecの依存関係)も確認する。
アプリケーション側でのチェック項目:
- 自前で
multi_jsonを使っている部分があれば、multi_jsonのバージョンとjson-3.0の互換性を確認。 - テストスイートで JSON を扱う部分(特にバックグラウンドジョブ、API パラメータのパース、キャッシュシリアライズなど)を重点的に実行して、
ArgumentErrorやTypeErrorの有無を確認。
- 自前で
一時的な回避策(どうしても先に json を上げたい場合):
jsonを 3.0 より前のバージョンにピン止めする(例:gem "json", "< 3.0")ことで、この PR の変更を取り込まない構成にする。- もしくは
multi_json/resque側の修正版が出るまで、この PR 自体をマージ対象から外しておく。
- 参考情報 (あれば)
multi_jsonのjson-3.0対応 PR:- https://github.com/sferik/multi_json/pull/67
JSON.parseなどのシグネチャ変更に追従する修正が含まれていると考えられる。
- https://github.com/sferik/multi_json/pull/67
本 PR の技術的本質:
- 「
jsongem を 3.0 系へ更新したいが、その前にmulti_json/resque側での API 互換性対応が必要」 - 実質的には依存ライブラリ群の足並みが揃うまで待つことが前提の PR。
- 「
実務的な運用としては、
- まず
multi_jsonの対応版リリース →resqueの依存アップデート → テストでキュー周りの正常動作を確認 → 最後にこのjson 3.0更新 PR を本番に反映、
という段階的アップデートが安全です。
- まず
#58678 Use Regexp#source instead of Regexp#to_s to detect deep filters
マージ日: 2026/9/7 | 作成者: @eregon
- 概要 (1-2文で)
Rails のパラメータフィルタ (ActiveSupport::ParameterFilter) で「ディープフィルタ」を検出する際、Regexp#to_sの代わりにRegexp#sourceを使うように変更し、不要な文字列生成を避けてパフォーマンスを改善した PR です。挙動は変えずに、高頻度パスでのオーバーヘッドを削減しています。
- 変更内容の詳細
対象は activesupport/lib/active_support/parameter_filter.rb の 1 行のみで、正規表現から「. が含まれているか」を判定するロジックの実装が変わっています。
元々はおおよそ以下のように正規表現を文字列化していました(※イメージ):
# 変更前のイメージ
if some_regexp.to_s.include?("\\.")
# 「ディープフィルタ」とみなす処理
endこれを to_s ではなく source を使うように変更しています:
# 変更後のイメージ
if some_regexp.source.include?("\\.")
# 「ディープフィルタ」とみなす処理
endRegexp#to_s は、スラッシュやオプション(/pattern/i のような表現)を含んだ新しい文字列表現を毎回組み立てるのに対し、Regexp#source はコンパイル元のパターン文字列("pattern" 部分)をそのまま返すだけのため、文字列生成コストがかかりません。
PR では以下のようなベンチマーク結果が示されています(Rails が config.filter_parameters でプリコンパイルする 2 つの正規表現を対象):
Regexp#sourcevsRegexp#to_s(単純取得):source: 8,781.2k i/sto_s: 347.3k i/s (約 25.3 倍遅い)
include?("\\.")まで含めた場合:source.include?("\\."): 2,533.8k i/sto_s.include?("\\."): 297.4k i/s (約 8.5 倍遅い)
railsbench では、1 リクエストあたり約 4.2μs がこの Regexp#to_s に費やされていたとのことで、それをほぼ削減できる変更です。
- 影響範囲・注意点
機能面の挙動:
- ディープフィルタ(
config.filter_parametersによるネストしたパラメータのフィルタリング)を検出するために、「正規表現パターン内に"\."が含まれるか」を判定している処理の実装のみが変わります。 Regexp#sourceもRegexp#to_sもパターン文字列部分については同じ内容を含むため、「"\\."を含むかどうか」の判定ロジック自体は変わらず、フィルタリング結果(どのキーがマスクされるか)は変わりません。
- ディープフィルタ(
互換性:
- 公開 API のシグネチャや設定方法 (
config.filter_parameters) 自体には一切変更なし。 Regexp#sourceは Ruby の標準的かつ古くからあるメソッドであり、サポート Ruby バージョン間での互換性リスクはほぼありません。
- 公開 API のシグネチャや設定方法 (
パフォーマンス:
- 高トラフィックなアプリケーションでは、1 リクエストごとに
config.filter_parametersのルールが評価されるため、本変更によるマイクロ秒レベルの削減が蓄積して意味のある改善につながる可能性があります。 - 特に
config.filter_parametersに多くの正規表現を指定している場合に効果が大きくなります。
- 高トラフィックなアプリケーションでは、1 リクエストごとに
- 参考情報 (あれば)
修正対象のクラス:
ActiveSupport::ParameterFilter- Rails のログ出力時、パラメータのマスキング(例:
passwordやcredit_cardの伏字化)を担当する機能。 - 「ディープフィルタ」はネストしたキー(例:
user[password]やorder.items[].credit_card)にもマッチさせるための仕組みで、その検出に正規表現の中の"\\."を利用している。
- Rails のログ出力時、パラメータのマスキング(例:
Ruby のメソッド仕様:
Regexp#source: コンパイルされた正規表現の元となる文字列を返す(例:/a\.b/i.source #=> "a\\.b")Regexp#to_s: スラッシュとオプションを含む正規表現リテラル形式の文字列を生成する(例:/a\.b/i.to_s #=> "(?i-mx:a\\.b)"など実装依存な形式で、新しく文字列を構築する)
#57198 Fix retry_job to respect enqueue options when enqueue is deferred
マージ日: 2026/9/7 | 作成者: @Saidbek
- 概要 (1-2文で)
enqueue_after_transaction_commitを有効にした状態でトランザクション内からretry_jobを呼び出した場合に、wait/queue/priorityといった enqueue オプションが無視されてしまうバグを修正する PR です。トランザクションコミット後に実際の enqueue が行われるケースでも、retry_jobで指定したオプションが正しく反映されるようになります。
- 変更内容の詳細
問題の背景
前提条件:
- Active Job で
enqueue_after_transaction_commitがオンになっている - トランザクション内で
retry_job(wait: ..., queue: ..., priority: ...)を呼ぶ
このとき、本来は「コミット後に、指定した wait / queue / priority を使ってキューイング」されるべきですが、実際には:
- デフォルトキューに即時実行扱いで enqueue されてしまう
retry_jobに渡したオプションが 黙って捨てられる というバグが起きていました
原因は #55141 の仕様変更にあります:
retry_jobは内部的にself(ジョブインスタンス)を書き換えてから enqueue し、ensureで元に戻す実装になった- 同期 enqueue(その場で adapter が呼ばれる)であれば問題ない
- しかし
enqueue_after_transaction_commitが有効な場合は:- 実 enqueue は「トランザクションコミット後のコールバック内」で行われる
- ところが
retry_jobメソッド自体はすでに終了しており、ensureによってscheduled_atqueue_namepriorityが元に戻されている
- 結果として、コミット後のコールバックで adapter が見るのは「オプション適用前の元の値」となり、
wait/queue/priorityの指定が失われる
解決方法
EnqueueAfterTransactionCommit#raw_enqueue を修正し、トランザクションコールバックに渡す時点で enqueue 用属性のスナップショットを取っておき、実 enqueue 直前に一時的に適用するようにしました。
PR の要点:
EnqueueAfterTransactionCommit#raw_enqueueで以下の属性をスナップショットする:scheduled_at(遅延実行時間:wait相当)queue_name(キュー名)priority(優先度)
- トランザクションの
after_commitコールバック内で:- 現在の
scheduled_at/queue_name/priorityを一時退避 - スナップショットしておいた値を
selfにセット super(元のraw_enqueue)を呼んで adapter に enqueue させる- 退避しておいた元の状態を
selfに戻す
- 現在の
これにより:
retry_jobが「最終的に self の状態を元に戻す」という契約(no-mutation contract)は維持される- 同時に、トランザクションコミット後に実行される enqueue でも、「
retry_job呼び出し時点の属性」が反映される
テスト
activejob/test/cases/enqueue_after_transaction_commit_test.rb にテストが追加されています:
- トランザクション内で
retry_jobを呼び、waitqueue(queue_name)priorityが正しく adapter に渡されていることを検証
- これにより、以前の挙動(オプションがデフォルトに戻ってしまう)が再発しないことを確認しています。
- 影響範囲・注意点
- 対象となるケース:
- Active Job を利用
enqueue_after_transaction_commitを有効にしている(config.active_job.enqueue_after_transaction_commit = true等)- トランザクション内で
retry_jobを呼んでいる
- この PR によって:
- そうしたケースで、今まで「気づかないうちに無視されていた」
wait/queue/priorityの指定が、本来期待される通りに効くようになります - 既存アプリが「バグ前提の挙動」に依存していた場合は、ジョブの実行タイミング・キュー名・優先度が変わる可能性があります(ただし、本来の仕様に沿った修正)
- そうしたケースで、今まで「気づかないうちに無視されていた」
注意点:
- ジョブインスタンスの属性は enqueue 前後で元に戻されるため、「コミット後にどこか別の場所から同じインスタンスを参照して、enqueue 用属性を読む」といった使い方には引き続き向きません(Active Job の設計的にも非推奨の領域です)。
- Active Job のアダプタを自作している場合も、
raw_enqueueの呼び出しインターフェイスは変わっていないため、特に対応変更は不要です。
- 参考情報 (あれば)
- 該当 Issue: https://github.com/rails/rails/issues/57197
- 影響した過去の PR: https://github.com/rails/rails/pull/55141
- 対応クラス:
ActiveJob::EnqueueAfterTransactionCommit(activejob/lib/active_job/enqueue_after_transaction_commit.rb)
#58684 Fix Active Support loading on JRuby without Ractor
マージ日: 2026/9/7 | 作成者: @nicolasva
- 概要 (1-2文で)
JRuby 10 がRUBY_VERSION = "4.0.0"を名乗る一方でRactor定数を定義していないために、Active Support の Ractor 対応コードが NameError で落ちていた問題を修正する PRです。Ractor定数の有無を見て挙動を切り替えることで、Ractor を持たない実装(JRuby など)でも安全にactive_support/core_extがロードできるようになりました。
- 変更内容の詳細
2-1. 問題の背景
- JRuby 10 は
RUBY_VERSIONを"4.0.0"と報告する - Active Support 側は「Ruby 4 なら Ractor 前提で動くはず」として、
ActiveSupport::Ractors内部で Ractor ベースの実装を選択していた - しかし JRuby 10 には
Ractor定数が存在しないため、ActiveSupport::Ractors::Ractorを参照する際にNameError: uninitialized constant ActiveSupport::Ractors::Ractor
が発生し、active_support/core_extのロード自体が失敗していた
この PR は「Ruby のバージョン文字列ではなく、Ractor 定数の存在実際にを見る」ことで、この前提崩れを解消しています。
2-2. ActiveSupport::Ractors の挙動切り替え
変更点の要旨
- これまで:
- Ruby のバージョンなどから「Ractor あり実装」を前提としていた
- これから:
- 実行時に
defined?(Ractor)などでRactor定数の有無を確認 - Ractor がある場合: これまでどおり Ractor バックエンドの実装を使用
- Ractor がない場合: スレッドセーフな「プロセスローカルストレージ」ベースの実装を使用
- 実行時に
いずれの場合も ActiveSupport::Ractors として提供されるパブリック API([], []=, store_if_absent)は変わりません。
インターフェース(利用側のコード例)
利用者が書くコードは従来と変わりません。たとえば:
# 値の設定
ActiveSupport::Ractors[:foo] = 123
# 値の取得
value = ActiveSupport::Ractors[:foo] # => 123
# 存在しないときだけブロックの結果を保存
ActiveSupport::Ractors.store_if_absent(:bar) do
expensive_computation
endRactor がある Ruby では、ActiveSupport::Ractors は Ractor ごとに独立したストレージを持つような振る舞いをし、
Ractor がない Ruby(JRuby など)では、スレッドセーフなプロセスローカルなストレージとして動作します。
フォールバック実装の概要
PR本文によると、Ractor 未サポート環境向けに:
- スレッドセーフ
- プロセスローカルなストレージ
- 既存の API (
[],[]=,store_if_absent) を維持
した実装に切り替わるようになっています。
実装詳細は active_support/ractors.rb 内で、Ractor の有無で分岐する形になっているはずです(例: if defined?(Ractor) ブロックなど)。
2-3. テストの変更
activesupport/test/ractors_test.rbが更新され、以下をカバー:- Ractor が利用可能な環境では、従来どおり Ractor ベースの挙動をテスト
- Ractor が利用できない環境(JRuby など)では:
- Ractor 固有のテストはスキップ
- フォールバック実装(スレッドセーフなプロセスローカルストレージ)の挙動をテスト
これにより、「Ractor あり」「Ractor なし」の両方のランタイムでテストスイートが安定して動作するようになります。
- 影響範囲・注意点
3-1. 影響を受ける環境
JRuby 10 (Ruby 4.0.0 と名乗るが Ractor がない)
- これまでは
active_support/core_extのロード時に NameError で落ちうる - この PR 適用後は、Ractor 非依存のフォールバック実装が使われるため、正常にロード可能
- これまでは
CRuby 3.0 以降など、Ractor を提供する実装
- 挙動は従来通り(Ractor バックエンドを使う)
- パフォーマンスやスレッドセーフティの特性も変わらない想定
その他の Ruby 実装(Ractor なし)
- 上記 JRuby と同様、フォールバック実装が使用される
3-2. アプリケーションコードへの影響
- 公開 API (
ActiveSupport::Ractors.[],[]=,store_if_absent) は変わっていないため、通常の利用コードを修正する必要はありません。 - ただし、もしアプリケーションやライブラリ側で:
ActiveSupport::Ractorsの内部実装(Ractor 前提)に依存したり、- 「Ruby 4 なら必ず Ractor がある」という前提でコードを書いている
場合は、その前提が崩れるので注意が必要です。
特に JRuby 10 では:
RUBY_VERSIONは"4.0.0"だが Ractor なし- Rails/Active Support 側は「RUBY_VERSION ではなく Ractor の有無」で挙動を切り替える
というポリシーになった点を意識しておくと良いです。
- 参考情報 (あれば)
- 対応している Issue:
Fixes #58654 - 変更ファイル:
activesupport/lib/active_support/ractors.rb- Ractor 有無による実装切り替え・フォールバックの導入
activesupport/test/ractors_test.rb- Ractor あり/なし両方の環境に対応するテストに修正
- Active Support の CHANGELOG も更新されており、「Ractor のない Ruby 実装でのロード失敗バグ修正」として記録されています。
#58688 Upgrade rubocop
マージ日: 2026/9/7 | 作成者: @byroot
- 概要
Rubocop をアップグレードしたうえで、それに伴いコードスタイルやテストコードを新しいルールに合わせて修正し、あわせてjsongem を一時的に< 3.0にロックした PR です。機能追加はなく、主に開発環境・テスト用の整備が目的です。
- 変更内容の詳細
2-1. Gem 関連
Rubocop のアップグレード
Gemfile/Gemfile.lockを更新し、Rubocop を新しいバージョンに上げています。- これにより、Rubocop 側で追加・強化された Cop に合わせて、Rails 本体およびテストコードのスタイルを調整した変更がファイル全体に入っています。
jsongem のバージョン固定- 説明文より:
And lock
json < 3.0temporarily (everything works with it except for the resque test suite that need a newmulti_jsonrelease). jsonを< 3.0に固定。- 現状 Rails 自体は
json 3.0でも動作するが、Resque のテストスイートがmulti_jsonの未対応により失敗するため、CI を安定させる目的で一時的にロックしています。 - 本番アプリにおいても
bundle update jsonで 3.0 系を入れると、Rails ではなく Resque / multi_json 周辺で問題が出る可能性がある、という状況を明示しています。
- 説明文より:
2-2. Rubocop に起因するコード修正
以下は代表的なパターンで、いずれも挙動は変えずにスタイル・記述方法を整理する変更です。
actionpack
page_dump_helper.rb- インデント・行末のスタイルや短い記法の統一など、軽微なスタイル修正が 1 行追加 / 1 行削除レベルで入っています。
parameters/mutators_test.rb- 追加 13 行 / 削除 19 行と少し大きめに見えますが、内容としては主に:
- ブロック内の変数名・スコープの明確化
- マルチラインのハッシュ/配列の記載方法の統一
- 不要な一時変数の削除や let/subject まわりの整理
など、Rubocop の警告解消のためのリファクタリングと思われます。
- 例(イメージ):
assert_equal({...}, params.to_h)を、キー順が制御しやすい書き方にしたり、マルチラインリテラルで読みやすく書き直している可能性があります。
- 追加 13 行 / 削除 19 行と少し大きめに見えますが、内容としては主に:
actiontext
attachments/conversion.rb- 添付ファイルの変換ロジック内でのスタイル修正:
- キーワード引数の書き方や条件分岐のワンライナー化/分割
if/elseのスタイル統一やメソッドチェーンの改行位置調整
といった Rubocop 的なクリーンアップが 3 行追加 / 3 行削除で行われています。
- 添付ファイルの変換ロジック内でのスタイル修正:
actionview
dependency_tracker_test.rb- 3 行追加 / 9 行削除と、コードが簡略化。
- 想定される変更:
- 重複していたテストセットアップをヘルパーメソッドにまとめる
- 正規表現・パスの扱いをより簡潔な構文に変更
assert_equal/assert_predicateなど、Minitest の推奨 API に沿う形への統一
activerecord
relation/finder_methods.rbfind_by,find_by!などの Finder 関連でのスタイル修正:- ガード節の導入・見直し
if/unlessの条件反転- メソッド内のネストを浅くするリファクタリング
が 8 行追加 / 8 行削除で行われています。
- Rubocop の
Style/GuardClauseやStyle/IfUnlessModifier,Layout系 Cop に起因する修正が中心と思われます。
enum_test.rb- enum の振る舞いをテストするコードのスタイル修正:
- シンボル・文字列リテラルの統一 (
:statusvs "status") - 期待値の書き方 (
assert_equal expected, actual) の順序や改行方法 - 1 行の長い期待値を複数行に分ける
などで、5 行追加 / 5 行削除。
- シンボル・文字列リテラルの統一 (
- enum の振る舞いをテストするコードのスタイル修正:
schema/schema.rb- Schema 定義ファイルで 1 行追加 / 1 行削除。
- インデント・空行・コメント書式の修正に近い、意味的にはノーチェンジのクリーンアップです。
activesupport
callbacks_test.rb- コールバックの動作を検証するテストのスタイル調整(7 行追加 / 7 行削除)。
- たとえば:
define_callbacks/set_callbackを使うクラスの定義方法を1か所にまとめるlambdaから->リテラルへの書き換え- テスト内でのインラインクラス定義のスタイル統一
などが想定されます。
evented_notification_test.rb- Evented Notifications のテストで、ブロックのネストや変数名などの整理(4 行追加 / 4 行削除)。
- イベントサブスクライバのダミークラスやブロック渡しの書き方を Rubocop 基準に合わせています。
railties
application/generators_test.rb- アプリケーションジェネレーターに関するテストコードの軽微なスタイル修正(1 行追加 / 1 行削除)。
- 例: メソッドチェーンの改行位置・使っていない変数の削除など。
- 影響範囲・注意点
機能面の挙動は基本的に不変
- 変更の大半がスタイル・テストコード・軽微なリファクタであり、Rails の公開 API や機能に直接の変更はありません。
- ただし、Rubocop に合わせてメソッド内部の書き方が変わっている箇所(特に
ActiveRecord::Relation::FinderMethods)は、ごくまれに境界的なケースで挙動差がないか確認しておく価値はあります(主に nil / 空配列などの扱い)。
json3.0 系を使いたい場合- 現時点では、この PR により Rails レポジトリ内では
json < 3.0にロックされます。 - 実サービスで
json 3.xを使う場合:- Rails 自体は動くというコメントですが、Resque + multi_json を併用している環境ではテスト同様の問題が起きる可能性があります。
multi_jsonの新バージョン(json 3.0対応)が出たら、ロックが解除されることが想定されるため、そのタイミングを待つ or 自前でパッチ適用・フォークする必要があります。
- 現時点では、この PR により Rails レポジトリ内では
Rubocop バージョンの追随
- Rails 本体が新しい Rubocop に追随したので、Rails に PR を送る場合:
- ローカルでも同じ Rubocop バージョン / 設定で
rubocopを走らせないと、CI のスタイルチェックに落ちやすくなります。 - 特にブロックの改行・メソッドチェーンの改行、ガード節、
if/unlessの書き方など、Rubocop が強く指摘する箇所を先に整えておくとよいです。
- ローカルでも同じ Rubocop バージョン / 設定で
- Rails 本体が新しい Rubocop に追随したので、Rails に PR を送る場合:
- 参考情報
- Rubocop 公式:
https://github.com/rubocop/rubocop jsongem:
https://github.com/flori/jsonmulti_jsongem:
https://github.com/intridea/multi_json- Resque:
https://github.com/resque/resque
この PR は「Rubocop に追随するためのスタイル整理」と「json 3.0 まわりの一時的なロック」が主題であり、Rails の新機能や仕様変更は含んでいません。
#58686 ExceptionWrapper: build backtrace lazily
マージ日: 2026/9/7 | 作成者: @byroot
- 概要 (1-2文で)
ActionDispatch::ExceptionWrapperが例外のバックトレース(backtrace)を「遅延評価(lazily)」で構築するように変更され、例外ラップ時点では backtrace を組み立てず、必要になったタイミングで初めて生成することでパフォーマンスの無駄なコストを避けるようになりました。
- 変更内容の詳細
※PR本文と差分概要からの推測を含みますが、Rails の既存コード構造に基づいています。
2-1. 何をしているクラスか
ActionDispatch::ExceptionWrapper は、Rack レベルの例外をラップし、
- ステータスコードの決定
- 例外クラス名・メッセージの整形
- バックトレースの整形・フィルタリング
などを行うミドルウェア用のヘルパークラスです。
例外ページの表示やログ出力で利用されます。
2-2. 従来の挙動
以前は、ExceptionWrapper が初期化されるタイミング(new / initialize)や、それに準ずる早いタイミングで、
- 元の
exception.backtraceを取得 - Rails の backtrace クリーンナップロジック(
Rails.backtrace_cleanerなど)を適用
といった処理を行い、「整形済み backtrace」をすぐに作っていました。
問題点:
- 例外をラップしたものの、実際には backtrace を使わないケース(例: ログレベルやレスポンス設定によって backtrace を出さない)がある。
- backtrace の構築・フィルタリングは比較的高コスト。
→ 使われない backtrace を毎回組み立てているのは無駄。
2-3. 今回の変更: backtrace を遅延構築
PR の説明「Building the backtrace is expensive, and there's a decent chance we won't need it, so it's better to do it lazily.」の通り、
- ExceptionWrapper 生成時には backtrace をまだ組み立てない
- backtrace が参照されたときに初めて生成する
という方式に変更されています。
Rails の典型的な実装例でいうと、以下のような変更が行われていると考えられます(疑似コード):
# 変更前(イメージ)
class ActionDispatch::ExceptionWrapper
def initialize(env, exception)
@exception = exception
@backtrace = clean_backtrace(exception.backtrace) # ここで即構築
end
def backtrace
@backtrace
end
end
# 変更後(イメージ)
class ActionDispatch::ExceptionWrapper
def initialize(env, exception)
@exception = exception
# @backtrace はここでは作らない
end
def backtrace
@backtrace ||= clean_backtrace(@exception.backtrace) # 最初に呼ばれたときだけ構築
end
endbacktraceアクセサ(もしくはそれに類するメソッド)の中で@backtrace ||= ...という形で初回アクセス時にだけ構築するようにしているはずです。- 差分が
+4/-2と少ないので、initialize内の eager な構築を削り、backtraceメソッド内に上記のような遅延構築ロジックを追加した程度の変更と推測されます。
2-4. テスト変更
actionpack/test/dispatch/exception_wrapper_test.rb が +25/-27 とほぼ書き換えられており、主な意図は:
- 例外をラップした直後に backtrace がまだ生成されていないこと(= 副作用がないこと)の検証
wrapper.backtrace(または関連メソッド)を呼んだタイミングで正しく backtrace が生成・クリーンナップされることの検証- 遅延評価によって API の挙動(戻り値の内容等)が変わっていないことの保証
などを行っていると考えられます。
- 影響範囲・注意点
3-1. パフォーマンス面の影響
- メリット:
- 例外が発生して
ExceptionWrapperが生成されても、backtrace が実際に必要になるまで高コストな backtrace 構築処理を行わないため、- 大量の例外が発生するケース
- backtrace を出力しない(あるいはごく一部でしか使わない)設定
では CPU 使用量・レスポンス時間の削減が期待できます。
- 例外が発生して
- Rails アプリ側が
ExceptionWrapperを直接使っていない場合は、アプリケーションコードの修正は不要ですが、例外が頻発する環境では地味に効く最適化です。
3-2. 互換性・既存コードへの影響
通常の利用では非互換はほぼありませんが、以下のようなケースを扱っている場合は注意が必要です。
ExceptionWrapper のインスタンス生成直後に backtrace の生成副作用に依存していたコード
- たとえば「ExceptionWrapper を作ること自体が backtrace クリーンナップを実行するトリガーだ」と仮定していたようなコードがあると、今回の変更で挙動が変わります。
- ただし、そのような使い方はかなり内部実装依存であり、通常のアプリケーションコードでは想定されていません。
exception.backtraceとwrapper.backtraceのタイミング差- 遅延評価により、
exception.backtraceの取得タイミングとwrapper.backtraceの生成タイミングが完全には一致しなくなる可能性があります。 - もっとも、Ruby の例外オブジェクトの backtrace は例外発生時点で固定されており、後で取得しても同じ内容なので、実害はほとんどありません。
- Rails の backtrace cleaner がどのタイミングで呼ばれるかが変わるだけです。
- 遅延評価により、
スレッド/並行実行との関係
- 例外オブジェクトや ExceptionWrapper を複数スレッドで共有し、その backtrace を同時に読むようなコードがある場合、
@backtrace ||= ...のような遅延初期化におけるスレッドセーフティが理論上の論点になります。 - ただし Ruby のインタプリタロック(GIL)や、Rails コードの一般的な利用パターンを考えると、実用上は問題になる可能性は低いです(とはいえ、マルチスレッドで内部オブジェクトを共有するような高度な使い方をしている場合は一応意識しておくとよいです)。
- 例外オブジェクトや ExceptionWrapper を複数スレッドで共有し、その backtrace を同時に読むようなコードがある場合、
3-3. ログ・エラーページ
- 開発環境のエラーページや、ログに backtrace を出す場面でのみ backtrace が生成されるようになるため、
- 本番環境で backtrace を抑制している設定であれば、これまで「裏で無駄に backtrace を組み立てていたコスト」が削減されます。
- 表示される backtrace の内容自体は変わらない想定であり、「開発時の見え方が変わる」といった影響は基本的にありません。
- 参考情報 (あれば)
- PR: https://github.com/rails/rails/pull/58686
- 関連コンセプト:
- 「遅延評価(Lazy evaluation)」 / 「遅延初期化(Lazy initialization)」
- Rails の backtrace クリーンナップ:
Rails.backtrace_cleaner
- 類似の最適化は、
- ログメッセージの組み立てを「ログレベルを満たしたときだけ行う」
- ActiveSupport::Notifications で「購読者がいないときは payload の重い処理を行わない」
といったパターンでもよく見られます。
#58687 Skip testing Action Mailbox and Active Storage scripts
マージ日: 2026/9/7 | 作成者: @byroot
- 概要 (1-2文で)
このPRは、Action Mailbox と Active Storage に関するガイド用バグレポートテンプレートのスクリプトテストを、一時的にスキップする変更です。原因は、テスト実行時に使用されるrails 8.1.3とjson 3.0の組み合わせが非互換であり、rails 8.1.4が出るまでテストが正常に動かないためです。
- 変更内容の詳細
対象ファイル:
guides/test/bug_report_template_test.rb(+5/-0)
やっていることは「特定のスクリプトテストを skip する」だけです。guides/test/bug_report_template_test.rb では、Rails ガイドに掲載されている「バグレポートテンプレート」のスクリプトが実際に動くかどうかをテストする仕組みがありますが、そのうち Action Mailbox と Active Storage に関連するものが、現在の依存関係 (rails 8.1.3 + json 3.0) だと失敗するため、これらを明示的にスキップするようにしています。
イメージとしては以下のような変更が入っていると考えられます(擬似コード):
class BugReportTemplateTest < ActiveSupport::TestCase
# 既存の他のテンプレートテストはそのまま
test "action mailbox bug report template" do
skip "Incompatible with rails 8.1.3 + json 3.0; re-enable after 8.1.4"
# ここで本来はスクリプトを実行する処理がある
end
test "active storage bug report template" do
skip "Incompatible with rails 8.1.3 + json 3.0; re-enable after 8.1.4"
# ここで本来はスクリプトを実行する処理がある
end
end実際には上記と文言やメソッド名は異なる可能性がありますが、「該当テストに skip を追加する」という方向の変更で、コードロジック自体の修正や仕様変更は行っていません。
PR の説明にもある通り:
They run using
rails 8.1.3andjson 3.0which aren't compatible. We can unskip them once8.1.4is released.
と明示されており、Rails 8.1.4 が出たタイミングで skip を外す前提の暫定対応 です。
- 影響範囲・注意点
- 影響範囲
- 影響を受けるのは Rails ガイド用テストスイート(
guides/test)のみであり、本体コード(Action Mailbox / Active Storage の実装)やアプリケーションコードには一切影響しません。 - CI 上で
guides関連のテストを回している場合、- Action Mailbox / Active Storage のバグレポートテンプレートのスクリプトテストがスキップされるため、「ガイドに掲載されているサンプルが最新環境で本当に動くか」の担保が一時的に弱くなります。
- 影響を受けるのは Rails ガイド用テストスイート(
- なぜ必要か
rails 8.1.3とjson 3.0の非互換により、テスト自体が環境要因で落ちてしまうため、実装上のバグと区別がつかなくなるのを防ぐ目的があります。- 一時的にスキップすることで、CI のノイズを減らし、本質的な回 regressions の検出を優先できます。
- 注意点
8.1.4リリース後にこのスキップを戻し忘れると、ガイド用テンプレートの実行確認が永続的に抜けてしまうので、Rails コアチーム側で「8.1.4 リリース後に unskip するタスク」を持っておく必要があります。- 自分の環境で「ガイドのバグレポートテンプレートを実際に試したい」場合は、
rails 8.1.3+json 3.0の組み合わせだと問題が起こりうる前提で検証したほうがよいです。
- 参考情報 (あれば)
- PR: https://github.com/rails/rails/pull/58687
- コンテキスト:
jsongem は Ruby/Rails 環境において頻繁に互換性問題の原因になることがあり、今回もrails 8.1.3とjson 3.0の組み合わせが特定のスクリプト実行テストに影響しているケースです。- Action Mailbox / Active Storage 自体が壊れているというよりは、「ガイドのバグレポートテンプレートスクリプトを、現状のテスト実行環境で動かすことができない」という性質の問題と考えられます。
#58109 Re-document application template advanced usage [ci skip]
マージ日: 2026/9/7 | 作成者: @jeromedalbert
- 概要 (1-2文で)
Railsガイドから消えてしまっていた「Application Template の高度な使い方(Advanced Usage)」セクションを、Generatorsガイド内に復活させるドキュメント修正のPRです。特に、テンプレートのディレクトリ(__dir__)を基準に Thor のcopy_fileなどが使えるようにするsource_pathsの設定方法が再度明示的にドキュメント化されました。
- 変更内容の詳細
何が変わったか
guides/source/generators.mdに、「Application Template Advanced Usage」相当の内容が再追加されました。- 主な内容は、Thor のテンプレート系メソッドが参照する
source_pathsをアプリケーションテンプレート側で上書き・拡張する方法の説明です。
背景
- 以前は「Rails Application Templates」ガイドに「Advanced Usage」セクションがあり、
「テンプレートファイル群をテンプレートスクリプトの隣に置いて、copy_fileなどで相対パス参照する」
というよくあるパターンが解説されていました。 - しかし #55020 で Application Template のガイドと Generators ガイドが統合される際、このセクションが落ちてしまい、
Rails Discuss でも「どう書くのが正しいのか?」という混乱が見られたため、その部分だけを復活させた形です。
具体的なテクニック: source_paths の調整
PR で再ドキュメント化された内容の要点を、代表的なコード例で説明します。
1. テンプレートファイルをテンプレートスクリプトの隣に置く
例えば以下の構成でテンプレートを配布したい場合:
my_template/
template.rb
app_files/
README.md
.rubocop.ymltemplate.rb の中で source_paths を調整して、copy_file が app_files 以下を見に行くようにします。
# template.rb
# テンプレートのソースパスに、template.rb と同じディレクトリ配下の app_files を追加
def source_paths
[File.expand_path("app_files", __dir__)] + super
end
# 以降、Thor の API で相対パス参照が可能になる
copy_file "README.md", "README.md"
copy_file ".rubocop.yml", ".rubocop.yml"ポイント:
__dir__は現在の Ruby ファイルが置かれているディレクトリを返すため、File.expand_path("app_files", __dir__)で「テンプレートスクリプトの隣のapp_filesディレクトリ」を指せる。superを足しているのは、既存のsource_paths(Rails 側で既に定義されているパス)を残すため。
上書きではなく「追加」の形をとるのが一般的です。
2. source_paths.unshift / source_paths << などによる拡張
テンプレート内でモンキーパッチ的に source_paths を直接書き換えるパターンもよく使われます。
(ガイドではこの書き方に触れている可能性があります)
# 既存の source_paths の先頭に追加する例
source_paths.unshift(File.expand_path("app_files", __dir__))
# or 末尾に追加
source_paths << File.expand_path("extra_templates", __dir__)このようにしておくと、copy_file, template, directory など Thor が提供するメソッドを、
テンプレートスクリプトのディレクトリ構造と連動させて使いやすくなります。
- 影響範囲・注意点
- コードへの影響は一切なく、ドキュメントのみの変更です。既存アプリやテンプレートが壊れることはありません。
- ただし、これまで「どうやるべきか分からず独自流儀で書いていた」テンプレート作者にとっては、
__dir__を使ってテンプレートファイルをまとめて管理するsource_pathsを適切に拡張して Thor のメソッドに解決させる といった「推奨的な書き方」が明文化されたことで、今後テンプレート構成をリファクタリングする指針になります。
- ドキュメントが Generators ガイド側に移ったため、
「テンプレート関連の高度な話は Generators ガイドを見る」
というナビゲーションが分かりやすくなります。
- 参考情報 (あれば)
- 当該 PR: https://github.com/rails/rails/pull/58109
- Application Template と Generators ガイドの統合 PR: https://github.com/rails/rails/pull/55020
- 旧ドキュメント(削除前)の Advanced Usage セクション:
https://github.com/rails/rails/blob/04df9bc3d120b51447bde54caa56e9237cb8da0e/guides/source/rails_application_templates.md?plain=1#L278-L296 - Rails Discuss での関連トピック:
https://discuss.rubyonrails.org/t/add-template-source-dir-to-templating-support/91327
アプリケーションテンプレートを自作・配布している場合は、
「テンプレートスクリプトと同じディレクトリにファイル群を置き、source_paths で参照する」
というパターンで設計できているか、見直すきっかけになる変更です。
#58675 Fix docker-entrypoint check to support extra bin/rails server flags
マージ日: 2026/9/6 | 作成者: @iamdipak39p
- 概要 (1-2文で)
Rails のアプリ生成時に作られるbin/docker-entrypointが、bin/rails serverに追加フラグを付けた場合にdb:prepareを実行しなくなる問題を修正した PR です。Docker のCMD ["./bin/rails", "server"]形式に合わせて、引数のチェック方法を「末尾2つ」から「先頭2つ」に変更し、あわせてテストを追加しています。
- 変更内容の詳細
問題点
生成される bin/docker-entrypoint は、サーバ起動時にだけ db:prepare を実行するため、次のような条件分岐をしていました:
if [ "${@: -2:1}" == "./bin/rails" ] && [ "${@: -1:1}" == "server" ]; then
./bin/rails db:prepare
fiここでは「コマンドライン引数の最後から2番目が ./bin/rails、最後が server」という前提で判定しているため、
./bin/rails serverのようなシンプルな呼び出しなら動作しますが、Docker / Podman でよくやるようにフラグを足すと:
./bin/rails server -b 0.0.0.0server が末尾から2番目の位置からずれてしまい、条件が偽になって db:prepare が実行されませんでした。この結果、コンテナ起動時にマイグレーション・テーブル作成が行われないというバグになっていました。
修正内容
条件チェックを「末尾2つ」から「先頭2つ」に変更しています。Docker の CMD / ENTRYPOINT を JSON 配列形式で書く場合、ベースとなるコマンドは常に先頭に来るため、それを利用します。
修正前:
if [ "${@: -2:1}" == "./bin/rails" ] && [ "${@: -1:1}" == "server" ]; then
./bin/rails db:prepare
fi修正後:
if [ "${1}" == "./bin/rails" ] && [ "${2}" == "server" ]; then
./bin/rails db:prepare
fiこれにより、以下はいずれも db:prepare が実行されます:
# ベーシックなケース
./bin/rails server
# ポートバインドやその他オプションを付与してもOK
./bin/rails server -b 0.0.0.0
./bin/rails server -p 3000 -b 0.0.0.0テスト追加
railties/test/generators/app_generator_test.rb に2つのテストが追加されています。
生成される
docker-entrypointが 先頭2引数${1},${2}をチェックしている ことを検証- 旧形式(
${@: -2:1},${@: -1:1})に戻っていないことの回帰テストも兼ねています。
- 旧形式(
--skip-active-recordオプションでアプリ生成した場合には、引き続きdb:prepare実行ロジックが含まれないことを検証- Active Record を使わないアプリでは、DB 関連コマンドが不要であるという従来仕様が保たれていることを確認しています。
- 影響範囲・注意点
- Docker / Podman で Rails を
bin/rails serverから起動しているプロジェクト- 追加のサーバフラグ(
-b 0.0.0.0,-p 3000,-e productionなど)を付けても、意図通りコンテナ起動時にdb:prepareが毎回実行されるようになります。
- 追加のサーバフラグ(
- エントリポイントの呼び方をカスタマイズしている場合
- このロジックは「
./bin/railsが第1引数」「serverが第2引数」であることを前提とします。 - 例:
docker-entrypointをbundle exec rails serverのように別のラッパーで呼び出す、あるいはCMD ["bash", "bin/docker-entrypoint", "rails", "server"]のようにしていると条件にマッチせず、db:prepareは実行されません。 - そのような場合は、エントリポイントか CMD/ENTRYPOINT の形を
["./bin/rails", "server", ...]に揃える必要があります。
- このロジックは「
- 既存アプリへの影響
- generator で新規に作られる
docker-entrypointテンプレートの変更であり、既に生成済みのアプリのbin/docker-entrypointは自動では書き換わりません。必要であれば手動で同様の修正を行う必要があります。
- generator で新規に作られる
- Active Record を使わないアプリ
--skip-active-recordで生成したアプリは、従来どおりdb:prepareロジックが含まれず、挙動は変わりません。
- 参考情報 (あれば)
- 対応する issue:
Fixes #58652- 内容: Docker コンテナで
./bin/rails server -b 0.0.0.0のように実行すると DB が自動で準備されない問題。
- 内容: Docker コンテナで
- 変更対象ファイル:
railties/lib/rails/generators/rails/app/templates/docker-entrypoint.tt- 条件式を
${@: -2:1}から${1},${2}に変更。
- 条件式を
railties/test/generators/app_generator_test.rb- 上記挙動を保証するためのテストを2件追加。
#58673 Refactor more class variables to accessors and delegators
マージ日: 2026/9/5 | 作成者: @gmcgibbon
- 概要 (1-2文で)
このPRは、Rails内部で使われていたクラス変数ベースの設定(mattr_accessor)をインスタンス用のattr_accessor等に置き換え、Ruby 4.0.6 でも Ractor から安全に参照できるようにするリファクタリングです。主に Action Pack / Action View の各種ヘルパの設定値の持ち方が変わり、それに対応するテストが追加されています。
- 変更内容の詳細
背景
- Ruby 4.0.6 では
@@varのようなクラス変数が Ractor セーフではない挙動をする一方、Ruby の edge 版では改善されている。 - Rails 内部では設定やフラグを
mattr_accessor(クラス変数ベースのアクセサ)で持っている箇所が多く、Ractor 利用時の安全性に問題が出る可能性がある。 - このPRは、そうした「クラススコープの設定」を、Ractor からも安全に扱えるように、クラス変数ではない形(
attr_accessor+ 適切な保持先)に移行する一環。
主な修正ポイント
※ファイルレベルの要約です。実際のコードは概ね以下のような方向の変更になっています。
1. ActionDispatch::Http::URL の設定まわり
変更ファイル: actionpack/lib/action_dispatch/http/url.rb
URL 生成時に使う各種設定(例:
default_port,asset_host的なURL関連設定)を保持するクラスレベルの状態管理から、インスタンスアクセサ経由の管理に移行。もともと以下のような形式だったと考えられます(疑似コード):
rubyclass ActionDispatch::Http::URL mattr_accessor :some_config, default: true endこれが、クラスインスタンス変数 +
class << self; attr_accessor ... endなど、@@を通らない構造になっている形です。変更行数を見ると
+8/-9と小さな差分なので、「アクセサ定義の仕方を変えたが、外部から見たAPIはそのまま(ActionDispatch::Http::URL.some_config=などで触れる)」というリファクタリングである可能性が高いです。テスト:
actionpack/test/dispatch/url_generation_test.rbに +19行
→ URL 生成が Ractor 上でも問題なく動く、あるいは設定値が期待通りに反映されることを確認するテストが追加されています。
2. Action View 各種ヘルパの設定
対象ファイル:
actionview/lib/action_view/helpers/content_exfiltration_prevention_helper.rbactionview/lib/action_view/helpers/form_helper.rbactionview/lib/action_view/helpers/form_tag_helper.rbactionview/lib/action_view/helpers/navigation_helper.rb
ここでは、以下のような「グローバルなヘルパ設定」を担っていそうなフィールドが対象になっています。
例(代表的なパターンのイメージ・疑似コード):
module ActionView::Helpers::FormHelper
# 以前:
mattr_accessor :default_form_builder, instance_writer: false, default: SomeBuilder
# 以後(イメージ):
class << self
attr_accessor :default_form_builder
end
@default_form_builder = SomeBuilder
endあるいは、モジュールに対して mattr_accessor でクラス変数を払い出していたものを、cattr_accessor 的な形やモジュール固有のインスタンス変数 + attr_accessor に替えているはずです。
想定される具体例:
FormHelper/FormTagHelperdefault_form_builderや、field_error_proc等の「フォーム全体に影響する設定」をクラス変数からクラスインスタンス変数へ移行。
NavigationHelper- ナビゲーションレンダリング時の設定(例:
current_page?判定に関するオプションや、リンクの生成ポリシー等)に関連した設定変数が変更されている可能性があります。
- ナビゲーションレンダリング時の設定(例:
ContentExfiltrationPreventionHelper- CSP的な出力エスケープ・コンテンツの持ち出し防止用のフラグ・設定クラス変数を、Ractor セーフな形へ移行。
3. Ractor + 各ヘルパ向けのテスト追加
変更ファイル:
actionview/test/template/base_ractor_test.rb(+10)actionview/test/template/form_helper_test.rb(+23)actionview/test/template/form_tag_helper_test.rb(+23)actionview/test/template/navigation_helper_test.rb(+11)
これらは、
- Ractor 内からヘルパを呼び出した場合にも、
- 設定値が参照できる
- 競合が起きない
- 例外が発生しない
- 従来通りのグローバル設定をいじるようなケースでも(
FormHelper.default_form_builder = ...等)、正常に動作する
といった動作保証を行うテストが中心になっていると考えられます。
base_ractor_test.rb は Action View の Ractor 対応テストのベースクラス/共通化のための試験コードが追加されている可能性が高いです。
- 影響範囲・注意点
公開APIレベルでの影響
- PRの内容・差分規模からすると、利用者のコードから見たAPI(メソッド名や使い方)はほぼ変わらないリファクタリングであると考えられます。
- たとえば
ActionView::Base.field_error_proc = ...やActionView::Helpers::FormHelper.default_form_builder = ...のような設定コードはそのまま動作するはずです。
- たとえば
- ただし、クラス変数 (
@@xxx) に直接アクセスしていたり、Rails の内部実装前提のハックをしている場合は注意が必要です。- 例:
ActionView::Helpers::FormHelper.class_variable_get(:@@default_form_builder)のようなコードは動かなくなっている可能性が高いです。
- 例:
スレッド / Ractor セーフティ
- Ruby 4.0.6 で Ractor を利用しているアプリケーションにとっては、
- Action Pack / Action View 内部でクラス変数を共有していた部分が減り、Ractor 間での不正共有や例外の発生可能性が下がります。
- Ractor ごとに別々の設定値を持ちたい、という要件まではこのPRのスコープ外で、
- あくまで「現状の『グローバル設定』モデルを維持しつつ、Ractor が問題なく使えるようにする」ことが目的です。
パフォーマンス・挙動変化の可能性
- クラス変数からクラスインスタンス変数への移行は、通常はほとんどパフォーマンス差は出ません。
- ただし、クラス階層をまたいでクラス変数の共有に依存していた場合(親クラス/モジュールで定義した
@@varを子クラスも共有するようなパターン)は、- クラスインスタンス変数化により「親と子で別々の値になる」可能性が出てきます。
- Railsの設計上、そのような継承前提の使い方を避ける方向に寄せる意味でも、この変更は理にかなっていますが、もし独自にヘルパを継承していて内部変数を見ている場合は挙動確認をした方が安全です。
- 参考情報 (あれば)
- PR本体: https://github.com/rails/rails/pull/58673
- 関連概念:
mattr_accessorは Rails のメタプログラミングヘルパで、内部的にクラス変数@@を使うため、Ractor では問題になることがある。- Ractor セーフな状態共有を行うには、
- クラスインスタンス変数 + スレッド/Ractor 安全なオブジェクト
- もしくはイミュータブルな定数・コピーオンライト前提の値
などを使う必要がある。
- Ractor とクラス変数の問題点は、Ruby 本体の Issue/Changelog にて継続的に議論されており、edge Ruby では徐々に改善中であるものの、Rails 側では保守的にクラス変数依存を減らす方向で対応している流れと見られます。
#58640 Allow to access view paths in a Ractor
マージ日: 2026/9/4 | 作成者: @Edouard-chin
- 概要 (1-2文で)
RailsアプリケーションをRactor対応(並列実行)した際に、「ビューのテンプレート探索(view paths lookup)」ができない問題を修正し、Ractor内からもビューのパスにアクセスできるようにした変更です。具体的には、Ractorで共有できなかったビュー関連キャッシュを「Ractor-shareable」になるように扱う(freezeする)ことで、Ractor内からのテンプレート検索を可能にしています。
- 変更内容の詳細
背景・問題点
- Ractor内でテンプレートをレンダリングしようとすると、テンプレート探索処理が内部で参照するビュー関連のキャッシュが「Ractor-shareable」ではないためエラーになる、もしくはアクセスできませんでした。
- Railsのビューシステム(Action View)は、テンプレート探索結果などをキャッシュして高速化していますが、このキャッシュオブジェクトがミュータブルな状態だとRactor間で共有できません。
- Ractorでは「凍結された(freezeされた)オブジェクト」など、一部の条件を満たすオブジェクトだけが共有可能です。
このPRでの方針
- 「Ractor化(ractorize)されたアプリケーション」では、ビュー関連のキャッシュを凍結してRactor-shareableにする、という割り切った実装を採用しています。
- これは「eager load が有効な環境」を前提にしています。理由は、lazy load/autoload 中にコントローラが
append_view_pathsなどでキャッシュを書き換える可能性があるためです。
PRの説明中の例:
class MyController < ApplicationController
append_view_paths("some/folder") # ここでビューのパスに関するキャッシュが更新される
endこのようなコードが autoload のタイミングで実行されると、Ractor化した後にキャッシュがミュータブルなまま書き換えられてしまう可能性があります。そのため、
- 「eager load を完了させてから」
- 「ビュー関連のキャッシュを freeze して」
Ractor実行を開始する、というフローを前提にしています。
具体的なコード変更(ファイルごとの概要)
※行数から読み取れる範囲での性質・目的を説明します。
actionview/lib/action_view/path_registry.rb(+11/-0)
- View paths のレジストリ(
ActionView::PathRegistry)周りに、Ractor対応のための変更が入っています。 - 典型的には以下のようなことをしている可能性が高いです:
- 内部のキャッシュ構造(たとえば
@cacheのようなハッシュや配列)を凍結するメソッドの追加 - Ractor対応用のメソッド(例:
ractorize!など)で、パスレジストリ内のオブジェクトを再構成/コピーして shareable にする、あるいはfreezeする
- 内部のキャッシュ構造(たとえば
- これにより、
ActionViewが持つビュー探索キャッシュが Ractor からも参照可能な形になります。
railties/lib/rails/application.rb(+1/-7)
- Railsアプリケーション全体をRactor対応するための「ractorize」処理の中で、ビュー関連キャッシュの扱いを変更しています。
- 行数減から推測すると:
- 以前は ractorize の中で別の(より大きな)処理をしていたのを簡略化し、View Path Registry 側の新しいAPI(freeze処理など)を呼び出すように整理した可能性があります。
- もしくは、複数箇所でやっていたキャッシュの調整を一本化した、または eager load 前後のタイミングを整えた変更です。
railties/test/application/ractors_test.rb(+16/-0)
- Ractor内からビューにアクセスできることを検証するテストが追加されています。
- 典型的には次のような流れのテストが考えられます:
- テスト用のアプリケーションを eager load する
- アプリケーションを Ractor 用に初期化(ractorize)する
- Ractor を起動し、その中でコントローラまたはビューをレンダリングするコードを実行
- エラーなくテンプレートが見つかりレンダリングできるかを確認
このテストにより、「Ractor内でのテンプレート探索が実際に成功すること」が自動的に保証されるようになっています。
- 影響範囲・注意点
影響範囲
- 影響するのは「Ractor を使って Rails アプリケーションを実行し、Ractor 内でビューをレンダリングするケース」です。
- 通常の(シングルRactor、マルチスレッド)運用のみであれば振る舞いはほぼ変わらない想定です。
- Action View のパスレジストリやビュー探索キャッシュの内部実装に依存しているコードがあれば、freeze による影響(変更不能になること)を受ける可能性はありますが、普通のアプリケーションでは直接触ることはまれです。
注意点
eager load が前提
- Ractorでビューを使いたい場合、
config.eager_load = trueなどにして、アプリケーションとビュー関連クラスがあらかじめ eager load されている必要があります。 - 開発環境のように autoload / lazy load に頼っている設定では、
- コントローラが autoload されるタイミングで
append_view_pathsなどを実行し、 - それが freeze 後のキャッシュに対して変更を試みる
といった問題が起きる可能性があります(これはPR中でも「非 eager loaded 環境はまだ最適化の対象外」と明言)。
- コントローラが autoload されるタイミングで
- Ractorでビューを使いたい場合、
append_view_pathsなどの動的変更との相性- Ractor化後(キャッシュ freeze 後)に
append_view_pathsのような API を使うと、- 例外になる
- あるいは無視される
など、挙動に制約が出る可能性があります。
- Ractor運用を想定するなら、ビューのパス構成は起動時に確定させ、実行時に追加/変更しない設計が望ましいです。
- Ractor化後(キャッシュ freeze 後)に
Ractor対応はまだ実験的な領域
- PR本文からも、「まずは eager load 環境に絞って現実的な解決を入れた」というスタンスが読み取れます。
- 非 eager load 環境でRactorを使うと、今回のビューキャッシュ以外にも同様の問題(ミュータブルなグローバル状態)が多数あると考えられるため、Ractorを本番導入する場合は十分な検証が必要です。
- 参考情報 (あれば)
- 関連しそうなRails内部コンポーネント:
ActionView::LookupContext… ビュー探索の中心クラスで、path set や template cache を扱うActionView::PathRegistry… ビューのパス集合とそれに紐づくキャッシュを管理するクラス(本PRで直接変更された部分)
- Ractorとshareableオブジェクト:
- Ruby 3系のRactorでは、
Ractor.shareable?(obj)が true を返すオブジェクトのみをRactor間で共有できます。 freezeされたオブジェクト(およびそこから辿れるオブジェクトも適切に freeze 済みであること)が基本的な要件になるため、「キャッシュをfreezeしてRactor-shareableにする」という方針はRubyのRactor設計に沿った妥当なものです。
- Ruby 3系のRactorでは、
- 今後の展望:
- 非 eager load / 開発環境でのRactorサポートをきちんと行おうとすると、autoloader(Zeitwerk)や各種キャッシュ、グローバル設定を広範に見直す必要があります。
- このPRは「まずはRactorでもビューが使えるようにする」という第一歩的な位置付けで、Ractorサポート全体の中では一部機能の解消に相当します。
#58659 Fix Ractor unshareable procs at boot
マージ日: 2026/9/4 | 作成者: @etiennebarrie
- 概要 (1-2文で)
Rails のブート時に生成されるいくつかの proc / lambda が Ractor 的に「共有不可能(unshareable)」になっていた問題を、キャプチャするローカル変数を整理することで「共有可能(shareable)」にする修正です。Ractor ワーカー利用時の IsolationError を避けつつ、ルートリローダーなど一部の「どうしても共有できない値」は Ractor 非対応機能として扱う方針を明示しています。
- 変更内容の詳細
背景: Ractor と shareable / unshareable オブジェクト
Ruby 3 の Ractor では、Ractor 間で自由に共有できるのは「shareable」にマークされたオブジェクトだけです。Proc / lambda も、そのクロージャがキャプチャしている値に「unshareable」なオブジェクト(例: ミューテックス、IO、通常のオブジェクトグラフなど)が含まれていると Ractor.make_shareable できず、Ractor ワーカー起動時などにエラーになります。
この PR では、
- Rails のブートプロセスで定義される複数の
lambda/procが - ブロック内で不要に「unshareable なコンテキスト」をキャプチャしてしまっていたのを
- 「必要なローカル変数だけを明示的に取り出してキャプチャする」パターンに統一
することで、Ractor 対応を強化しています。
主なパターン: 「使う値だけローカルに退避してから lambda を定義」
ほとんどの変更は同じリファクタリングパターンです。
典型パターン(抽象化)
修正前(イメージ):
some_setup do
# ここで self や外側のコンテキストをそのままキャプチャする
callback = -> { do_something_with(self.config) }
register_callback(callback)
end修正後(イメージ):
some_setup do
config = self.config # shareable な値だけ抜き出す
callback = -> { do_something_with(config) }
register_callback(callback)
endポイント:
selfやレシーバに紐づいた巨大なオブジェクトグラフをキャプチャせず- shareable な値(例えば、freeze された設定値やシンボル、文字列、単純な Hash など)だけをローカル変数に退避し、それを lambda 内で参照
- これによって
callback自体をRactor.make_shareableできるようになる
このパターンが、以下のファイルで適用されています。
actioncable/lib/action_cable/engine.rbactivejob/lib/active_job/railtie.rbactiverecord/lib/active_record/associations/builder/association.rbactiverecord/lib/active_record/belongs_to.rbactiverecord/lib/active_record/secure_token.rbrailties/lib/rails/application/finisher.rbrailties/lib/rails/engine.rb
各所とも、ブート時に登録される初期化処理・コールバック・フックなどを、
- 「Ractor 的に安全な lambda/proc」
- かつ「グローバルなブート時に一度だけ定義されるもの」
として扱えるようにしています。
ActiveRecord::Associations / belongs_to まわり
belongs_to や関連ビルダーでは、関連付けの設定やオプションをクロージャでキャプチャしておき、後からコールバック内で使うパターンがよくあります。この PR では、そのキャプチャ対象を最小限のローカル変数に絞り、Ractor で共有しやすいように整理しています。
ActiveRecord::SecureToken
has_secure_token などで使われるトークン生成ロジックも lambda/proc で保持されることが多く、ここでも同様に「共有可能な値だけをキャプチャする」ように変更されています。たとえば:
- 対象カラム名
- 指定された長さ
- 生成メソッド
などをローカル変数として抜き出し、その変数だけをクロージャ内で参照するようにすることで、トークン生成ロジックの proc が Ractor で安全に共有可能になります。
Routes reloader の特別扱い
説明文にもある通り、ルートのリローダーだけは他とパターンが異なります。
- 「ルートリローダーが保持する状態」自体が Ractor 的に unshareable
- よって、その値をキャプチャした
Procを shareable にすることは不可能
このため:
- ルートリローダーに関しては「unshareable な値は reloader オブジェクトに保持させる」
- proc 側からは reloader を使って処理するが、proc 自体は shareable にするのが困難
- Ractor ワーカーからこのブロックを呼び出した場合は
IsolationErrorが起きうる
ただし、現状の前提として:
Ractor workers are only supported for apps that don't enable reloading, and those don't mount the reloader middleware at all.
- Ractor ワーカーは「コードリロードを有効にしていないアプリ」でしかサポートしていない
- その場合は reloader middleware 自体をマウントしない=問題の proc が Ractor から呼ばれない
という実運用上の制約があるため、「ルートリローダーは Ractor 非対応で OK」という整理になっています。
テスト (railties/test/application/ractors_test.rb)
railties/test/application/ractors_test.rb にテストが追加され、以下を検証しています:
- Rails アプリケーションを Ractor ワーカー対応の構成で起動したとき
- ブート時に定義される各種 proc / lambda が
Ractor.make_shareable可能であること - #58655 と 88f486fc18 のコミットに依存している(これらがないとテストが通らない)
このテストが「どの初期化フックが Ractor shareable であるべきか」の仕様を事実上固定し、今後のリグレッションを防ぐ役割を担っています。
- 影響範囲・注意点
- 影響を受けるのは主に Ractor を使うアプリケーション
- Ractor ワーカーを利用していない通常の Rails アプリでは、挙動の変化はほぼありません(クロージャの書き方が変わっただけで、ロジックは同じ)。
- Ractor ワーカー + コードリロード有効 は依然として非サポート
- ルートリローダーは unshareable なままなので、Ractor ワーカーとコードリロードを同時に有効にする構成は、引き続きサポート外です。
- その場合、reloader に関わる処理が Ractor 内で実行されると
IsolationErrorが発生し得ます。
- 独自の Railtie / Engine / 初期化処理を書く場合の実務的注意
- Ractor を見据えている場合、今回の PR のパターンを真似するのが安全です:
initializerやto_prepareなどでlambda/procを定義するときは、selfやRails.applicationなどを丸ごとキャプチャしない- 必要な情報だけローカル変数に抜き出し、その変数だけをクロージャ内で参照する
- shareable でないオブジェクト(DB コネクション、ミドルウェアインスタンスなど)は Ractor 間で共有しない前提で設計する
- Ractor を見据えている場合、今回の PR のパターンを真似するのが安全です:
- 参考情報 (あれば)
- 該当 PR:
- 依存している PR / コミット:
- #58655
- コミット
88f486fc18
- Ractor / shareable について:
- Ruby 3.0 Ractor docs: https://docs.ruby-lang.org/en/master/doc/ractor_md.html
(Ractor.make_shareableや shareable object の制約など)
- Ruby 3.0 Ractor docs: https://docs.ruby-lang.org/en/master/doc/ractor_md.html
#58669 Fix broken link to Redirecting Requests in layouts_and_rendering.md
マージ日: 2026/9/4 | 作成者: @yahonda
- 概要 (1-2文で)
Rails ガイド「Layouts and Rendering」の中で、存在しないアンカー#redirecting-requestsへの自己リンクがあったため、正しいガイド「Action Controller Overview」の該当セクションへのリンクに修正した PR です。これによりrake guides:lintのリンクチェックが再び通るようになっています。
- 変更内容の詳細
問題点
guides/source/layouts_and_rendering.md内でmarkdownのように同一ファイル内の見出し[Redirecting Requests](#redirecting-requests)#redirecting-requestsを参照していました。- しかし、
layouts_and_rendering.mdにはそのセクションが存在せず、実際にはaction_controller_overview.mdの中にあるため、rake guides:lint実行時に以下の警告が出ていました:text[WARN] BROKEN LINK(s): layouts_and_rendering.md: #redirecting-requests
修正内容
- 上記の自己参照リンクを、別ガイド「Action Controller Overview」の該当アンカーへのクロスリンクに変更しています。
- 具体的には、リンク先を以下のように修正した形です(実際の一部抜粋例):diff
- See [Redirecting Requests](#redirecting-requests) for more details. + See [Redirecting Requests](action_controller_overview.html#redirecting-requests) for more details. - 同じファイル内の後ろの方には、すでに
action_controller_overview.html#redirecting-requestsへのクロスガイドリンクが存在しており、それと揃える形になっています。
検証
- ローカルで以下のコマンドを実行し、いずれも exit code 0 で通ることを確認済み:bash
rake guides:lint:check_links rake guides:lint:mdl
- ローカルで以下のコマンドを実行し、いずれも exit code 0 で通ることを確認済み:
- 影響範囲・注意点
影響範囲
- 変更対象はガイド文書 (
guides/source/layouts_and_rendering.md) のみで、Rails の実行コードや挙動には一切影響しません。 - Rails ガイドを HTML 生成したときに、「Redirecting Requests」へのリンクが正しく
Action Controller Overviewガイド内の該当セクションに飛ぶようになります。 - CI(Buildkite など)で
rake guides:lintを実行している場合、#57152 以降に発生していたリンク切れによる失敗が解消されます。
- 変更対象はガイド文書 (
注意点
- もし他ガイドから
#redirecting-requestsを layouts_and_rendering 側に向けてリンクしている箇所があれば、同様にaction_controller_overview.html#redirecting-requestsに統一する必要があります(この PR はそのうち 1 箇所のみを修正)。 - 将来的にレイアウト/レンダリングのガイドに「リダイレクト」に関する独自セクションを追加する場合は、アンカー名の重複やリンク先の混在に注意する必要があります。
- もし他ガイドから
- 参考情報 (あれば)
- 問題を検知した
rake guides:lintの Buildkite ログ(PR 説明より) - 関連 PR
- #57152: Layouts and Rendering ガイドの書き直し(この変更が入って以降でリンク切れが発生)
- 修正対象ファイル
guides/source/layouts_and_rendering.md(+2 / -1, リンク 1 箇所の微修正)
#57152 [RF-DOCS] Rewrite the Layouts and Rendering guide
マージ日: 2026/9/4 | 作成者: @ayushn21
- 概要 (1-2文で)
Layouts and Rendering ガイドがほぼ全面的に書き直され、内容の重複を整理しつつ「コントローラとビューの関係」を中心とした構成に再編されました。これに伴い、レイアウト構造やパーシャルなどの説明は Action View Overview へ移動され、リクエストバリアントの説明は Action Controller Overview から本ガイドへ移されています。
- 変更内容の詳細
2.1 ガイド全体の再編方針
- 目的:
- Layouts and Rendering ガイドが他ガイドと内容重複しており、話があちこちに飛ぶ構成になっていたため、「コントローラとビューの関係」に主眼をおいて書き直し。
- 主な再配置:
- Layouts and Rendering ガイドから削除/移動
- パーシャルの詳細な説明 →
Action View Overviewに移動 - レイアウトの構造、
yield/content_for、アセットタグヘルパーなど「ビュー寄り」の話 →Action View Overviewに移動
- パーシャルの詳細な説明 →
- Action Controller Overview から移動
- Request variants のセクション → Layouts and Rendering ガイドへ移動
(レンダリングの話とまとめることで、より自然な流れに)
- Request variants のセクション → Layouts and Rendering ガイドへ移動
- Layouts and Rendering ガイドから削除/移動
これにより、
- Layouts and Rendering: 「コントローラがどうレスポンスを作り、どのビュー・レイアウトを選ぶか」にフォーカス
- Action View Overview: 「ビューやレイアウトの書き方、構造、ヘルパー」にフォーカス
という役割分担が明確になりました。
2.2 Layouts and Rendering ガイドの主な内容整理
PR 本文や内部レビューコメントから読み取れる主なポイントです。
2.2.1 コントローラとビューの関係を中心に再構成
- コントローラの説明を、初心者にもわかるように修正:
- 以前: “heavy code を Model に渡す” という曖昧な表現
- 修正方針: “より複雑なロジックはモデルのメソッドへ委譲する” といった表現へ
- 「HTTP レスポンスとは何か」「それを作る3つの代表的な方法」が明示される方向:
renderで HTML や JSON などボディを含む完全なレスポンスを返すredirect_toで別 URL にリダイレクトするレスポンスを返すheadでヘッダのみのレスポンスを返す
renderの説明:- “full response” の意味を初心者向けに言い換え(例: ステータスコード + HTTP ヘッダ + レスポンスボディをブラウザに返すこと)
headの説明:- 「なぜヘッダだけ返すのか?」という動機例を入れる方向
- 例:
head :ok、head :no_content(204)、head :not_foundなど API エンドポイントや条件チェックに使う場面
- 例:
- 「なぜヘッダだけ返すのか?」という動機例を入れる方向
2.2.2 レンダリングの詳細 (render, redirect_to, head)
- 「Using
render」セクションを独立したトップレベルセクションに格上げする提案が反映されている可能性が高いです。- 同列に「Rendering by Default」「Using
redirect_to」「Usinghead」といったセクションが整理され、サイドバーに出るように構造改善。
- 同列に「Rendering by Default」「Using
renderに関して:renderオプション説明の整理 (template,partial,json,xml,inlineなど)inline:について:- 有効なオプションとして示しつつも、**「通常はあまり使うべきでない」**ことを警告ボックスで明示。
- もし使うとしても「小さなデバッグや非常に単純なレスポンス」などに限る、というトーンに。
- JSON/XML レンダリング (
render json:,render xml:) のセクションが重複気味だったため、1つのセクションに統合して説明する方針。
- ステータスコードと
render:status: :unprocessable_entityのような例について、- 「テンプレートをレンダするときに任意のステータスコードを付けられる」
- 「なぜそうするか(検証エラーなど)」に軽く触れ、
- 詳細は後段の HTTP ステータス・MIME 関連の節へのリンクで補足するよう整理。
2.2.3 MIME タイプとレスポンス形式
- MIME Content-Type の説明に対して、MDN Web Docs などへの外部リンクを付ける提案があり、ガイド内から参照しやすくなっています。
- JSON/XML レスポンスをまとめて説明:
- それぞれ別節だったものを「API レスポンス」「非 HTML レスポンス」としてまとめる形。
- Ajax という古い用語は避け、「API endpoint」「JSON で返す API」などの表現に更新されている可能性が高いです。
2.2.4 レイアウト探索ロジックの整理
- 「Finding Layouts(レイアウトの見つけ方)」がガイド冒頭でも末尾でも説明されていたため、内容を DRY に再編。
- レイアウト探索ルールをどこか1カ所に集約し、他の場所からはリンクする形に。
- ビルダー(例えば Jbuilder など)とレイアウトの関係に関する情報も、重複を減らすように整理。
- セクション 3 冒頭で「前に説明したレイアウト探索ルール」とだけ書いていた箇所に、明示的なリンクを追加する形に修正。
2.2.5 Request Variants の移動
- 元々 Action Controller Overview にあった リクエストバリアント(
request.variant/respond_to経由など) の節を Layouts and Rendering ガイドへ移動。 - これに伴い:
- Action Controller Overview には「レンダリングのごく短いイントロ」を書き足し、
- 詳細な内容は Layouts and Rendering ガイドへ誘導する形に。
2.2.6 Action View Overview の拡充
Layouts and Rendering から削った「ビュー側の話」をこちらに統合:
- 移動/強化されたテーマ:
- レイアウトの構造 (
application.html.erb、yield、content_for) - アセットタグヘルパー (
javascript_include_tag,stylesheet_link_tag,image_tagなど)
→ ここでまとめて説明し、Layouts and Rendering からは削除 - パーシャル (
render "form", コレクションのレンダリング、ローカル変数の渡し方など) - ネストしたレイアウト(使い所が限定的なため、説明を薄くするか・削るかの方向)
- レイアウトの構造 (
- 「Layouts and Rendering なのに、ビューの話ばかり」という状態を避け、ビューの話は概ねこちらに集約。
2.2.7 Action Controller Advanced Topics, Overview の差分
action_controller_advanced_topics.md- レンダリング関連の説明が少し削られ、内容を他ガイドへ移動。
action_controller_overview.md- 行数が +1003 / -839 と大きく変化しており、
- コントローラの役割
- レスポンスの作り方(
render,redirect_to,headの概要) - Layouts and Rendering へのリンク など、**「レンダリングへの入口」としての説明が手厚くなっていると考えられます。
- 行数が +1003 / -839 と大きく変化しており、
- 影響範囲・注意点
- コードへの直接の影響はなし(ガイドのみの変更)ですが、学習・参照パターンには影響があります。
- 以前: Layouts and Rendering ガイドに「パーシャル」「アセットタグ」「
yield/content_for」「ネストレイアウト」などが分散していた。 - 今後:
- コントローラが何を返すか/どうレンダするか → Layouts and Rendering
- ビュー/レイアウトの書き方・構造・ヘルパー → Action View Overview
- 以前: Layouts and Rendering ガイドに「パーシャル」「アセットタグ」「
- 古いブログ記事や社内ドキュメントで
- 「Layouts and Rendering ガイドの ○○セクションを参照」といったリンクや案内をしている場合、対応する場所が Action View Overview に移っている可能性があるので注意。
render inline:のように「存在はするが推奨されない API」については、ガイド上も「基本使わない」トーンが強まっているため、- これらに依存するコードを書く理由/必要性は、より慎重に検討すべき、というメッセージと読み取れます。
- JSON/XML/API 向けの説明が整理されているため、
- 新規開発者には「Ajax 用ビュー」ではなく「API エンドポイントとしてのコントローラ・レスポンス」という観点で教えるのが自然になります。
- 参考情報 (あれば)
- 該当 PR: https://github.com/rails/rails/pull/57152
- 関連しそうなガイド(変更後を前提として探すとよいもの):
- Layouts and Rendering:
guides/source/layouts_and_rendering.md - Action View Overview:
guides/source/action_view_overview.md - Action Controller Overview:
guides/source/action_controller_overview.md
- Layouts and Rendering:
- MIME タイプの基礎:
- レスポンスヘッダだけ返す用途の例:
head :no_content(204 No Content): 成功だが本文不要な APIhead :not_found(404): リソースが存在しない場合に本文不要で返す
(これらはガイドのheadセクションで例示されている可能性があります)
#58628 Use new max_connections instead of pool in guide [ci skip]
マージ日: 2026/9/4 | 作成者: @rnestler
- 概要 (1-2文で)
Rails 8.1 でデータベース設定オプションpoolがmax_connectionsに名称変更されたことを受けて、公式ガイド内の記述を新しいオプション名に合わせて修正した PR です。コードや挙動の変更ではなく、ドキュメント(ガイド)のみを対象とした修正です。
- 変更内容の詳細
何が変わったか
対象ファイル
guides/source/action_cable_overview.mdguides/source/configuring.md
主な変更点
- これまでデータベース接続数を表す設定としてガイド中で使われていた
poolというキー名を、Rails 8.1 で正式に導入されたmax_connectionsというキー名に置き換えています。 poolという用語の説明やサンプル設定が、max_connectionsの説明・サンプル設定に更新されています。
- これまでデータベース接続数を表す設定としてガイド中で使われていた
想定されるガイド内の記述例(イメージ)
※PR本文からは正確な文面までは分かりませんが、Rails のガイド構成からすると、以下のような記述が pool → max_connections に書き換えられていると考えられます。
# 旧: Rails 8.0 までの例(ガイドに載っていた想定)
production:
adapter: postgresql
database: my_app_production
pool: 5
timeout: 5000
# 新: Rails 8.1 以降の例(このPRでガイドがこういった形に更新)
production:
adapter: postgresql
database: my_app_production
max_connections: 5
timeout: 5000Action Cable のガイド (action_cable_overview.md) についても、内部で利用するデータベース接続数に関連する設定例や説明部分が pool から max_connections に読み替えられています。
- 影響範囲・注意点
コード・挙動への影響
- この PR 自体はドキュメントのみの変更であり、Rails 本体の挙動には一切影響しません。
- ただし、Rails 8.1 を使う開発者がガイドを見たときに、正しい設定キー名(
max_connections)が参照できるようになるという意味で、実務上の混乱を防ぐ効果があります。
マイグレーションの観点
- Rails 8.1 からは
poolがmax_connectionsにリネームされています。 - 既存アプリの
database.ymlを Rails 8.1 に合わせる場合は、ガイドに倣って以下のような変更が必要になる可能性があります(※実際の互換性・非推奨期間などは CHANGELOG / 該当PR を要確認)。diff- pool: 10 + max_connections: 10 - 新たに Rails 8.1 でアプリを作る場合は、最初から
max_connectionsを使うべきです。
- Rails 8.1 からは
ドキュメントとの整合性
- ガイドが
poolではなくmax_connectionsを前提にすることで、- Active Record の新しい設定オプション群(
keepalive,max_age,min_connections,max_connections)との整合性が取れる - 「コネクションプールのサイズ」という概念が、より直感的な名前(最大接続数)で説明される
といったメリットがあります。
- Active Record の新しい設定オプション群(
- ガイドが
- 参考情報 (あれば)
- Rails 8.1 Active Record CHANGELOG(PR本文で言及)
keepalive,max_age,min_connectionsの追加pool→max_connectionsへのリネーム
- 関連 PR
- #57736: 新オプションに関する追加ドキュメント
- #54175:
max_connectionsオプションを最初に導入した PR
Rails 8.1 以降で DB コネクション関連を調整する場合は、max_connections / min_connections / keepalive / max_age をセットで確認しておくとよいです。
#58665 [8-1-stable] Fixed guard around AS::TC.run_order
マージ日: 2026/9/4 | 作成者: @fesiqueira
- 概要 (1-2文で)
Rails 8.1系(8-1-stableブランチ)で、ActiveSupport::TestCase.run_orderを扱うガード条件(安全な存在チェック)が誤っていたのを 1行だけ修正したバックポートPRです。Minitest 6 対応のフォローアップで、テスト実行順序の取得まわりで発生しうる不具合を防ぐ目的の修正です。
- 変更内容の詳細
※このPRは activesupport/lib/active_support/test_case.rb の 1 行差し替えのみです。
元PR (#56879) および Minitest 6 対応 (#56434) のフォローアップで、「AS::TC.run_order を参照する際の条件分岐(ガード)」が不適切だったのを修正しています。
典型的には、次のような処理が関係します(擬似コード):
module ActiveSupport
class TestCase < Minitest::Test
# テストの実行順序を Rails 側から制御/参照するコード
def self.test_order
# ここで Minitest::Test.run_order や ActiveSupport::TestCase.run_order を見たり、
# Minitest のバージョンによって存在しない場合があるメソッド/定数を
# 条件付きで呼び分ける
end
end
endMinitest 6 では API や挙動が微妙に変わっており、例えば:
run_orderが定義されていない状況で無条件に呼び出すとエラーになるnilや想定外の値を返す可能性を考慮していない条件式だと、テスト順序の設定が正しく反映されない
といった問題が起こりえます。
このPRでは、その「run_order を利用する前のチェック条件」だけを 1行修正し、
run_orderが存在しない/使えないケースでも例外にならない- Rails 側で意図した実行順序(例:
:random,:sortedなど)が正しく反映される
ようにしています。
実際の差分イメージ(あくまで例示):
- if AS::TC.respond_to?(:run_order)
+ if AS::TC.respond_to?(:run_order) && AS::TC.run_order
# run_order が有効なときだけ特定の処理を行う
endあるいは、Minitest 5 / 6 でのインターフェイス差異を正しく判定するようなガードに修正されています。
- 影響範囲・注意点
影響範囲
- Rails 8.1 系で Minitest を使っているプロジェクトの「テスト実行順序(test order)」周りに影響します。
- 特に Minitest 6 系と組み合わせたときに、
- テストスイートの実行開始時に
NoMethodErrorやundefined method 'run_order'のようなエラーが出ていたケース config.active_support.test_order、ENV['MT_RUN_ORDER']などと組み合わせた挙動が不安定だったケース
が安定することが期待されます。
- テストスイートの実行開始時に
互換性 / 破壊的変更について
- 1 行のガード修正のみであり、
run_orderが正しく提供されている環境ではほぼ影響はありません。 - 既存の Minitest 5 系を利用している Rails 8.1 プロジェクトでも、仕様上の挙動(ランダム実行やソート順など)は基本的に変わらない想定です。
- バグ回避のための条件式強化が中心で、「仕様変更」というより「正しい条件に合わせた」修正に近いです。
- 1 行のガード修正のみであり、
注意点
- 8.1 系を Minitest 6 と組み合わせている場合は、この修正が含まれるパッチレベルにアップデートしておくと安全です。
- Rails 本体側のテスト(特に
ActiveSupport::TestCaseを継承した独自テストクラス)で、run_orderを直接参照しているような高度なカスタマイズを行っている場合は、一度テストを走らせて想定通りの順序になっているか確認してください。
- 参考情報 (あれば)
- 元PR(本流ブランチ向け):
- #56879 — Minitest 6 対応の follow-up として
AS::TC.run_order周りを修正した PR
- #56879 — Minitest 6 対応の follow-up として
- 元の Minitest 6 対応 PR:
- #56434 — Rails が Minitest 6 で動作するようにするための対応一式
- このPR:
- #58665 は上記 #56879 のバックポート (
git cherry-pick -x e2feab4...) を8-1-stableに適用したものです。
- #58665 は上記 #56879 のバックポート (
#58666 Use equality comparison when fetching information about a single table
マージ日: 2026/9/4 | 作成者: @ilianah
- 概要 (1-2文で)
MySQL 用スキーマリーダが「単一テーブル」に対してもIN (...)を使ってinformation_schemaを問い合わせていた挙動を、1件の場合は=比較に戻すPRです。Vitess 環境でremove_indexなどが正しく動かずエラーになっていた問題を、古い Vitess バージョンも含めてワークアラウンドする目的があります。
- 変更内容の詳細
背景
- #58421 以降、MySQL のスキーマリーダは「テーブル情報を取るメソッド」が常に「テーブル名のリスト」を前提とする実装になり、SQL も
table_name IN (...)形式で発行されるようになりました。 - Vitess 環境では 1つの「論理DB」に複数 keyspace がぶら下がる構成があり、
information_schemaが keyspace ごとに分かれているため「どの keyspace の information_schema に投げるべきか」をルーターが解決できません。 - 特に
IN句でinformation_schemaを叩くクエリは keyspace 解決に失敗し、「ルーティングできない」→「デフォルト keyspace に投げる」挙動になり、デフォルト keyspace 外のテーブルについては空結果が返ってしまいます。 - Rails の
remove_indexは内部でfetch_indexes(table_name)を呼び出し、これがINクエリで index 情報を取りに行くため、Vitess 上では「インデックスが存在しても見つからない」という状態になり、rubyというエラーを引き起こしていました。No indexes found on #{table_name} with the options provided.
Vitess 本体側でも修正 (vitessio/vitess#20973) が入りますが、v23 以降にしか入らないため、それより古いバージョンを使うユーザには影響が残ります。
このPRでの修正方針
- 「単一テーブル の情報を取得する場合」は
INではなく=(等価比較)に戻す。 - 「複数テーブル」を対象とするクエリは従来どおり
IN (...)のまま据え置き(Vitess では依然として問題になるが、少なくともremove_indexなど単一テーブル向けの主要ユースケースを救済できる)。
コード上の変更イメージ
※実際の行差分は抜粋レベルですが、概念的には次のような変化です。
変更前(単一テーブルでも IN)
def quoted_scope(table_names)
# table_names は単一/複数問わず配列前提
"AND table_name IN (#{table_names.map { |t| quote(t) }.join(', ')})"
end変更後(1件なら =、複数なら IN)
def quoted_scope(table_names)
names = Array(table_names)
if names.size == 1
# 単一テーブル → 等価比較
"AND table_name = #{quote(names.first)}"
else
# 複数テーブル → 従来通り IN
"AND table_name IN (#{names.map { |t| quote(t) }.join(', ')})"
end
endこれに付随して、abstract_mysql_adapter 側のヘルパーメソッドも「常に list 前提」だったものを、「1件の場合の処理分岐」を持つ形に調整しています(+6/-10 程度の軽微なリファクタリング)。
- 影響範囲・注意点
影響範囲
- 対象:
- MySQL 系アダプタ(
mysql2など)を使った Active Record - スキーマ情報を
information_schemaから取得する処理全般remove_index/index_exists?/ マイグレーション実行時の index 情報参照 など
- MySQL 系アダプタ(
- 特に恩恵を受けるケース:
- Vitess 上で Rails アプリを動かしており、かつ
- 複数 keyspace を使っている or テーブルがデフォルト keyspace 以外にある
この PR 適用後は、単一テーブルに対して index 情報を取るクエリが等価比較に戻るため、Vitess の keyspace ルーティングが正しく働き、remove_index などが期待通り動作するようになります。
注意点
- 複数テーブルを対象とするクエリは依然として Vitess では問題になりうる
INを使うクエリは Vitess が keyspace を解決できず、デフォルト keyspace で実行される挙動は変わりません。- ただし、この PR の目的は「単一テーブル向けの主要ユースケース(
remove_indexなど)を壊さないこと」であり、複数テーブル同時問い合わせの挙動は元々も保証されていなかったため据え置きになっています。
- Vitess v23 以降を使う場合
- Vitess 側に
IN句サポートの修正が入るため、理論上は Rails 側のワークアラウンドがなくても問題は軽減されますが、この PR による挙動変更は後方互換的であり、v23 を使っても悪影響はほぼありません(単一テーブルクエリが=になるだけ)。
- Vitess 側に
- MySQL 単体利用・非 Vitess 環境
- ローカル MySQL や一般的なクラウド MySQL では、
IN ('users')と= 'users'は実質同じ意味なので、パフォーマンスや動作への影響はほぼありません。
- ローカル MySQL や一般的なクラウド MySQL では、
- 参考情報 (あれば)
- 元PR:
- rails/rails#58666: Use equality comparison when fetching information about a single table
- 関連PR:
- rails/rails#58421: MySQL schema readers answer for a list of tables(今回の挙動変更の発端となったPR)
- vitessio/vitess#20973: Vitess 側で
information_schema+IN句に関するルーティング問題を修正するPR
- 関連するRails機能:
- ActiveRecord::ConnectionAdapters::Mysql::SchemaStatements
remove_index,index_exists?, マイグレーションでの index 操作全般
#58623 Check PATCH and QUERY in routing assertions with method: :all
マージ日: 2026/9/3 | 作成者: @carlosdanielpohlod
- 概要 (1-2文で)
Rails のルーティングテスト用アサーションassert_recognizes/assert_routingにおいて、method: :all指定時にPATCHと新たに追加されたQUERYメソッドも検証対象とするように修正した PR です。これにより、「全ての HTTP メソッドを受け付けるはず」という前提で書かれたテストが、実際にはPATCHやQUERYを受け付けていないルートを正しく検出できるようになります。
- 変更内容の詳細
2-1. 背景と問題点
Rails のテスト用アサーション:
assert_recognizes(expected_options, path, method: ...)assert_routing(path, expected_options, method: ...)
これらは
method: :allを指定すると、「そのパスが全 HTTP メソッドでルーティング解決される」ことを検証する意図のオプションになっている。しかし、実装上は
GET,POST,PUT,DELETEの 4 つしかチェックしていなかった。そのため、以下のようなルート定義をしていても:
ruby# PATCH と QUERY を受け付けない match "/posts/:id", to: "posts#update", via: [:get, :post, :put, :delete]テスト側で:
rubyassert_routing "/posts/1", { controller: "posts", action: "update", id: "1" }, method: :allと書くと、「全ての HTTP メソッドにマッチする」と主張しているにもかかわらず、実際には
PATCHやQUERYは通っていない状態でテストが通ってしまう、という不整合があった。このリスト (
GET,POST,PUT,DELETE) は Rails 4 以前に由来しており、- 当時は
PATCHが更新のデフォルトメソッドになる前だった QUERYは今回の開発サイクルで追加された HTTP メソッド という歴史的経緯がある。
- 当時は
2-2. 今回の修正内容
(1) method: :all がチェックするメソッドを拡張
action_dispatch/testing/assertions/routing.rb 内の実装を変更し、method: :all のときにチェックされる HTTP メソッドに :patch と :query を追加しました。
※ 実際のコード断片(イメージ):
# 変更前(イメージ)
HTTP_METHODS_FOR_ALL = [:get, :post, :put, :delete].freeze
# 変更後(イメージ)
HTTP_METHODS_FOR_ALL = [:get, :post, :put, :delete, :patch, :query].freezeこれにより、以下のようなテストが:
assert_routing "/articles/1",
{ controller: "articles", action: "show", id: "1" },
method: :all内部的には少なくとも:
[:get, :post, :put, :delete, :patch, :query].each do |http_method|
assert_routing "/articles/1",
{ controller: "articles", action: "show", id: "1" },
method: http_method
endに相当するチェックを行うようになります。
(2) OPTIONS, HEAD, TRACE を含めなかった理由の明示
PR 内で、以下のメソッドは method: :all の検証対象から外した理由が説明されています:
HEAD:- Rails では一般に
GETルートから自動的に提供されるため、個別チェックは不要と見なした。
- Rails では一般に
OPTIONS:- 多くの場合 Rack ミドルウェア(CORS ハンドラなど)が応答し、アプリ側のルーティングで扱わないケースが多い。
TRACE:- 実務ではほとんどルーティングしないため対象外とした。
ただし、必要であればこれらを含める変更も検討可能である、というスタンスが示されています。
(3) ドキュメントとテストの追加
method: :allオプションに関する RDoc が存在しなかったため、ドキュメントを追加。actionpack/test/controller/resources_test.rbに、PATCH/QUERYを含めた形でのmethod: :allの挙動を検証するテストを追加。- 既存のテスト名
test_assert_routing_fails_when_not_all_http_methods_are_recognizedの挙動が、PATCH/QUERYも含めて「全ての対象メソッド」をチェックするように強化されている。
- 既存のテスト名
(4) CHANGELOG の更新
actionpack/CHANGELOG.mdに、本変更によって挙動が厳密になり、既存のテストが失敗する可能性があることが明記されています。
- 影響範囲・注意点
3-1. 既存テストへの影響
最も重要な影響点は、「今まで通っていたテストが落ちる可能性がある」ことです。
条件としては:
- テストコード:
assert_recognizesまたはassert_routingでmethod: :allを指定している
- ルーティング定義:
- 対象のルートが
PATCHまたはQUERYを受け付けていない (via: [:get, :post, :put, :delete]のような定義 など)
- 対象のルートが
この場合、今回の修正後は PATCH / QUERY でもそのパスが解決されることを期待されるため、テストが失敗します。
これは PR の意図通りのブレイキング変更であり、「本当にすべての HTTP メソッドを受け付けるべきルートか?」を見直すきっかけになります。
3-2. 対応パターン
テストが落ちた場合の代表的な対応は次の 3 つです。
ルート定義を更新して本当に “all” を受け付けるようにする
ruby# 以前 match "/posts/:id", to: "posts#update", via: [:get, :post, :put, :delete] # 修正後: PATCH / QUERY を含める match "/posts/:id", to: "posts#update", via: :all # または match "/posts/:id", to: "posts#update", via: [:get, :post, :put, :delete, :patch, :query]そして
method: :allのテストを維持する。テストが “全メソッド” である必要はなかった場合
もともと「特定のメソッドだけ通ればよい」ルートであるのに、便宜上
method: :allを使っていた場合は、明示的にメソッドを指定する:rubyassert_routing "/posts/1", { controller: "posts", action: "update", id: "1" }, method: :patch # あるいは :get, :post など必要なものだけもしくは、
[:get, :post]のように自前でループを書くなど。QUERY をサポートしない方針の場合の検討
- Rails 側に QUERY が追加されたこと自体を念頭に置き、自アプリで QUERY をサポートするかどうか設計から見直す必要がある場合があります。
- 現時点で QUERY を完全に使わないポリシーなら、
method: :allは使わず、必要なメソッドに限定したテスト戦略を取る方が明確です。
3-3. 新規開発時のベストプラクティスへの示唆
- 「
via: :allでルートを定義し、テスト側でもmethod: :allを使う」ことがより正しく噛み合うようになりました。 PATCHが更新系のデフォルトであること、QUERYメソッドが Rails に追加されていることを前提に、本当に全メソッドを許可したいケース以外ではvia:/method:の指定を明示的かつ限定的にするのが安全です。
- 参考情報 (あれば)
- 対象 PR: https://github.com/rails/rails/pull/58623
- 関連コード:
actionpack/lib/action_dispatch/testing/assertions/routing.rbactionpack/test/controller/resources_test.rbactionpack/CHANGELOG.md
assert_routing/assert_recognizesドキュメント (Rails Guides / RDoc) も今回の変更でmethod: :allが明文化されているため、最新版の公式ドキュメント参照が推奨されます。
#58651 SchemaContext#attributes: preload schemas to prevent deadlock
マージ日: 2026/9/3 | 作成者: @kyrofa
- 概要 (1-2文で)
SchemaContext#attributesでRactor.store_if_absentによるロックを使ってAttributesを初期化していた結果、スキーマ読み込み時に再帰的に同じロックを取りに行ってデッドロックが発生しうる問題を解消した PR です。
ロックを完全に取り除き、デッドロックを防ぎつつ動作をテストで担保しています。
- 変更内容の詳細
問題の構造
SchemaContext#attributes は「そのクラスの Attributes オブジェクト」を遅延初期化する箇所です。元の実装イメージは概ね次のような流れになっていました:
SchemaContext#attributesが呼ばれるRactor.store_if_absentを使って、Attributesをスレッド/Ractor 安全にキャッシュ- その際
Attributes.newが呼ばれる Attributes.new内でAttributeRegistration#apply_pending_attribute_modificationsを実行- ここでスーパークラス側の attribute 修飾(例:
EncryptableRecordの暗号化関連)を適用するため、クラス階層を遡りながらcolumns_hashを呼ぶ columns_hashがスキーマのロードをトリガし、load_schema→ 再度SchemaContext#attributesを呼ぶ- 2 に戻り、同じ
Ractor.store_if_absentでロックを取りに行く → 再入ロック不可なためデッドロック
つまり「Attributes 初期化 → attribute 修飾の適用 → スキーマロード → 再度 Attributes 初期化」のループの途中で、同じロックを再取得しようとして固まる、という構造的な問題です。
この PR の修正
修正の核心は「SchemaContext#attributes 内で使っていた Ractor.store_if_absent によるロックを削除する」ことです。
変更点は以下の通りです:
activerecord/lib/active_record/model_schema/schema_context.rbSchemaContext#attributesからRactor.store_if_absent呼び出しを削除- 代わりに、ロックなしで
Attributesを生成・利用する形に変更 - 行レベルでは +1/-3 なので、ほぼ「ロックまわりの削除」のみ
擬似コードでの Before / After は以下のようなイメージです(実際のコードは多少異なりますが概念的にはこうです):
# Before (イメージ)
def attributes
Ractor.store_if_absent(@attributes_key) do
Attributes.new(self)
end
end
# After (イメージ)
def attributes
# ロックなしに Attributes を返す/生成する
@attributes ||= Attributes.new(self)
end実際には Ractor 対応用のストアを使っているためこのままではない可能性がありますが、ポイントは「Ractor.store_if_absent ベースのロックメカニズムをやめた」ことです。
テストの追加
activerecord/test/cases/attributes_test.rbに 17 行追加- 再帰的に
SchemaContext#attributes→ スキーマロード →SchemaContext#attributesが呼ばれるケースでもハングしないこと - EncryptableRecord やスーパークラス側の attribute 修飾を含むような実運用に近いシナリオをカバーしていると考えられます
- デッドロックが再発しないことを保証する回帰テストとして機能
- 再帰的に
- 影響範囲・注意点
影響しうる領域
- Active Record のスキーマロード周り (
load_schema,columns_hash) - モデルの attributes 定義とその修飾(
AttributeRegistration, 暗号化属性を提供するEncryptableRecordなど) - Ractor / 並列実行環境での Attribute キャッシュ
これらを使うすべてのアプリケーションが潜在的な影響範囲です。
デッドロック解消の代償としての懸念点
- ロック削除により、「同時に複数の Ractor/スレッドが同じクラスの Attributes を初期化しに行く」ケースで
- 同じ
Attributesに対して重複初期化が発生する可能性 - ただし Active Record の attribute 定義は基本的に idempotent かつスキーマ/メタデータであり、多少の重複計算は許容される設計になっていることが多い
- 同じ
- 本 PR は「安全性よりも、致命的なデッドロックを確実に回避する」ことを優先した修正といえる
実運用での注意点
- Rails アプリで以下のような条件がそろうときに、この修正の恩恵を強く受けます:
- モデルに暗号化属性などの attribute 修飾(
EncryptableRecordやそれに準ずるモジュール)を利用している - 並列実行/Ractor を利用している、もしくは並列なスキーマロードが起こりうる
- 起動時や初回アクセス時にランダムにハングするような現象があった場合、このバグに該当していた可能性がある
- モデルに暗号化属性などの attribute 修飾(
- PR #58650 に紐づくバグであり、その Issue に類似する症状(スキーマロード中に固まる)が起きていた場合、Rails バージョンをこの修正を含むものまで上げる価値があります。
- 参考情報 (あれば)
- この PR が修正する Issue: #58650
(SchemaContext#attributes/Ractor.store_if_absentに起因するデッドロック報告) - 関連クラス・メソッド:
ActiveRecord::ModelSchema::SchemaContext#attributesActiveRecord::AttributeRegistration#apply_pending_attribute_modificationsActiveRecord::Encryption::EncryptableRecord(暗号化属性を提供するモジュール)ActiveRecord::Base#columns_hash,ActiveRecord::ModelSchema#load_schema
#58656 Prevent freezing Action Dispatch default headers config itself
マージ日: 2026/9/3 | 作成者: @Edouard-chin
- 概要 (1-2文で)
Rails のActionDispatchが持つ「デフォルトレスポンスヘッダ設定」を Ractor 安全性のために freeze する際、Rails.application.config.action_dispatch.default_headersそのものが凍結されてしまう問題を修正し、設定オブジェクト自体は凍結されないようにした PR です。これにより、古くから存在する「load hook 内で config を破壊的変更する」ようなコードがクラッシュせず動作(※実質的な効果は元々ほぼなかった)を続けられるようになります。
- 変更内容の詳細
背景
以前のコミット(
56d19e5...)で、Ractor 対応の一環として「Action Dispatch のデフォルトヘッダ」を freeze するようになった。その際に「ヘッダの中身」だけでなく、「
Rails.application.config.action_dispatch.default_headers(設定オブジェクト)そのもの」も結果的に freeze されてしまっていた。そうすると、以下のように load hook の中で
default_headersを破壊的変更しようとするコードがクラッシュするケースが出る:rubyinitializer :mutation do ActiveSupport.on_load(:action_controller) do Rails.application.config.action_dispatch.default_headers.clear end endActionController::Baseが先にロードされるとActionDispatch::Responseもロードされ、そのタイミングでdefault_headersが freeze される。- その後に
ActionController::APIをロードすると load hook がもう一度呼ばれ、2 回目のclearが「frozen なオブジェクトへの変更」となり例外が発生する。
このようなコードは設計的には「やるべきではない」ものの、10 年ほど問題なく動いていたため、いきなり壊すのは避けたいという判断になっています。
具体的な修正
変更ファイルは 2 つだけです。
actionpack/lib/action_dispatch/railtie.rb(+1 / -1)ここで行っていることは「freeze 対象を変える」ことです。
おおまかには次のようなイメージになります(実際のコードはこれと等価なことをより Rails 的な書き方でやっていると考えてください):
ruby# 以前のイメージ(問題があった実装) config.action_dispatch.default_headers.freeze # 今回の修正イメージ headers = config.action_dispatch.default_headers.dup # 設定値をコピー headers.freeze # コピー側を freeze ActionDispatch::Response.default_headers = headers # Response 側に渡す重要な点は:
Rails.application.config.action_dispatch.default_headersは 凍結しない。ActionDispatch::Responseが使う実体としては「コピーされたヘッダ Hash(もしくは類似オブジェクト)」を freeze して利用する。- したがって、アプリ・ライブラリ側から
Rails.application.config.action_dispatch.default_headersをその後に変更しても、実際のレスポンスヘッダには反映されない(これは元々もほぼ同様の挙動)。
railties/test/application/configuration_test.rb(+11 / 0)テストが追加されています。主に次のようなことを確認しています:
ActionDispatch読み込み後でも、Rails.application.config.action_dispatch.default_headersが freeze されていないこと。- つまり、
config.action_dispatch.default_headers.clearや他の破壊的操作を行っても「FrozenError」等を投げずに動くこと。
- 影響範囲・注意点
影響範囲
影響を受けるのは主に以下のようなコードを持つアプリ/ライブラリです:
ActiveSupport.on_load(:action_controller)や他の load hook 内でRails.application.config.action_dispatch.default_headersを破壊的に変更しているコード。- 特に「
ActionController::BaseとActionController::API両方がロードされる環境」で、同じ load hook が複数回呼ばれる場合。
この PR 適用後:
- これらのコードは 例外なく動作を継続 します。
- ただし、
ActionDispatch::Responseが実際に使うデフォルトヘッダは freeze 済みのコピーなので、- 「Response 実行中のヘッダ挙動」がこれらの後付け変更で書き換わることはなく、
- 設定オブジェクトを書き換えても「見かけ上書き換えられたように見えるが、実際のレスポンスに影響しない」状態になります(これは仕様として以前からほぼ同じで、新たな挙動ではない)。
注意点・ベストプラクティス
PR の説明にもある通り、load hook 内で
Rails.application.configを破壊的変更するのは推奨されません。- 設定は通常、環境ごとの config ファイル(
config/environments/*.rbなど)で「起動時に一度だけ」行うのが筋です。 - 特に Ractor 対応以降は、マルチスレッド/マルチ Ractor 環境でも予測可能な挙動を維持するため、設定値は immutability を前提として扱う傾向が強まっています。
- 設定は通常、環境ごとの config ファイル(
もし「ActionDispatch::Response が使うデフォルトヘッダをアプリケーション側から確実に制御したい」場合は、
- 起動時に
config.action_dispatch.default_headers = { ... }のように設定し、 - それ以降は変更しない(変更しても意味がない)という前提で設計した方が安全です。
- 起動時に
- 参考情報 (あれば)
元になった Ractor 対応のコミット:
56d19e536b409b17d8f89545ff5281c4e1c3eaf6- ActionDispatch のデフォルトヘッダを freeze して Ractor セーフにする修正。
今回の PR での方針:
- Ractor 安全性(レスポンスヘッダの immutability)は維持。
- 10 年以上動いていた既存コードとの後方互換性も尊重し、
config.action_dispatch.default_headers自体は凍結しないようにする。 - 将来的には、こうした「load hook 中で config を mutate する」パターンはライブラリ側で修正していくべき、という含みを持った暫定的な互換性維持策と言えます。
#58635 Re-enable PostgreSQL triggers when disable_referential_integrity raises
マージ日: 2026/9/3 | 作成者: @lukasedw
- 概要 (1-2文で)
PostgreSQL 18.4 未満の環境でdisable_referential_integrity実行中に例外が発生した場合でも、必ずトリガーが再有効化されるように修正した PR です。これにより、テストやクリーナー処理の失敗をきっかけに、DB 全体の外部キー制約が恒久的に無効化されてしまうバグを防ぎます。
- 変更内容の詳細
問題の背景
PostgreSQL 18.4 より前のバージョンには、外部キーを一時的に「NOT ENFORCED」にする機能がないため、Rails の
disable_referential_integrityは内部的にsqlALTER TABLE ... DISABLE TRIGGER ALL; -- ブロック実行 ALTER TABLE ... ENABLE TRIGGER ALL;というカタログ(テーブル定義)レベルの変更を行います。
元の実装では
ENABLE TRIGGER ALLがensureで保護されていなかったため、ブロック内で例外が発生すると「トリガーを有効に戻す処理」が実行されず、- 外部キー用トリガー(FK トリガー)が全て無効のまま残る
- これはセッションではなくカタログ状態なので、同一 DB を使う全てのコネクション・プロセスに影響が波及
する、かなりクリティカルな状態になっていました。
#50196 で fixtures のケースについては、「トランザクション内で
DISABLE TRIGGERを行う」ことで、例外時にROLLBACKによって元に戻るようになりましたが、- トランザクション外で
disable_referential_integrityを呼ぶケース(truncate_tables, DatabaseCleaner の truncation strategy など)は依然として危険なままでした。
- トランザクション外で
実際の問題例:
parallel_tests実行中、あるワーカーでtruncateによるデッドロックが発生して例外で落ちる- その時
ENABLE TRIGGER ALLが実行されず、以降その DB では:ON DELETE CASCADEが動かないRESTRICTがエラーを出さないSET NULLが実行されない
- 後続テストが外部キー前提で書かれていると、ランダムにテストが落ちる(後の別 truncate がたまたま成功して元に戻ることもあるため、非常に「フレーク」っぽく見える)
この PR の修正内容
1) ENABLE TRIGGER ALL を ensure ブロックに移動
activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rbのdisable_referential_integrity(PostgreSQL 用実装)で、トリガー再有効化処理をensureに移動しました。
概念的には以下のようなイメージです(簡略化した Ruby 擬似コード):
def disable_referential_integrity
disable_triggers_for_all_tables
begin
yield
ensure
# ブロック内で例外が起きても必ず実行される
begin
enable_triggers_for_all_tables
rescue ActiveRecord::ActiveRecordError
# 既存の挙動を維持:
# 外側でトランザクションロールバックが行われるケースなどでは
# ここでの失敗は握りつぶす
end
end
end- 重要な点:
- 既存の
rescue ActiveRecord::ActiveRecordErrorは維持されています。 - これは「外側にトランザクションがあり、内側で
DISABLEした後、外側のロールバックで状態が戻る」ケースで、
トリガー再有効化のENABLEが失敗しても、元のトランザクションロールバックで状態が正しく戻る、という挙動を壊さないためです。
→ これについてはtest_does_not_break_transactionsでカバーされています。
- 既存の
2) PostgreSQL 18.4+ (NOT ENFORCED パス) は変更なし
- #57378 で導入された
NOT ENFORCEDを使うコードパス(PostgreSQL 18.4+)は、「単一トランザクション内で ON/OFF を切り替え、最後にトランザクションでまとめて戻す」実装になっており、そもそも今回の問題が発生しません。 - そのため、18.4+ のコードパスには変更を加えていません。
3) 回帰テストの追加
activerecord/test/cases/adapters/postgresql/referential_integrity_test.rbに新しいテストを追加:ブロック内で例外を発生させる:
rubyconnection.disable_referential_integrity do raise "boom" endrescue した後、子テーブルに「無効化されたトリガー」が残っていないことを確認するテストです。
pg_triggerテーブルを参照し、tgenabled = 'D'(Disabled)なトリガーが残っていないかをチェックする形。
テストは以下の条件で skip されます:
- PostgreSQL 18.4 以上の場合
→ 実装パスが異なり、このバグ自体が起こらないため。 - テスト実行ユーザが superuser でない場合
→DISABLE TRIGGER ALLは superuser でないと実行できず、その場合 no-op になってしまうため。
no-op だとテストが「たまたま」通ってしまい、回帰検知にならないため。
- PostgreSQL 18.4 以上の場合
4) CHANGELOG の更新
activerecord/CHANGELOG.mdに、本修正に関する記述が追加されています(バグ修正であること、PostgreSQL の referential integrity に関する変更であることなど)。
- 影響範囲・注意点
影響範囲
主に影響を受けるのは、PostgreSQL 18.4 未満かつ Rails の
disable_referential_integrityを利用しているケースです。truncate_tablesを使うテストヘルパ- DatabaseCleaner の
truncationstrategy - その他、手動で
disable_referential_integrityを呼び出しているコード
この修正により、これらの処理中に例外が発生しても、必ずトリガーが元に戻されるため、
- 外部キー制約が意図せず無効化されたまま放置される
- それによって、後続テストや本番に近い検証環境で不整合なデータが生まれる
といった問題が防止されます。
互換性・挙動の変化
- 期待される(望ましい)意味での挙動の変化:
- 以前は
disable_referential_integrityブロック内で例外が出ると「トリガーが無効化されたまま」だったが、
今後は「例外が出てもトリガーは必ず再有効化される」ようになります。
- 以前は
- 既存のトランザクションとの相互作用は維持:
- 外側でトランザクションを張った状態で
disable_referential_integrityを使うケースでは、従来通り「ロールバックで元に戻る」パターンもサポートされ、テストで確認済みです。
- 外側でトランザクションを張った状態で
注意点
PostgreSQL 側の権限:
- このコードパスは
DISABLE TRIGGER ALL/ENABLE TRIGGER ALLを実際に発行するので、本番環境で使う場合は DB ユーザ権限(superuser か否か)に注意が必要です。 - 非 superuser では
DISABLE TRIGGER ALLは no-op になるので、この修正も実質的には影響しません。
- このコードパスは
PostgreSQL 18.4+ を使っている場合:
NOT ENFORCEDベースの別実装が使われるため、この PR の修正は直接の影響はありません。- ただし、将来バージョンアップや DB バージョン切り替え時に、「どのパスが使われているか」を意識しておくとデバッグしやすくなります。
- 参考情報 (あれば)
- 元 PR: #58635
- 関連 PR:
- #50196 — fixtures 利用時に
disable_referential_integrityをトランザクション内で実行するようにした対応 - #57378 — PostgreSQL 18.4+ の
NOT ENFORCEDを使った referential integrity の新実装
- #50196 — fixtures 利用時に
- 関連する PostgreSQL の仕様:
ALTER TABLE ... DISABLE TRIGGER ALL/ENABLE TRIGGER ALLはセッション状態ではなくカタログ状態を変更する点に注意(トランザクションに包まないと永続的な変更になる)。
#58638 Add PostgreSQL adapter support for configuring error_verbosity
マージ日: 2026/9/3 | 作成者: @beauraF
- 概要 (1-2文で)
PostgreSQL アダプタに、接続設定 (database.yml) からerror_verbosityを指定して libpq のエラーメッセージ詳細度を制御できる機能が追加されました。これにより、ユニーク制約違反などのエラーに含まれる PII(メールアドレス等)がログ経由で漏洩することを抑制しやすくなります。
- 変更内容の詳細
2-1. 何ができるようになったか
config/database.yml の各環境ごとの設定に、error_verbosity キーを追加して、PostgreSQL 接続のエラーメッセージ詳細度を指定できます。指定された値は、接続確立後に PostgreSQLAdapter#configure_connection 内で一度だけ PG::Connection#set_error_verbosity に渡されます。
サンプル:
production:
adapter: postgresql
database: my_app_production
username: my_user
password: <%= ENV["DB_PASSWORD"] %>
error_verbosity: <%= PG::PQERRORS_TERSE %>Rails 側は、この error_verbosity の値をそのまま pg gem の PG::Connection#set_error_verbosity に渡します。
そのため、利用可能な値は pg / libpq がサポートしている定数になります:
PG::PQERRORS_TERSE
→ もっとも簡潔なメッセージ。DETAIL や HINT などが削られ、PII が含まれる可能性を減らしやすい。PG::PQERRORS_DEFAULT
→ PostgreSQL のデフォルト。従来と同等の挙動。PG::PQERRORS_VERBOSE
→ より詳細な情報(内部情報やバックトレースに近いもの)を含む。PG::PQERRORS_SQLSTATE
→ SQLSTATE コード中心の出力。
2-2. 実装のポイント
変更点は主に以下です。
activerecord/lib/active_record/connection_adapters/postgresql_adapter.rbconfigure_connectionメソッド内で、config[:error_verbosity]が存在する場合のみ@connection.set_error_verbosity(config[:error_verbosity])を実行するロジックが追加。- 既存の
encoding設定と同じパターンで、「設定されていれば一度だけ適用」という方針。
activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rberror_verbosityが設定されたときにPG::Connection#set_error_verbosityが呼び出されることを検証するテストが追加。
activerecord/CHANGELOG.md- Active Record への新機能として、「PostgreSQL アダプタで error_verbosity を設定できる」ことが追記。
コードイメージ(Rails 内部の処理イメージ・擬似コード):
def configure_connection
super
if config[:error_verbosity]
@connection.set_error_verbosity(config[:error_verbosity])
end
end- 影響範囲・注意点
デフォルト動作は変わらない
error_verbosityを指定しなければ、これまで通りpg/ PostgreSQL のデフォルト設定(PQERRORS_DEFAULT)が利用されます。- 既存アプリは、この設定を追加しない限り挙動は変わりません。
PII流出対策としての有効性
- ユニーク制約違反、外部キー制約違反、デッドロックなどのエラーの
DETAILフィールドには、実際に違反した値(例: メールアドレス、電話番号)が含まれます。 ruby-pgはこのテキストをそのまま例外メッセージに含めるため、ログやエラートラッカー(Sentry など)に PII が載りがちでした。error_verbosityをPG::PQERRORS_TERSE等に下げることで、この詳細テキストが抑制されるため、ログの情報量とプライバシー保護のバランスを取りやすくなります。
- ユニーク制約違反、外部キー制約違反、デッドロックなどのエラーの
設定値は Ruby コードとして評価されることに注意
- ドキュメント例でも
<%= PG::PQERRORS_TERSE %>のように ERB でPGの定数を参照しています。 - 誤って文字列
"PG::PQERRORS_TERSE"のように書くと正しく動作しないので、「Ruby の定数として評価させる」記法にする必要があります。
- ドキュメント例でも
再接続・コネクションプールとの関係
configure_connectionは接続確立時に呼ばれるので、プールされた各コネクションごとに一度だけ適用されます。- 接続切断→再接続が起きても、そのタイミングで再びこの設定が適用されます。
依存バージョン
PG::Connection#set_error_verbosityはpggem / libpq が対応している必要があります。- 通常のサポート対象バージョンの Rails + pg であれば問題ないはずですが、極端に古い
pgを使っている場合は事前に確認が必要です。
- 参考情報 (あれば)
libpq ドキュメント(PQsetErrorVerbosity)
https://www.postgresql.org/docs/current/libpq-control.html#LIBPQ-PQSETERRORVERBOSITYpggem ドキュメント(PG::Connection#set_error_verbosity)
https://deveiate.org/code/pg/PG/Connection.html#method-i-set_error_verbosity想定される設定例(本番環境で PII を抑えたい場合):
yamlproduction: adapter: postgresql database: my_app_production error_verbosity: <%= PG::PQERRORS_TERSE %>逆に、開発環境で詳細なデバッグ情報が欲しい場合:
yamldevelopment: adapter: postgresql database: my_app_development error_verbosity: <%= PG::PQERRORS_VERBOSE %>
#58647 Make the controller configs shareable in ractorize!
マージ日: 2026/9/2 | 作成者: @etiennebarrie
- 概要 (1-2文で)
Railsアプリをractorize!した際に、コントローラのコンフィグ(設定オブジェクト)が Ractor 間で安全に共有できるようにした PR です。アプリケーションブート後の「コントローラレベルの設定」は共有可能な形に固定し、各コントローラインスタンス側で変更可能なコピーを持つ設計に寄せています。
- 変更内容の詳細
※PR本文と差分構成からの推測を含みますが、Rails の既存構造と整合するように整理しています。
2-1. 背景: Ractor と controller configs
- Ruby 3 以降の Ractor では「shareable(共有可能)」なオブジェクトしか Ractor 間で共有できません。
- Rails には
ActionController::Baseに紐づくクラスレベルの設定(例:config.action_controller.perform_cachingのようなコントローラ設定オブジェクト)があり、その多くはActiveSupport::OrderedOptionsを使って保持されています。 - これら設定オブジェクトが「共有不可能(unshareable)」な状態だと、
Rails.application.ractorize!実行時にエラーや予期しない挙動の原因になります。
PRでは、この「コントローラの設定オブジェクト」を Ractor 的に安全な状態(shareable)にする対応が入っています。
2-2. Rails.application.ractorize! 内で controller configs を shareable に
railties/lib/rails/application.rb にある Rails.application.ractorize! 処理の中で、コントローラ設定を shareable にする処理が追加されています(+6行)。
典型的には、以下のような処理が入っているイメージです(あくまで擬似例):
def ractorize!
# 既存: 他の設定やロードのための処理...
# 追加: controller configs を shareable にする
# 例: ApplicationController やそのサブクラスの config を shareable! 呼び出し
ActionController::Base.descendants.each do |controller|
controller.config.shareable! if controller.respond_to?(:config) && controller.config.respond_to?(:shareable!)
end
# 既存: Ractor 用の最終セットアップ
end実際のコードは多少違う可能性がありますが、趣旨としては:
- Rails が
ractorize!するタイミングで - コントローラクラスが持つ設定(
configなど)を - Ractor 間で共有可能なオブジェクトに変換 or マークする
というものです。
PR本文にもあるように、「freeze + copy-on-write」方式は採用していません。代わりに「アプリ起動後はコントローラレベルの config は基本的に不変」という前提で shareable にしており、インスタンス側での変更は各インスタンスが持つ継承済みコピーで行うという設計に寄せています。
2-3. ActiveSupport::OrderedOptions に shareable 対応テストを追加
activesupport/test/ordered_options_test.rb に +10行のテストが追加されています。
意図としては:
- Rails の設定の多くが
ActiveSupport::OrderedOptionsを使っているため、
OrderedOptions 自体が「shareable 対応」できているかをテストした。 - 例として考えられるテストイメージ:
def test_ordered_options_can_be_shareable
opts = ActiveSupport::OrderedOptions.new
opts.foo = "bar"
opts.shareable! # もしくは、Ractor.shareable?(opts) が true になるように処理
assert Ractor.shareable?(opts)
end実際のメソッド名/API は PR によりますが、
- OrderedOptions インスタンスに対して
- Ractor 観点でも安全な状態にする API を呼び
- Ractor から
shareable?判定しても通ること
が確認されるテストが追加されています。
これにより、コントローラ設定に限らず、Rails が内部で使っている OrderedOptions ベースの設定が Ractor 化に耐えられることを保証します。
2-4. ractors_test にコントローラ設定の shareable テスト
railties/test/application/ractors_test.rb に +9行のテストが追加されています。
ここでは:
Rails.application.ractorize!実行後に- controller configs が実際に shareable になっているかを確認するテストが入っています。
想定されるテストイメージ:
test "controller configs are shareable after ractorize" do
app("production") # テスト用のアプリ起動
Rails.application.ractorize!
assert Ractor.shareable?(ActionController::Base.config)
# または特定のアプリケーションコントローラの config を確認
endこれにより、「ractorize! を呼んだあとで、まだ unshareable な controller config が残っていないか」を回帰テストとして担保しています。
- 影響範囲・注意点
3-1. コントローラレベルの設定を「起動後に書き換える」コードへの影響
PRの説明にも明記されている通り、
controller-level configs shouldn't be mutated once the app has booted.
Especially since controller instances have their own inherited copy of the config to mutate.
という設計思想に寄せられています。
つまり:
- アプリケーションがブートした後(本番環境でプロセスが立ち上がった後など)に
- コントローラクラスの config を直接変更するようなコード
は、Ractor 環境下では問題になる可能性があります(shareable オブジェクトを mutate しようとしてエラー、あるいは想定外の動作)。
例:
# 悪い例(本番起動後や ractorize! 後にやるべきでない)
PostsController.config.some_option = :new_valueこのような用途が必要な場合は:
- コントローラインスタンスに紐づく設定(
@config的なもの)があるならそちらを変更する - あるいは「実行時フラグ」「Feature flag」など別の仕組みを使う
といった方向が推奨されます。
3-2. Ractor を使わないアプリへの影響
- 変更箇所は主に
Rails.application.ractorize!経由のパス、および shareable テストの追加であり、 - 通常の(Ractor を使わない)Rails アプリでは基本的に挙動は変わらないはずです。
- ただし、将来的な Ractor 対応を見据え、**「ブート後にクラスレベルの設定をむやみに書き換えない」**という前提が一層強くなった、と理解しておくと良いです。
3-3. マルチプロセス / スレッド環境との関係
- 以前から推奨されていた「起動後にグローバルな設定オブジェクトをいじらない」方針と整合的です。
- Ractor 特有の shareable 制約が明文化された形になるため、スレッドセーフ設計との相性もよく、「設定は起動時確定、以後は読み取り専用」という構成に寄せると安全です。
- 参考情報 (あれば)
- Ruby Ractor 公式ドキュメント(shareable オブジェクトの概念など)
https://docs.ruby-lang.org/ja/latest/doc/spec=2fractor.html ActiveSupport::OrderedOptions
Rails の設定オブジェクト(config.x.xxxなど)でよく使われるクラスで、ハッシュライクに任意キーを格納できる。- 類似の PR / 方針
- Rails の Ractor 対応系 PR は、
ractorize!まわりで「設定を shareable にする」「Lazy ロードを排除する」といった変更が継続的に入っているため、同系列の1つと位置づけられます。
- Rails の Ractor 対応系 PR は、
このPRにより、「Ractor化した Rails アプリでコントローラ設定を安全に共有できる」基盤が整ったと整理できます。
#58653 Freeze Active Record specific on callbacks
マージ日: 2026/9/2 | 作成者: @Edouard-chin
- 概要 (1-2文で)
Active Record のトランザクション系コールバック(after_create_commitなどのonオプション付き)を Ractor でも共有可能(shareable)にするため、これらのコールバック定義を freeze する変更です。これにより、Ractor モード:raiseでアプリ起動時に落ちてしまう問題が解消されます。
- 変更内容の詳細
背景
class Post < ApplicationRecord
after_create_commit :foo
end上記のような「トランザクション完了時系」のコールバックは、内部的には Active Record のトランザクション機構 (ActiveRecord::Transactions) が on: :create などのオプション付きでコールバックを登録しています。
Ruby の Ractor では「共有不可能なオブジェクト」が Ractor 間にまたがると例外になります。Rails のブート時に eager load されたモデル定義が Ractor で評価される場合、コールバック定義に含まれるオブジェクトが shareable でないと、Ractor モード :raise では起動時にクラッシュします。
この PR は、その中でも Active Record 特有の on 付きコールバックが shareable になるように修正しています。
コードレベルの変更点
変更ファイルは 2 つです。
activerecord/lib/active_record/transactions.rbトランザクションコールバックの定義時に使われる
on:オプション(on: :create,on: :update,on: :destroyなど)を freeze するような修正が入っています。差分は +2/-2 行と小さいため、典型的には次のようなイメージの変更になっています(擬似コード):
ruby# 変更前(イメージ) set_callback :commit, :after, method, if: ..., on: [:create, :update] # 変更後(イメージ) set_callback :commit, :after, method, if: ..., on: [:create, :update].freeze実際には、
onに渡すシンボル/配列などのオプションオブジェクトを凍結するような変更です。
freeze することで、そのオブジェクトが Ruby の shareable オブジェクトと見なされ、Ractor から安全に参照できるようになります。
activerecord/test/cases/transaction_callbacks_test.rb- テストが 19 行追加されており、以下のような点を確認していると考えられます:
- トランザクション系のコールバック (
after_commit,after_create_commit,after_update_commit,after_destroy_commitなど) に設定されるonオプションが freeze されていること。 - freeze されても、従来通りコールバックが正しいタイミングで呼ばれること(機能面に影響がないこと)。
- トランザクション系のコールバック (
- テストが 19 行追加されており、以下のような点を確認していると考えられます:
- 影響範囲・注意点
対象となる機能
- Active Record のトランザクション系コールバック全般
(after_commit,after_rollback,after_create_commit,after_update_commit,after_destroy_commitなど) - 特に
on: ...オプションを内部的に利用している部分。
- Active Record のトランザクション系コールバック全般
アプリケーションコードへの影響
通常の利用(
after_create_commit :fooなど)では挙動は変わりません。ただし コールバック定義時に渡されるオプションオブジェクトを後からミューテート(破壊的変更)していた場合、freeze により
FrozenErrorになる可能性があります。
例:(やってはいけないが、もしやっていた場合)rubyoptions = { on: [:create] } after_commit :foo, options options[:on] << :update # ここで FrozenError になるようになる可能性そもそもこのような使い方は非推奨であり、Rails 的にも不変(immutable)である前提のほうが望ましいため、この変更は「仕様として筋が良い」方向です。
Ractor を使っていないアプリ
- 実質的な挙動変更はほぼなく、パフォーマンスや互換性への影響は軽微と考えられます。
Ractor モードを使う/検証したいアプリ
- Ractor モード
:raiseを有効にしている/検証している環境では、トランザクションコールバックを持つモデルが eager load されてもクラッシュしなくなります。 - Ractor 対応を進める上での前提条件(Active Record コールバックが shareable であること)が一つ満たされます。
- Ractor モード
- 参考情報 (あれば)
- PR: https://github.com/rails/rails/pull/58653
- 関連箇所(概念的な参考)
ActiveRecord::Transactionsモジュール- トランザクションコールバック関連の API:
- Ruby Ractor の shareable オブジェクト仕様:
この PR は小さい差分ですが、Rails の Ractor 対応を一歩進めるための基盤的な変更と位置付けられます。
#58639 Preserve fetch_multi key order when local cache is active
マージ日: 2026/9/2 | 作成者: @moizafzal936
- 概要 (1-2文で)
ActiveSupport::Cache::Store#fetch_multiが「渡したキー順で結果を返す」という仕様を、ローカルキャッシュ (Strategy::LocalCache) 使用時にも正しく守るようにしたバグ修正です。ローカルキャッシュ命中/ミスの有無に関わらず、呼び出し側が指定したキー順で結果が返るようになります。
- 変更内容の詳細
何が問題だったか
ActiveSupport::Cache::Store#fetch_multi は、ドキュメント上「引数に渡したキーの順序で値を返す」と明記されています (#34700 で導入)。
しかし Strategy::LocalCache が有効な場合のオーバーライド実装では、次のような流れになっていました:
- ローカルキャッシュに既に存在するキー(ヒット)を先に
resultsに詰める - ローカルキャッシュに無いキー(ミス)だけを
super(下位ストアのfetch_multi)で取得してresultsにマージ
それぞれのステップ単体では順序は保たれているものの、「ヒットしたキー群 → ミスしたキー群」という順番で連結されるため、呼び出し元が渡したキーの順序とは異なる並びで返ってしまうケースがありました。
例:
cache.with_local_cache do
cache.increment("bar") # "bar" がローカルキャッシュに乗る
cache.fetch_multi("foo", "bar") { 0 }.keys
# 期待: ["foo", "bar"]
# 実際(修正前): ["bar", "foo"] # ローカルヒットの "bar" が先頭に来てしまう
endどう直したか
ローカルキャッシュ付きの fetch_multi の返り値を、最終的に呼び出し元が指定したキー順で並べ直すように変更しています。
具体的には、ローカルキャッシュ分と下位ストア分をマージした後のハッシュに対して slice(*keys) を呼び出し、元のキー配列の順序に従って再構成しています。slice は指定したキーのみを順序どおりに取り出すメソッドのため、
- 結果に含まれるキー集合は変わらない
skip_nilオプションや「ミスの記録」などの挙動は変えない
という条件を満たしたまま、順序だけを修正できます。
イメージ:
# 擬似コードイメージ(実際のPRは1行の置き換え)
def fetch_multi(*names, **options)
# hits: ローカルキャッシュにある分
# misses: 下位ストアから取ってきた分
merged = hits.merge(misses)
# ここで渡されたキー順に並べ直す
merged.slice(*names)
endこれにより、Strategy::LocalCache 経由でも、ベース実装 Store#fetch_multi と同じく「リクエストしたキー順で返す」という保証が得られます。
テストとドキュメント
activesupport/test/cache/behaviors/local_cache_behavior.rb- 共有の
LocalCacheBehaviorモジュールに回帰テストを追加 - すべてのローカルキャッシュ対応ストア(たとえば Redis, MemCache)で同じテストが動くようになっています
- テスト内容のポイント:
incrementを使ってローカルキャッシュにだけ値を載せる(ストア種別に依存しない手段)- 先頭・末尾キーはローカルミス、中間のキーだけローカルヒットになるようにし、その順序が乱れないことを検証
- 共有の
activesupport/CHANGELOG.mdfetch_multiのキー順序がローカルキャッシュ使用時にも保存されるようになったことを明記
なお、Issue 側で挙げられていた「write_multi の後に fetch_multi をすると、古い値が返ってくるのでは」という懸念については、PR説明によると仕様どおりであり、本PRでは順序の問題のみにフォーカスしています。
- 影響範囲・注意点
影響範囲
cache.with_local_cacheなどでローカルキャッシュを有効化した状態でfetch_multiを使っているコード全般- Redis キャッシュストア、MemCache など
Strategy::LocalCacheをミックスインしているストアが対象
互換性
- キーの「どれが含まれるか」や取得される値自体のロジック(ヒット/ミス判定、
skip_nil、ミスの記録など)は変わりません - 変わるのは返り値のハッシュのキー順のみです
- これまで「ローカルにヒットしたキーが先に並ぶ」実装に依存していた場合は、順序が変わることに注意してください
- 正しくは「渡したキー順で返る」ことに依存すべきであり、今回の修正はドキュメントどおりの挙動への是正です
- キーの「どれが含まれるか」や取得される値自体のロジック(ヒット/ミス判定、
テスト
- 既存のテストに加え、回帰テストが共通のBehaviorとして入っているため、今後別ストアの変更などによる再発リスクは低減されています
- 参考情報 (あれば)
- 対応Issue: #58637
- 元仕様導入PR: #34700 (
fetch_multiがキー順を保証するようになった変更) - 関連クラス:
ActiveSupport::Cache::Store#fetch_multiActiveSupport::Cache::Strategy::LocalCache
#58642 Freeze Active Record time zone related configuration
マージ日: 2026/9/2 | 作成者: @Edouard-chin
- 概要 (1-2文で)
Rails 7 での Ractor 対応の一環として、Active Record の「タイムゾーン関連設定」をフリーズ(凍結)し、Ractor 間の共有時に起きる不変性違反・Isolation 問題を解消する PR です。これにより、属性セット(attribute set)が Ractor ごとに遅延構築されても、安全に設定を共有できるようになります。
- 変更内容の詳細
背景・問題点
#58578 により、Active Record の「attribute set(カラム定義などを含むメタ情報)」は:
- 以前:
load_schema時に一括・事前ロード(eager load) - 現在: Ractor ごとに遅延構築(lazy, per ractor)
- 以前:
その結果、次のようなコードが動かなくなっていました:
rubyPost.create! Ractor.new do Post.create! end理由:
- Ractor 内で attribute set が再構築される過程で、Active Record の「タイムゾーン関連設定」(例:
config.active_record.default_timezoneやtime_zone_aware_attributesなど)にアクセスする。 - これらの設定オブジェクトが Ractor-safe(不変 / deep-freeze)ではないため、Ractor 間で共有すると isolation ルールに違反してしまう。
- Ractor 内で attribute set が再構築される過程で、Active Record の「タイムゾーン関連設定」(例:
この PR の対応内容
- Active Record Railtie (
activerecord/lib/active_record/railtie.rb) において、- 「Active Record のタイムゾーン関連設定」をアプリケーション起動時に**freeze(凍結)**するように変更。
- これにより、その設定オブジェクトは不変になり、Ractor 間で安全に共有可能になる。
railties/test/application/configuration_test.rbにテストを追加し、- 該当の設定が起動時にフリーズされていること、
- かつ通常利用に支障がないこと を確認している。
※ PR 本文から読み取れる範囲では「どの設定キーをフリーズしているか」の列挙はありませんが、通常は以下のような Active Record のタイムゾーン系設定が対象になります(イメージ):
config.active_record.default_timezoneconfig.active_record.time_zone_aware_attributesconfig.active_record.time_zone_aware_typesconfig.active_record.active_record.default_timezone周辺の内部設定
実際のコードでは、これらの設定を参照している内部構造(配列・ハッシュなど)もフリーズ対象にしていると考えられます。
- 影響範囲・注意点
影響範囲
Ractor を使う Rails アプリ
- Ractor 内で Active Record モデルを初回利用する際に attribute set が構築されても、タイムゾーン設定が Ractor-safe になったことでエラーなく動作するようになります。
- とくに並列処理で
Ractor.new { Post.create! }などを行うケースが対象。
Active Record のタイムゾーン設定を実行時に書き換えていたコード
- 起動後に
config.active_record.xxx = ...相当のことを行う / 内部構造を変更するようなコードがあった場合、freeze によりFrozenErrorが発生する可能性があります。 - そのため、タイムゾーン関連設定は「起動前(configuration phase)」にのみ変更することが前提になります。
- 起動後に
注意点
- 設定を書き換えるパターンの例(NG になりうる):ruby
# 起動後どこかで ActiveRecord::Base.time_zone_aware_attributes = false # これが禁止/例外になる可能性あり ActiveRecord::Base.time_zone_aware_types << :datetime # フリーズされた配列の破壊的変更は不可 - 対策としては:
config/application.rbもしくは各config/environments/*.rb内で、rubyのようにアプリ起動前にすべて設定しておく必要があります。config.active_record.time_zone_aware_attributes = false config.active_record.time_zone_aware_types = [:datetime, :time]
- Ractor を使わない・設定を書き換えない通常のアプリでは、基本的に挙動の変化はなく、パフォーマンスへの影響も軽微です(freeze は起動時に一度行われるだけ)。
- 参考情報 (あれば)
- 関連 PR:
- #58578: attribute set を「Ractor ごとに遅延構築」に変更した PR
- Ractor 関連の一般的な注意:
- Ractor 間で共有できるのは「イミュータブル(freeze 済み)」なオブジェクトか、一部の Ractor-safe オブジェクトのみ。
- Rails の設定オブジェクトや内部配列/ハッシュを Ractor で共有するには、今回のような freeze 対応が必須になる。
#58627 Make ActionPack settings instance variables
マージ日: 2026/9/2 | 作成者: @etiennebarrie
- 概要 (1-2文で)
ActionPack 内の一部設定をmattr_accessorベースのクラス変数管理から、インスタンス変数ベースの設定管理に切り替える変更です。特にActionDispatch::Requestの MIME ネゴシエーション設定(ignore_accept_header)などで、「インスタンスからクラス設定が書き換わる」不自然な挙動をやめ、より一貫した設定の扱いにしています。
- 変更内容の詳細
2-1. MimeNegotiation (mime_negotiation.rb)
元の挙動の例として PR で挙げられているのが以下です:
ActionDispatch::Request.ignore_accept_header
# => false
ActionDispatch::Request.new({}).ignore_accept_header = true
# => true
ActionDispatch::Request.ignore_accept_header
# => trueここでは ignore_accept_header が mattr_accessor で定義されていたため、
- クラスレベル:
ActionDispatch::Request.ignore_accept_header - インスタンスレベル:
ActionDispatch::Request.new({}).ignore_accept_header = true
の両方から同じクラス変数を触っており、インスタンスの設定変更がそのままクラス設定を書き換えてしまう状態でした。
PR では:
mattr_accessorをやめて、ActionPack の「設定オブジェクト」(ActionPack::Railtieがぶら下げている config)に紐づくインスタンス変数として扱うように変更- それに伴って、
MimeNegotiationでは インスタンスレベルのアクセサを提供しない 形に整理
(PR 本文で「インスタンスレベル accessors を消すが、非公開 API かつインスタンスからクラスを間接的に変えるのはおかしいので問題ない」と明言)
結果として:
ActionDispatch::Request.ignore_accept_headerのような「グローバル設定」は Rails 設定オブジェクト由来のインスタンス変数で一元管理されるrequest.ignore_accept_header = trueのように、個々の Request インスタンスからグローバル設定を書き換えるパターンは廃止される(そもそも公開 API ではなかった)
といった整理が行われています。
2-2. ExceptionWrapper (exception_wrapper.rb)
ExceptionWrapper 周りの設定も同様に、「クラス変数 / mattr_accessor ベース」から「インスタンス変数ベースの設定」に寄せる変更が入っています。
典型的には以下のような設定が対象と考えられます(実際のコードは PR ファイル内ですが、Rails 既存仕様からの類推です):
- backtrace の表示有無
- 内部エラーかどうかの判定に使うクラス・ステータスコードのマッピング
これらが:
- 以前:
mattr_accessorでクラス単位のグローバル設定 - 以後: ActionPack 設定オブジェクトに紐づいたインスタンス変数で管理される
ようになります。
テスト (exception_wrapper_test.rb) では、例外ラップ時の設定反映のしかたやデフォルト値がインスタンス変数経由でも正しく動作することを検証しています。
2-3. テストの追加 (request_test.rb, exception_wrapper_test.rb)
ActionDispatch::Requestが新しい設定保持方法でも期待通り動くことを確認するテストが追加ExceptionWrapperについても、例外発生〜ラップ〜レスポンス生成の流れで、設定が正しく適用されるかを確認するテストが追加
これにより:
- インスタンス変数ベースへの移行による挙動変化が意図通りであること
- 互換性の維持(公開 API ベースで見たときに問題がないこと)
がテストで裏づけられています。
- 影響範囲・注意点
3-1. インスタンスからクラス設定を書き換えるコードは壊れる可能性
次のようなコードを書いている場合は動かなくなる/意味が変わる可能性が高いです:
# 非推奨な使い方の例(元々ドキュメント化されていない)
request = ActionDispatch::Request.new(env)
request.ignore_accept_header = true # これでグローバル設定が変わることに依存しているPR ではこのパターンを「非ドキュメントな挙動」と位置づけており、ここへの依存はサポート対象外として切り捨てています。
クラス全体の設定変更はクラス/設定オブジェクト経由でのみ行うようにコードを見直す必要があります。
3-2. 設定のライフサイクルとスレッド安全性
mattr_accessor + クラス変数は、マルチスレッド環境で扱いが難しく、かつ複数アプリケーション/エンジンから共有される場合に意図せず衝突することがあります。
インスタンス変数ベースへの移行は:
- Rails の設定オブジェクト単位にスコープが閉じる
- テスト時に設定を差し替えたり、1 プロセス内で複数アプリを動かしたりするケースでの独立性が上がる
といったメリットがあり、これに合わせてコード側でも「設定は config/initializer 経由で一元管理する」書き方に寄せていくのが望ましいです。
3-3. 公開 API と非公開 API の線引き
今回削られたのは主に:
- インスタンスから ActionPack のクラス設定をいじるためのアクセサ
- もともとドキュメント化されていない(= Rails チームとして互換性を保証していない)部分
です。
もし自前で ActionDispatch::Request や ExceptionWrapper を直接拡張・モンキーパッチしている場合は、この PR の差分を一度確認して、クラス変数/mattr_accessor に直接依存していないかをチェックする必要があります。
- 参考情報 (あれば)
- この PR は
#58620と同系統の変更で、ActionPack 全体で「設定をインスタンス変数ベースに寄せる」リファクタリングの一部と位置付けられています。 - 関連するコードは主に以下:
actionpack/lib/action_dispatch/http/mime_negotiation.rbactionpack/lib/action_dispatch/middleware/exception_wrapper.rb- テスト:
actionpack/test/dispatch/request_test.rbactionpack/test/dispatch/exception_wrapper_test.rb
- Rails の設定まわりの設計方針として、「
mattr_accessor/ クラス変数を減らし、フレームワーク設定オブジェクトとインスタンス変数側に寄せる」流れが続いており、この PR もその一環です。
#58643 Store the AssetTagHelper settings in singleton class attributes
マージ日: 2026/9/2 | 作成者: @etiennebarrie
- 概要 (1-2文で)
AssetTagHelper が持つ6つの設定値の保持方法を、クラス変数からモジュール自身のインスタンス変数(単一オブジェクト上の属性)へ変更し、インスタンス経由でグローバル設定を書き換える仕組みを削除して、Ractor 対応と設計の明確化を図る変更です。Rails 7以降で予定される Ruby 4.0 / Ractor 環境でも AssetTagHelper の設定が安全に参照できるようにすることが主目的です。
- 変更内容の詳細
2-1. 何が問題だったか
- 対象:
ActionView::Helpers::AssetTagHelper - ここには「6つの設定」があり、従来は クラス変数 (
@@fooなど) で保持されていました。 - Ruby 4.0 では、メイン Ractor 以外からクラス変数を読むことができない 仕様になる予定であり、
AssetTagHelper の設定が非メイン Ractor から参照できず、Ractor 対応に支障が出る状態でした。 - さらに、「ヘルパーインスタンスの setter を呼ぶとグローバル設定を書き換える」というような設計になっており、
どこから設定が変更されるか分かりづらく、副作用も大きい状態でした。
2-2. どう直したか
1) クラス変数 → モジュールのインスタンス変数へ
AssetTagHelperモジュール自体は Ruby のオブジェクトなので、そこに インスタンス変数 (@foo) を持たせることで
「事実上のシングルトン設定オブジェクト」として扱うように変更されています。- これにより:
- Ractor 間でクラス変数を共有しない Ruby 4.0 の仕様に依存せず、
「モジュールオブジェクトの属性」という形で状態を保持できます。 - グローバルな設定は依然として 1 箇所にまとまっているが、保持方法が Ractor 互換的になります。
- Ractor 間でクラス変数を共有しない Ruby 4.0 の仕様に依存せず、
※ PR本文では「Use instance variables on the module directly」と記載されている箇所がこれに該当。
2) 「委譲用の reader メソッド」を追加
実際に
image_tag,javascript_include_tagなどのアセットタグヘルパーからは、
直接インスタンス変数にアクセスするのではなく、reader メソッドを経由して設定値を取得するように変更。構造イメージ(擬似コード):
rubymodule ActionView module Helpers module AssetTagHelper class << self attr_accessor :asset_path, :asset_host, :asset_url_options # ... 他の3つの設定も同様 end # インスタンスから呼ばれる reader def compute_asset_host(...) AssetTagHelper.asset_host # ← モジュールのクラスメソッド経由 end end end end※実際の名前や数はPRの「six settings」に相当するものですが、構造イメージとして。
これにより、「どの設定がどこから参照されているか」が明確になり、
かつ Ractor 互換な形で設定にアクセスできます。
3) インスタンス writer による「グローバル書き換え」を廃止
これまで AssetTagHelper には、「インスタンスメソッドの writer を呼ぶと内部的にクラス変数を書き換え、
それがグローバル設定になる」というパターンがありました。例(イメージ):
ruby# 以前はこういうことができた可能性がある: helper.asset_host = "https://cdn.example.com" # 実はグローバルを書き換えていたPRでは、このようなインスタンス writer が削除されています。
- これにより、「個々の view コンテキストでの操作が、実はグローバル設定を壊していた」 といった曖昧な挙動がなくなります。
- グローバルな設定変更は、
config.action_controller.asset_host = ...やRails.application.config.action_controller.asset_host = ...のような正式な設定経路を使うのが前提になります。
4) テストの追加
actionview/test/template/asset_tag_helper_test.rbに 28 行のテストが追加。- 主な意図:
- 新しい設定保持方式で、従来どおりの挙動(設定値の参照結果)が維持されているか
- インスタンス writer の削除に伴う API の変化で、想定どおりにエラー/挙動になるか
- これにより後続の Ractor 対応や Ruby バージョンアップでのリグレッションを防ぎます。
- 影響範囲・注意点
3-1. 公開API的な影響
一般的な Rails アプリで普通に
image_tag,stylesheet_link_tagなどを使っているだけなら、
挙動の変化は基本的にありません。影響しうるのは、以下のようなコードを書いている場合です:
ActionView::Helpers::AssetTagHelperの クラス変数 (@@...) を直接参照・変更していた。- AssetTagHelper の インスタンスメソッドの setter を使ってグローバル設定を書き換えていた(非推奨な内部実装依存)。
これらはもともと内部実装への依存度が高い書き方であり、Rails の想定する使い方ではありません。 そのため、この PR によりコードが動かなくなる可能性があります。
3-2. Ractor / 並行実行環境での意味
- Ruby 4.0 での Ractor を使った並行実行環境では、
クラス変数はメイン Ractor 以外から読めなくなります。 - 本変更により、AssetTagHelper の設定は:
- モジュールオブジェクト上のインスタンス変数として一元管理される
- reader メソッド越しに参照される
- これが Ractor 間でどう共有/コピーされるかは、さらに上位レイヤ(設定の freeze・複製戦略など)にも依存しますが、
少なくとも「クラス変数が読めなくて落ちる」という問題は避けられるようになります。 - 将来的に Rails が Ractor フレンドリーな構成に進んでいく上での前提整備的な変更といえます。
3-3. gem / ライブラリ作者への注意
- AssetTagHelper をモンキーパッチしてクラス変数を書き換えていた / 参照していた gem がある場合は、
これを機に 公式にサポートされる設定 API(Rails.application.config.action_controller.asset_hostなど)への
依存に切り替える必要があります。 - AssetTagHelper の内部実装(変数の種類や場所)に直接依存するのは今後ますます壊れやすくなるため、
本PRは「内部実装への依存をやめてほしい」というメッセージとも解釈できます。
- 参考情報 (あれば)
- 本PRが参照している前段PR:
- https://github.com/rails/rails/pull/58627
→ 同様に「グローバル設定をクラス変数で持ち、インスタンス writer がグローバルを書き換える」ような箇所を
Ractor 互換な形へリファクタリングしていると考えられます。
- https://github.com/rails/rails/pull/58627
- Ractor とクラス変数の制約(Ruby 側の事情):
- Ruby 3 系以降、Ractor ではグローバル可変状態の扱いが厳しくなっており、 クラス変数やグローバル変数は基本的に Ractor 間で安全に共有できない方向の設計です。
- Rails はこの流れに合わせ、設定値の持ち方を徐々に Ractor フレンドリーな形へ移行しています。
#58629 Make the allow_browser callback shareable
マージ日: 2026/9/2 | 作成者: @etiennebarrie
- 概要 (1-2文で)
allow_browserの「未サポートブラウザに対するデフォルト動作」が Ractor でも共有可能なオブジェクト(shareable proc)になるよう修正された PR です。あわせて、Ruby の制約に対応するため、オプションキーワード引数:blockの扱いが変更されています。
- 変更内容の詳細
2-1. 背景: Ractor 対応と shareable なオブジェクト
Ruby 3 以降の Ractor では、「Ractor 間で共有できるオブジェクト (shareable)」と「共有できないオブジェクト」が明確に区別されます。allow_browser 内部で利用していた「未サポートブラウザ時のデフォルト動作」を表す Proc / lambda が shareable でない場合、Ractor からそのコントローラやミドルウェアを利用しようとするとエラーや制約にぶつかります。
この PR では、そのデフォルト動作を shareable な Proc として定義し直すことで、Ractor 内から allow_browser を使えるようにしています。
2-2. allow_browser のデフォルト動作を shareable に
actionpack/lib/action_controller/metal/allow_browser.rb において、未サポートブラウザに対するデフォルトのコールバック(たとえば 406 を返す、または何らかのエラー/レスポンスを返す処理)が、Ractor でも共有可能な Proc/Lambda として定義し直されています。
コード断片イメージ(※実際のコード構造のイメージです):
# もともとクラス定義内のメソッドで直接 Proc を生成していたところを、
# Ractor.shareable な形で定義するように変更
DEFAULT_UNSUPPORTED_BROWSER_ACTION = lambda do |controller|
# 未サポートブラウザ時のデフォルトのレスポンス処理
end
Ractor.make_shareable(DEFAULT_UNSUPPORTED_BROWSER_ACTION) if defined?(Ractor)allow_browser を呼び出すときにブロックを渡さなかった場合、この shareable な DEFAULT_UNSUPPORTED_BROWSER_ACTION が使われるような方針になっています。
2-3. オプションキーワード引数 :block のローカル変数化
PR で説明されている通り:
I also had to capture the optional keyword
:blockas a separate local variable to allow the lambda to capture it. It's a limitation with Ruby detecting optional keywords with non-literal default values as reassignable.
Ruby では、「非リテラルなデフォルト値を持つオプションキーワード引数」は「再代入される可能性がある」と見なされ、lambda 内でのキャプチャに制限がかかるという仕様上の制約があります。
そのため、以下のような形の修正が入っています。
def allow_browser(block: DEFAULT_UNSUPPORTED_BROWSER_ACTION, **options)
# 直接 block を lambda からキャプチャすると問題が出るため、
# 一度ローカル変数に退避する
action = block
before_action(**options) do
# この lambda / proc が action (block) をキャプチャする
instance_exec(&action)
end
endポイント:
- キーワード引数
block:を直接 lambda/Proc が参照するのではなく、
一度actionなどのローカル変数に代入してからキャプチャする。 - これにより Ruby の「非リテラルなデフォルト値を持つキーワード引数は再代入可能」という判定を回避しつつ、Ractor shareable な形で Proc を取り回せるようにしている。
2-4. テストの追加
actionpack/test/controller/allow_browser_test.rb に 23 行のテストが追加されています。
主に以下の点を検証していると考えられます。
- ブロック未指定で
allow_browserを呼び出した場合も正常に動作すること。 - デフォルトの unsupported-browser コールバックが期待通りのレスポンスを返すこと。
- Ractor や shareable 化に起因する動作の回 regressions がないこと。
(テスト名の構成から、少なくともデフォルト動作の利用パスや、block: キーワードの扱いまわりがカバーされているはずです。)
- 影響範囲・注意点
通常のシングルスレッド / マルチスレッド Rails アプリ
既存のallow_browser利用コードは、基本的に挙動はそのままです。
ブロックを明示的に渡している場合も、渡していない場合も、API のシグネチャは変わっていません。Ractor を利用する環境での改善
allow_browserを使ったコントローラやミドルウェアが、Ractor 内でも利用しやすくなります。- 従来は Ractor で
ActionControllerを扱うときに、内部に non-shareable な Proc があって問題になる可能性がありましたが、その一部が解消されています。
カスタムの unsupported-browser コールバックを渡している場合
- 自分で
allow_browser block: -> { ... }のように Proc/Lambda を渡している場合、その Proc/Lambda 自体が shareable でないと、Ractor 間で共有する際には依然として制限を受けます。 - この PR が shareable にしたのは「Rails のデフォルト動作」であり、ユーザー定義の Proc までは自動的に shareable にはなりません。
- Ractor をガチ利用する場合は、自分のブロックも Ractor.shareable? を意識した実装にする必要があります。
- 自分で
Ruby の仕様に依存した書き方のため、バージョン依存の可能性
「オプションキーワードの非リテラルデフォルト値」に関する Ruby の挙動に対処する実装となっているため、Ruby の将来のバージョンでこの仕様が変わると、また別の書き方が必要になる可能性があります。ただし現時点では Rails 側で安全なパターンに寄せているため、実務的には問題になりにくい想定です。
- 参考情報 (あれば)
変更ファイル
actionpack/lib/action_controller/metal/allow_browser.rbactionpack/test/controller/allow_browser_test.rb
関連ドキュメント
- Rails ガイド (Edge Guides): Action Controller Overview / Browser support /
allow_browserセクション(最新版に追従される可能性があります) - Ruby 3+ Ractor ドキュメント:
Ractor.shareable?,Ractor.make_shareable - Ruby のキーワード引数とデフォルト値に関する仕様:
- 非リテラルなデフォルト値の扱い
- lambda / Proc へのキャプチャ時の制約
- Rails ガイド (Edge Guides): Action Controller Overview / Browser support /
#58645 Fix TypeCaster sometimes leaking connection
マージ日: 2026/9/2 | 作成者: @skipkayhil
- 概要 (1-2文で)
TypeCaster が内部で使う DB コネクションを「借りっぱなし」にしてしまうケースがあり、接続プールからコネクションがリークする不具合を修正した PRです。Executor によるクリーンアップに依存せず、with_connectionを使って即座にコネクションを返却するように変更されています。
- 変更内容の詳細
変更ファイルは 1 件で、実質 1 行のメソッド呼び出し差し替えです。
activerecord/lib/active_record/type_caster/connection.rb 内で、TypeCaster がデータベース接続を取得する際のコードが、(推測される形として)以下のように変わっています:
# 変更前(例示)
def with_connection
@connection_pool.connection.tap do |conn|
yield conn
ensure
# Executor 終了時のクリーンアップに依存していた
end
end
# 変更後(例示)
def with_connection
@connection_pool.with_connection do |conn|
yield conn
end
end実際の差分としては +1 / -1 行なので、だいたい次のような単純な呼び出し変更です:
# before
connection = @pool.connection
# after
@pool.with_connection do |connection|
...
endポイントは「connection で取得してそのまま使う」のではなく、「with_connection ブロックを通して使う」ようにしたことです。with_connection はブロックを抜けるタイミングで確実に connection_pool にコネクションを返却するため、Executor(スレッド実行コンテキスト)に紐づくクリーンアップ処理に頼らずともリークしない形になります。
PR の説明にある通り、「Live actions(ActionController::Live / Turbo Streams など、長時間生きる処理のコンテキスト)では Executor のクリーンアップが期待通りに呼ばれないことがある」ため、その前提に依存していた TypeCaster 経由のコネクション利用がリークの原因になっていました。この PR によって TypeCaster 側だけで完結してコネクションの貸し出し/返却を管理するように修正されています。
- 影響範囲・注意点
影響範囲
- ActiveRecord の TypeCaster が DB にアクセスして型情報を得るようなケース(例: カラム情報の解決など)で、内部的に利用される DB コネクションの扱いが変わります。
- 特に Live actions やストリーミングレスポンスなど「リクエストライフサイクルが長い」コンテキストで、接続プールのコネクション枯渇やリークが発生していた環境で効果が出ます。
- 通常のリクエスト/レスポンスでも、より確実にコネクションがプールに戻る形になるため、コネクション使用の健全性が高まります。
注意点
- 互換性への影響はほぼありません。API シグネチャの変更はなく、内部実装の改善に留まっています。
- もし独自に
TypeCasterまわりを拡張してconnection_pool.connection取得をラップしているようなメタプログラミングをしている場合は、with_connection前提の実装になったことを念のため確認するとよいです(通常はそのようなケースは稀です)。 - コネクションが早めに返却されることにより、「同じスレッドがずっと同じコネクションを保持する」ことを前提にしたようなコードがあれば(これ自体がアンチパターンですが)、挙動が変わる可能性があります。
- 参考情報 (あれば)
- 関連 Issue: #58644
- 内容: TypeCaster 利用時にコネクションがリークし、特に Live actions で接続プールが枯渇する問題の報告。
- Rails コネクションプール API:
ActiveRecord::ConnectionAdapters::ConnectionPool#with_connection- ブロック内でコネクションを貸し出し、ブロック終了時に必ずプールへ返却する公式かつ推奨の使い方。
- 背景技術:
- Rails では Executor(
ActiveSupport::Executor)を通じて、スレッドまたはリクエスト単位でのリソースライフサイクル管理(run!/complete!)を行っていますが、Live actions など特殊なライフサイクルではこれに完全には乗らないケースがあり、そのギャップを埋めるための修正と言えます。
- Rails では Executor(
#58578 Ractorize Active Record schema context
マージ日: 2026/9/2 | 作成者: @gmcgibbon
- 概要 (1–2文で)
Active Record のSchemaContextを、Ractor(マルチスレッド/マルチRactor環境)でも安全に扱えるように再設計したPRです。SchemaContextの中にAttributesオブジェクトを埋め込み、スキーマ関連の状態を Ractor ごとに分離しつつ、モデルクラス側の ivar はメイン Ractor で遅延評価できるように整理しています。
- 変更内容の詳細
2-1. SchemaContext の構造変更
目的SchemaContext が持つ属性関連の状態を、Ractor ごとに独立させるために、Ractor のストレージ内に埋め込める専用クラス (Attributes) に切り出しています。
これにより、同じモデルでも Ractor ごとに別のスキーマ状態を持てるようになり、Ractor セーフなスキーマ参照が可能になります。
主な変更点
ActiveRecord::ModelSchema::SchemaContextが次のような構造に変更:- これまではコンテキスト自身が直接 ivar で各種状態(attributes / primary key / predicate builder など)を持っていた
- 変更後は
SchemaContext::Attributesのようなオブジェクトを内部に保持し、それを Ractor ストレージに載せる設計に
- 「スキーマコンテキストに固有なインスタンス変数」をモデルクラス側に移動し、メイン Ractor で遅延評価できるようにした
例:- 某モデルクラスの
@arel_table/@primary_key/ predicate 関連キャッシュなど、これまでSchemaContext側にあった(または強く結合していた)ものを、モデルクラスの ivar として保持+必要になった時点で計算
- 某モデルクラスの
- この設計により:
- 「Ractor ローカルな状態」…
SchemaContext::Attributesなどを Ractor ストレージに置く - 「プロセス全体で共有しつつ、スレッドセーフかつ遅延評価するもの」… モデルクラスの ivar で扱う という切り分けが明確になります。
- 「Ractor ローカルな状態」…
2-2. Active Model / Active Record 周辺の調整
変更ファイルから読み取れる範囲でのポイント:
activemodel/lib/active_model/attribute_registration.rb- 属性登録まわりで、
SchemaContextの新しい設計(Attributesオブジェクト)に対応 - モデルに属性を定義・登録する際に、Ractor ごとに正しい属性定義が取れるような参照経路に変更
- 属性登録まわりで、
activemodel/lib/active_model/type.rb- 型情報 (
ActiveModel::Type) を扱う部分で、SchemaContext/Ractor 対応のための小さな修正 - 例: type マッピングの参照先が
SchemaContext内のAttributesを経由するように調整されている可能性が高い
- 型情報 (
activerecord/lib/active_record/attributes.rb- モデルインスタンスの attributes 管理が、新しい
SchemaContext構造に追従 @attributesのビルドや読み込み時に、Ractor ごとのスキーマ状態を使うような経路に
- モデルインスタンスの attributes 管理が、新しい
activerecord/lib/active_record/model_schema.rb- モデルのスキーマ設定(
table_name,primary_key,inheritance_columnなど)を扱うメソッドが、- Ractor ローカルな情報は
SchemaContext - グローバルかつ遅延評価すべき情報はモデルクラス ivar に適切に分離されるよう処理をリファクタリング
- Ractor ローカルな情報は
- モデルのスキーマ設定(
※PR本文にコード例は無いですが、概念的には以下のようなイメージになります:
# 旧: SchemaContext が直接いろいろ持っていた
class SchemaContext
def primary_key
@primary_key ||= compute_primary_key
end
end
# 新: Ractor ローカルな部分を Attributes に分離し、モデルクラスの ivar にも責務を移動
class SchemaContext
class Attributes
# Ractor ごとに変化しうる attribute 情報をここに持つ
end
def attributes
@attributes ||= Attributes.new(...)
end
end
class ApplicationRecord < ActiveRecord::Base
class << self
def primary_key
@primary_key ||= schema_context.attributes.primary_key
end
end
end2-3. テストまわりの変更
activerecord/test/cases/attributes_test.rb- 属性定義・読み書きロジックが、新しい
SchemaContext実装でも変わらないことを確認するように更新 - 既存の仕様を壊していないかの回帰テスト寄りの変更
- 属性定義・読み書きロジックが、新しい
activerecord/test/cases/core_test.rbactiverecord/test/cases/primary_keys_test.rbactiverecord/test/cases/relation/predicate_builder_test.rb- 主キー解決・リレーション生成・predicate builder など、スキーマ情報を多用する部分について、
- Ractor 対応後も今まで通り動作するか
- Ractor ごとの状態差分を持てるか などを確認するテストが追加/更新
- 主キー解決・リレーション生成・predicate builder など、スキーマ情報を多用する部分について、
activesupport/lib/active_support/testing/ractors_assertions.rb- Ractor を使ったテストを支援する assertion 群に、追加アサーションが入っている
- 例: 「複数 Ractor で同じコードを実行しても衝突しない」「Ractor 間で状態が混ざらない」ことを検証する機能
- 影響範囲・注意点
- Ractor を使わないアプリケーション
- 基本的には挙動は変わらない想定(後方互換性を維持するリファクタリング)
- ただし、Active Record の内側の ivar 構造が変わっているため、
- 内部 API(
SchemaContextや内部 ivar)を直接触っている場合は影響しうる
- 内部 API(
- Ractor / マルチスレッドで Active Record を使う場合
- Ractor ごとに独立したスキーマ属性状態を持てるようになり、データ競合や不正なメモリアクセスのリスクが下がる
- モデルのクラスレベル状態(スキーマ由来の ivar)はメイン Ractor で lazy に評価されるため、
- 初回アクセス時にわずかなオーバーヘッドがある一方、
- Ractor 作成時点で全モデルのスキーマを読み込む必要はなくなり、メモリと初期コストを抑えられる
- 内部 API・メタプログラミングに依存しているコード
SchemaContextの具体的な実装(インスタンス変数構成やメソッド参照)に依存している場合、今回の設計変更で壊れる可能性がある- 公開 API(通常の Active Record モデルの使い方)内にとどまっていれば、変更は透過的なはず
- 参考情報 (あれば)
- PR: https://github.com/rails/rails/pull/58578
- 関連キーワード:
- Ractor セーフな Active Record
- SchemaContext / Attributes の責務分離
- クラスレベル ivar の遅延評価 (lazy evaluation)
- 背景知識:
- Ruby Ractor: Ruby 3 以降で導入された並列実行機構。オブジェクト共有制限があるため、フレームワーク側で Ractor 対応のための設計変更が必要になる。
#58636 Remove leftover perform_query stubs in TrilogyAdapterTest
マージ日: 2026/9/2 | 作成者: @yahonda
- 概要 (1-2文で)
TrilogyAdapterTest でperform_queryをスタブしていたテストが、スタブ後に元のメソッドを正しく戻さずにコネクションプールへ返していたため、非同期クエリのリトライ系テストが期待どおりに失敗を注入できなくなっていました。
このPRでは、スタブ終了後にコネクションインスタンスに残ってしまうperform_queryのシングルトンメソッドをensureブロックで確実に削除し、後続テストへの副作用をなくしています。
- 変更内容の詳細
問題の背景
TrilogyAdapterTest内の3つのテスト#begin_db_transaction raises error#commit_db_transaction raises error#rollback_db_transaction raises error
で、プールから取得したコネクションオブジェクトに対してperform_queryをMinitest#stubで差し替えていました。
Minitest#stubは、対象インスタンスの シングルトンクラスにメソッドを定義する形で stub を差し込む ため、ブロック終了後に「元のメソッドに戻す」処理はしてくれるものの、- シングルトンクラスにエイリアスや元メソッドを残す
- その結果として、そのインスタンスに「クラス定義よりも優先される
perform_queryがぶら下がった状態」
になっていました。
後続の
AsynchronousQueriesTestでは、「perform_queryをアダプタクラスレベルで再定義」することで、意図的にクエリ失敗を発生させ、リトライ動作をテストしています:rubywith_async_query_failures do # perform_query をクラスレベルで上書きし、失敗 -> リトライを検証 endところが、一部の接続インスタンスは前述の TrilogyAdapterTest によるシングルトンメソッドを保持したままプールに戻されており、その接続を掴んだスレッドでは
- クラスレベルでの
perform_query再定義が インスタンスに届かない - つまり「失敗注入されない接続」で非同期テストが動いてしまう
という状態になっていました。
- クラスレベルでの
どのテストが落ちるかは「どのスレッドがその汚染済み接続を取るか」に依存するため、再現性がシード値やCI環境に依存する、やや厄介なテスト汚染バグになっていました。
このPRでの修正内容
- 上記3つのテストで、
perform_queryをスタブした後、必ずシングルトンメソッドを削除するよう修正しています。 - 具体的には、テスト内の stub 利用箇所を
ensureブロックで囲み、connection.singleton_class.remove_method(:perform_query)のような形で後始末を行っています(実際のコードはtrilogy_adapter_test.rbに6行追加)。
イメージとしては以下のような変更です(疑似コード):
def test_begin_db_transaction_raises_error
connection = ActiveRecord::Base.connection_pool.checkout
begin
connection.stub(:perform_query, ->(*) { raise SomeError }) do
# ここで begin_db_transaction の挙動を検証
end
ensure
# stub によりインスタンスにぶら下がった perform_query を削除
connection.singleton_class.send(:remove_method, :perform_query) if
connection.singleton_class.method_defined?(:perform_query)
end
endこれにより、テスト終了時にはコネクションインスタンスから perform_query のシングルトンメソッドが確実に消え、クラスレベルの再定義が正しく効くようになります。
- 影響範囲・注意点
- 影響範囲は テストコードのみ であり、本番コード(ActiveRecord Trilogy Adapter 本体)の挙動には変更ありません。
- ただし、これまで「たまたま通っていた」非同期クエリのリトライ周りのテストが、他のテストとの順序依存で落ちる可能性があったものが、順序に依存せず安定して動作するようになります。
- 類似のパターン(インスタンスに対して
stubして、接続プール等に返すもの)が他のアダプタ/テストにもある場合、同様の問題を引き起こす余地があります:- 接続オブジェクトやグローバルに再利用されるオブジェクトに対して
stubを行う場合、- ブロック終了後のメソッド解決順序(シングルトンメソッドの残存)を意識する
- 必要なら
ensureでの後片付けや、クラスレベルの stub/mocking を検討する
ことが推奨されます。
- 接続オブジェクトやグローバルに再利用されるオブジェクトに対して
- 参考情報 (あれば)
- 関連PR: 非同期クエリのリトライテストを追加した PR #58049 のフォローアップ。
- 再現条件:
TrilogyAdapterTestの該当テストが先に実行され、その後AsynchronousQueriesTestのリトライ系テストを実行すると、
コネクションプールに残ったシングルトンperform_queryのせいでリトライ用の失敗注入が機能しなくなる。ARCONN=trilogy+ 特定シード (6, 7, 8, 10 など) でのテスト実行で再現していた。
- このPR適用後は、上記の最小再現ケースおよび該当テストファイル全体が、シードに依存せず通ることが確認されています。
#58625 Avoid a redundant join in scoped through associations
マージ日: 2026/9/1 | 作成者: @dpaluy
- 概要 (1-2文で)
has_many :throughの関連にスコープをマージする際、すでに through の結合(JOIN)で登場しているテーブルがスコープ側でも再度 JOIN されてしまうケースで、その冗長な JOIN を自動的に削除するようにした変更です。belongs_to/has_one方向の単一レコード JOIN に限って重複を取り除き、クエリ結果が変わり得るコレクション JOIN はそのまま残すようになっています。
- 変更内容の詳細
問題の背景
has_many :through に対して、スコープ内でさらに関連を joins していると、through のチェーンが貼る JOIN とスコープ側の JOIN が同じテーブルを重ねて JOIN してしまうことがありました。
例として PR 説明に出ているケース(#51259由来)を簡略化すると:
class Rubygem < ApplicationRecord
has_many :versions
has_many :dependencies, through: :versions
# このあたりで versions / dependencies を join するスコープを
# incoming_dependencies 的な名前で定義しているイメージ
endhas_many :through の定義と、そのスコープの中身の両方で versions を JOIN してしまうと、生成される SQL に同じテーブルが2回出てきます。
PR前のSQL:
SELECT "rubygems".* FROM "rubygems"
INNER JOIN "versions" ON "rubygems"."id" = "versions"."rubygem_id"
INNER JOIN "dependencies" ON "versions"."id" = "dependencies"."version_id"
INNER JOIN "versions" "versions_dependencies" ON "versions_dependencies"."id" = "dependencies"."version_id"
WHERE "dependencies"."rubygem_id" = ?
AND "versions"."indexed" = ?
AND "versions"."position" = ?3つめの INNER JOIN "versions" "versions_dependencies" は、実質的に同じ結合条件をもう一度書いているだけで、結果セットを絞り込みも増やしもしていない「無駄な JOIN」です。
このPRの対応内容
activerecord/lib/active_record/associations/association_scope.rb の AssociationScope#add_constraints が変更され、以下のロジックが追加されています(要約):
- has_many :through の関連を解決する際に:
- すでに through チェーンの中で JOIN 済みのテーブルに対して、
- マージ対象のスコープ側でも
joinsが指定されている場合、 - その関連が
belongs_toまたはhas_one(= 単一レコードを返す関連)の場合には、- スコープ側の JOIN を「マージ前に」落としてしまう(削除する)。
- 一方、
has_manyなど「コレクションを返す JOIN」は削除しない。
理由: JOIN を1つ減らすと行数が変わる可能性があるため(結合の多重度に影響する)。
結果として、上記の SQL は次のように簡潔になります。
PR後:
SELECT "rubygems".* FROM "rubygems"
INNER JOIN "versions" ON "rubygems"."id" = "versions"."rubygem_id"
INNER JOIN "dependencies" ON "versions"."id" = "dependencies"."version_id"
WHERE "dependencies"."rubygem_id" = ?
AND "versions"."indexed" = ?
AND "versions"."position" = ?削除された versions_dependencies JOIN は、同じキーで同じテーブルを JOIN しているだけなので、行をフィルタしておらず、削除しても返るレコードは変わりません。
テスト・仕様面
activerecord/test/cases/associations/has_many_through_associations_test.rbにテストが追加され、#51259 のレポータが示したテストケースが通ることが確認されています。- 単純に結果が同じなだけでなく、「AST(Arelツリー)が、余計な JOIN を書かずに同じ関連を表現した場合と一致する」ことも検証しています。
activerecord/CHANGELOG.mdに動作変更として記載されています。
なぜアプリ側で JOIN を消せないのか
PR説明にもある通り:
Applications cannot just drop the joins themselves: incoming_dependencies needs it for its own where, and raises without it. It is only redundant once the chain supplies the same table.
つまり:
- スコープ単体で見ると、その
joinsはwhere句内で使っているため必須(消すとActiveRecord::StatementInvalidなどになる)。 - しかし「has_many :through のチェーン経由で同じテーブルが JOIN される場合」に限って、その JOIN は結果として冗長になる。
- この「冗長になるかどうか」の判定は、Active Record が内部の関連解決時にしか把握できないため、アプリケーションコード側で安全に消すのは難しい。
そこで、AssociationScope の中で「through チェーンによる JOIN 情報」と「スコープの JOIN 情報」を突き合わせ、冗長な単一レコードJOINだけを安全に落とすようにしている、という設計です。
- 影響範囲・注意点
- 対象:
has_many :through絡みで、関連のスコープ内にも同じテーブルへのbelongs_to/has_oneJOIN を記述しているコード。- また、そのスコープを
has_many :throughから利用しているケース。
- 期待される影響:
- 発行されるSQLのJOIN数が減り、無駄なJOINがなくなる。
- 結果セット(返るレコード)は変わらない想定。
なぜなら、削除対象は「同じキーで同じテーブルを結合している単一関連JOIN」であり、削除しても条件が変わらないから。
- 意図的に「行数を変えうる」コレクションJOIN(
has_many側)は削除しないため、includesや手動で行っている複雑なコレクションJOINには影響を与えない設計です。 - カスタムArelや、JOINエイリアス名に強く依存した高度なSQLハックをしている場合:
- 内部的に冗長JOINが減ることで、結果的に「エイリアス名が減る」などの副作用が出る可能性はありますが、通常のActive Record APIの範囲では問題は起こりにくいと考えられます。
- 参考情報 (あれば)
- このPRの元Issue: #51259
- has_many :through のスコープが冗長なJOINを生む問題の報告と再現ケース。
- 変更ファイル:
activerecord/lib/active_record/associations/association_scope.rb
→ JOINマージ時のロジックが追加。activerecord/test/cases/associations/has_many_through_associations_test.rb
→ 冗長JOINが取り除かれること、およびASTが同一になることを確認するテスト。activerecord/CHANGELOG.md
→ 振る舞い変更としての記載。
#58622 Avoid preloading nil converted association keys
マージ日: 2026/9/1 | 作成者: @ThrwatElmo
- 概要 (1-2文で)
Rails の Active Record プリロード処理で、関連の外部キーがnilの場合に余計なクエリが発行されていた問題を修正し、nilキーを正しくスキップするようにした PR です。外部キー/主キーの型が異なり、プリローダがキーを文字列化するケースでも、nilは空文字列に変換せずnilのまま保持します。
- 変更内容の詳細
問題の背景
- Active Record のプリローダ (
ActiveRecord::Associations::Preloader) は、関連付けの外部キーと主キーの型が異なるとき、両方のキーを文字列化してマッチングします。 - その際、オーナー側のキーが
nilだった場合も文字列化され、""(空文字列)になっていました。 - Ruby では空文字列は truthy なので、
owners_by_key(キーごとにオーナーをまとめたハッシュ)に「キー:""」として登録されてしまいます。 - その結果、実際には関連キーが存在しない(
nil)レコードに対してもプリロード用クエリが発行されるが、クエリの結果は当然ヒットせず、関連は最終的にnilになるだけ、という「無駄なクエリ」が発生していました。
今回の修正内容
Preloader::Association#convert_key の挙動を変更:
変更前(概念的なイメージ):
rubydef convert_key(key) key.to_s # nil => "" end変更後(概念的なイメージ):
rubydef convert_key(key) return nil if key.nil? key.to_s end
要点:
- キー変換が必要な場合でも
nilはnilのまま残す。 nil以外のキーは従来通りto_sで文字列化する。
これにより、既存のガードロジック:
owners_by_key[key] # などで「キーが存在するか」を見ている処理において、nil キーを持つオーナーは正しく除外されるようになります(型変換が不要なケースではもともとそうなっていた挙動と揃う)。
テストと関連調整
- 既存の「文字列の外部キーが整数の主キーを指す」関連を用いたリグレッションテストを追加し、
nilの外部キーを持つレコードについて余計なクエリが走らないことを検証。
- 既存の「クエリのグルーピング(条件の異なる 2 クエリになること)」をテストしていたケースは、今までは暗黙的に
nilキー由来の不要なクエリに依存していたため、- 明示的に外部キーを設定して「2 種類の scope のクエリが発行される」ことをテストするように修正。
また、CHANGELOG にバグ修正として追記されています。
- 影響範囲・注意点
- 対象: Active Record の関連プリロード(
includes,preload,eager_load等)で、- 外部キーと主キーの型が異なり、
- プリロー ダ側でキー変換(文字列化)が行われるケース。
- 影響:
- これまで「外部キーが
nilなのにプリロードクエリが飛んでいた」パターンで、余計なクエリが出なくなるため、若干のパフォーマンス改善・無駄な DB 負荷軽減が見込めます。 - 検索結果そのもの(関連が
nilになる/ならない)はもともと正しかったため、アプリケーションの表向きの挙動は基本的に変わりません。
- これまで「外部キーが
- 互換性:
- アプリ側で「なぜか 1 本余計なクエリが走っている」ことに依存していたようなコードがない限り、互換性問題はほぼありません。
- 監視やテストで「発行クエリ数」を厳密に見ている場合は、関連のプリロードでクエリ数が減ることによりテストが変化する可能性があります(N+1 検出用のメトリクスなど)。
- 参考情報 (あれば)
- 対応ファイル:
activerecord/lib/active_record/associations/preloader/association.rbactiverecord/test/cases/associations_test.rbactiverecord/CHANGELOG.md
- テスト状況:
ARCONN=sqlite3 bin/test test/cases/associations_test.rb- 137 runs, 525 assertions, 0 failures, 0 errors, 0 skips
- RuboCop チェックも問題なしとのこと。
#58626 Optimize EventReporter::LogSubscriber#subscription_filter
マージ日: 2026/9/1 | 作成者: @byroot
- 概要 (1-2文で)
EventReporter::LogSubscriber#subscription_filterの実装を見直し、イベント名から namespace を抽出するのではなく、namespace から prefix を組み立てる形に変更することで、処理を単純化・最適化した PR です。挙動は従来と同等で、主に内部実装の整理・パフォーマンス改善が目的です。
- 変更内容の詳細
※ 実際の diff は 2 行追加・6 行削除と小規模ですが、概要説明に基づくとロジックの方向性が変わっています。
従来:
- 「イベント名(
"render.action_view"のような文字列)から namespace を切り出す」処理をしていた。 - 例えば
event.name.split(".").lastのような形で"action_view"部分を取り出してフィルタに使っていた、というイメージです。
今回の変更:
- 「namespace(
action_viewなど)から prefix("action_view."のような文字列)を組み立てる」アプローチに変更。 - イベント名 → namespace の抽出処理をなくし、namespace を元に subscription 用の prefix を作ることで、不要な文字列処理を削減しています。
イメージ的なコード変化は以下のようなものです(擬似例・イメージであり実際のコードではありません):
# 変更前(イメージ)
def subscription_filter(event)
# event.name: "render.action_view"
namespace = event.name.split(".").last # => "action_view"
# namespace を元に何らかの条件判定
namespace == expected_namespace
end
# 変更後(イメージ)
def subscription_filter(event)
# 事前に namespace から prefix を作る
prefix = "#{expected_namespace}."
# event.name: "render.action_view"
event.name.start_with?(prefix)
endポイント:
- 「イベント名から namespace を抽出する」ための split などの処理が不要になる。
- フィルタ条件として単純な
start_with?のような prefix マッチが使える。 - これにより、処理コストとコードの複雑さが下がる。
EventReporter::LogSubscriber は ActiveSupport のイベントレポート機能に紐づいたログ購読処理なので、この最適化はイベント購読フィルタが多く呼ばれるケースでじわっと効く可能性があります。
- 影響範囲・注意点
外部 API の挙動は原則として変わらない
PR の説明からは、あくまで内部実装の最適化であり、EventReporter::LogSubscriberの public API や利用方法を変えるものではありません。イベント名の構造前提は維持されている
「namespaceから prefix を構築する」ということは、イベント名が"something.namespace"のような形式になっている前提が引き続きあります。
既存の ActiveSupport インストゥルメンテーションの命名規則に乗っていれば問題はありません。独自拡張している場合は注意
EventReporter::LogSubscriber#subscription_filterを直接 monkey patch している、- あるいは EventReporter 周りの内部実装に強く依存したコードを書いている
といったケースでは、この変更を取り込んだ際に挙動を一度確認することをおすすめします。
パフォーマンス面のわずかな改善が期待される
特に大量のイベントが流れる本番環境などで、文字列 split / 解析が減る分、オーバーヘッドが僅かに減ります。ただし変更規模からして「劇的な改善」ではなく、地味な最適化レベルです。
- 参考情報 (あれば)
- 該当クラス:
ActiveSupport::EventReporter::LogSubscriber- ファイル:
activesupport/lib/active_support/event_reporter/log_subscriber.rb
- ファイル:
- 関連ドキュメント:
- ActiveSupport インストゥルメンテーション:
https://api.rubyonrails.org/classes/ActiveSupport/Notifications.html - ログサブスクライバ (LogSubscriber):
https://api.rubyonrails.org/classes/ActiveSupport/LogSubscriber.html
- ActiveSupport インストゥルメンテーション:
この PR はその LogSubscriber の一種である EventReporter 用実装の内部ロジックを整理・最適化するもので、Rails アプリケーションコード側の修正は基本的に不要です。
#58303 Require concurrent before using in ActionCable
マージ日: 2026/9/1 | 作成者: @josevalim
- 概要 (1–2文で)
Action Cable を単体で読み込んだときにクラッシュしていた問題を、必要なconcurrent系モジュールを明示的にrequireすることで解消した PRです。Action Cable を Rails 本体経由ではなく、直接ロードして使うケースでの初期化エラーを防ぐためのバグフィックスです。
- 変更内容の詳細
何が問題だったか
PR の説明にある再現コード:
ruby -Iactivesupport/lib -Iactioncable/lib \
-e 'require "action_cable"; ActionCable.server.executor'のように、Rails 全体ではなく activesupport と actioncable だけを -I で指定して require "action_cable" した場合に、ActionCable.server.executor の呼び出しでクラッシュしていました。
原因は、Action Cable 内部で concurrent-ruby(ActiveSupport::Concurrency / Concurrent::Map など)に依存しているにもかかわらず、Action Cable 単体ロード時にはその依存を自前で require していなかったことです。Rails アプリ全体を require "rails/all" 等で読み込む場合は、他のコンポーネントが concurrent を読み込むため問題が表面化しませんでした。
実際の変更点
変更ファイルは 2 つで、それぞれ 1 行の require が追加されています。
actioncable/lib/action_cable/channel/base.rbactioncable/lib/action_cable/server/base.rb
PR タイトルの「Require concurrent before using in ActionCable」から推測される通り、これらのクラスが concurrent ベースの機能(スレッドセーフなデータ構造や executor 等)を使う前に、明示的に:
require "active_support/concurrent" # もしくはそれに準ずる concurrent 関連ファイルのような行が追加されています。
イメージとしては以下のような差分です(実際のファイル名やパスは PR 依存ですが、概念的にはこのような追加です):
# actioncable/lib/action_cable/server/base.rb
# 追加された行
require "active_support/concurrent"
module ActionCable
module Server
class Base
# ここで executor などの concurrent な仕組みを利用
end
end
end同様に、ActionCable::Channel::Base でも concurrent な構造(例: connection の管理や subscription の状態保持など)を使う前に require を追加しています。
これにより、Rails 以外の環境や、ミニマルなロードパスで Action Cable を利用した際にも、必要な concurrent 機能が確実に読み込まれるようになります。
- 影響範囲・注意点
- 影響範囲
- 主に「Action Cable をライブラリとして単体利用する」「最小限の gem ロードで動かす」ような環境に影響します。
- 例:
require "action_cable"だけをして WebSocket サーバ機能を組み込むようなユースケース
- 例:
- Rails フルスタック(
rails newで生成された通常のアプリ)では、元々別コンポーネント経由でconcurrentが読み込まれていたため、挙動はほぼ変わりません。
- 主に「Action Cable をライブラリとして単体利用する」「最小限の gem ロードで動かす」ような環境に影響します。
- 互換性
- 既存機能の挙動を変えるものではなく、「今までクラッシュしていたパスが動くようになる」方向の変更です。
- 追加されたのは
require行のみであり、API 変更や挙動変更はありません。
- パフォーマンス
concurrent-ruby関連のrequireが Action Cable ロード時に確実に走るようになるため、Action Cable 初回ロード時のオーバーヘッドがごくわずかに増えますが、実用上は無視できるレベルです。
- 参考情報 (あれば)
- PR 番号: https://github.com/rails/rails/pull/58303
- 類似の問題は、「コンポーネント単体ロード時に他コンポーネントへの暗黙の依存が崩れる」パターンで発生しがちです。Rails コンポーネントを単体 gem として利用する場合は、「自分のコンポーネント内で使っている外部モジュールは明示的に
requireする」ことを意識しておくと同種のバグを防げます。
#58620 Make some ActionView::Base settings shareable
マージ日: 2026/8/31 | 作成者: @etiennebarrie
- 概要 (1-2文で)
ActionView::Baseが持ついくつかの設定値を Ractor 間で共有可能(shareable)にし、Ractor を使ったビュー描画が Ruby 4.0 でも問題なく行えるようにする変更です。クラス変数ベースのcattr_accessorを、シングルトンクラス上のattr_accessorに置き換えることで、Ractor の shareable 制約に対応しています。
- 変更内容の詳細
主な目的
- Ractor 上で
ActionView::Baseを使ってビューをレンダリングできるようにするため、ActionView::Baseの「設定値」を Ractor-shareable な形で保持する。 - 具体的には、以下の設定を Ractor から安全に読める/セットできるようにする:
automatically_disable_submit_tagremove_hidden_field_autocompletedefault_formatsfield_error_proc
実現方法
1) cattr_accessor からシングルトンの attr_accessor への変更
今まで:
class ActionView::Base
cattr_accessor :automatically_disable_submit_tag, default: true
cattr_accessor :remove_hidden_field_autocomplete, default: true
cattr_accessor :default_formats
cattr_accessor :field_error_proc
endのように、クラス変数 (@@var) を内部的に使う cattr_accessor を利用していた部分を、
class ActionView::Base
class << self
attr_accessor :automatically_disable_submit_tag,
:remove_hidden_field_autocomplete,
:default_formats,
:field_error_proc
end
endのように、シングルトンクラスのインスタンス変数 (@var) を使う attr_accessor に変更しています。
ポイント:
cattr_accessorが定義していた「インスタンスメソッド側のアクセサ」は使われていない、という前提に立っているため、この変更は既存の呼び出し(クラスメソッドとしての呼び出し)には影響しない。- Ractor の shareable 制約的に、クラス変数よりもクラスインスタンス変数の方が扱いやすく、今回の実装方針に合致する。
2) 各設定値ごとの shareable 性の扱い
automatically_disable_submit_tagremove_hidden_field_autocomplete
これらは bool 値 (true/false) で運用されており、そのままでも shareable。
※ 型をより厳密に true/false に縛ることも検討できるが、現状はそこまでしていない、という位置づけ。
default_formats- ブート時にミュータブルに設定されるが、
Mime.eager_load!によって shareable な状態になるよう保証される。 - Rails 起動後は、Ractor 間で読み取り可能な不変(or 実質不変)の配列/構造として扱える前提。
- ブート時にミュータブルに設定されるが、
field_error_proc- Proc オブジェクトであり、デフォルト値および設定時に shareable にしておく必要がある。
- この PR では、ロード時に設定されるデフォルトの
field_error_procを shareable な形で保持し、さらにユーザーが値を上書きする際も shareable であることを満たすようにしています(テストでその前提を検証)。
3) Ractor 向けのテスト追加
actionview/test/template/base_ractor_test.rb が新規追加・拡張され、以下を検証:
- Ractor 内で
ActionView::Baseを使ったレンダリングが行えること。 - Ractor 内から
automatically_disable_submit_tag,remove_hidden_field_autocomplete,default_formats,field_error_procにアクセスしてもエラーにならないこと。 field_error_procを Ractor 上で評価しても動作すること(shareable な Proc であることの確認)。
全体として、「Ractor 上から ActionView::Base の設定値にアクセスしても、shareable 制約に引っかからず動作する」ことを CI テストで保証しています。
- 影響範囲・注意点
既存 API との互換性
- 公開インターフェースとしてはクラスメソッド (
ActionView::Base.default_formatsなど) のままなので、基本的にアプリケーションコードに対する互換性は高いです。 - ただし、もし内部実装に依存して「クラス変数
@@fooを直接読む/書く」ようなメタプログラミングをしている場合は壊れます(@@ではなくクラスインスタンス変数@に変更されるため)。
- 公開インターフェースとしてはクラスメソッド (
Ractor を使わない場合
- Ractor を使わない通常の Rails アプリでは、挙動上の違いはほぼありません。
cattr_accessorが生成していたインスタンスメソッド (view = ActionView::Base.new; view.default_formatsのような呼び出し) がもし暗黙に使われていた場合は注意が必要ですが、PR 説明によるとこのインスタンス側アクセサは使われていない前提で変更されています。
Ractor を使う場合
field_error_procをカスタマイズする場合、その Proc が Ractor shareable である必要があります。- キャプチャしている外部変数や、閉じているオブジェクトが Ractor shareable でないとエラーになる可能性があります。
- シンプルなラムダ/Proc、凍結されたオブジェクトのみを参照する Proc にしておくのが安全です。
default_formatsに対し、ブート後にミュータブルな変更(破壊的な<<,map!など)を行うと、Ractor 間共有との整合性が崩れる可能性があるため、基本的には「起動時に確定させ、その後は不変として扱う」想定です。
Ruby バージョンとの関係
- Ruby の「次のバージョン」ではクラス変数からの shareable 値の読み取りサポートが入る予定だが、この PR により「Ruby 4.0 でも問題なく動く」よう、先行してクラスインスタンス変数+Ractor shareable 設計にしている、という位置づけです。
- 参考情報 (あれば)
- Ractors と shareable オブジェクトについて(Ruby 3 以降の仕様)
- Ractor 内で共有できるのは「shareable」なオブジェクトに限られ、基本的に:
- 不変オブジェクト(凍結された文字列、数値、シンボルなど)
- 特定のルールで構成された凍結済みの配列・ハッシュなど
- shareable な Proc(Env が shareable)
- それ以外は Ractor 間に送れない/参照できない。
- Ractor 内で共有できるのは「shareable」なオブジェクトに限られ、基本的に:
- この PR: rails/rails #58620 「Make some ActionView::Base settings shareable」
(ActionView で Ractor 対応を進めるための一環と見なせます)
#58606 Fix encrypted fixtures for JSON columns
マージ日: 2026/8/31 | 作成者: @carlosdanielpohlod
- 概要 (1-2文で)
config.active_record.encryption.encrypt_fixtures = trueを有効にした状態で、json/jsonbカラムをencryptsしている属性のフィクスチャ読込時に発生していた復号エラー(#48601)が、暗号化フィクスチャのシリアライズ処理を修正することで解消されています。暗号化済みペイロードが JSON カラム用に二重シリアライズされていたのを防ぐパッチです。
- 変更内容の詳細
問題の背景
前提:
config.active_record.encryption.encrypt_fixtures = true- モデルで
encrypts :some_attrを宣言 some_attrが DB 上ではjson/jsonbカラムであるケース
これまでの挙動:
EncryptedFixturesがフィクスチャ値を暗号化し、「暗号化済み文字列」をフィクスチャ値として扱う。- フィクスチャを DB に流し込む
build_fixture_sqlが、全カラム値に対してcolumn.typeに応じた型シリアライズ (column.cast_type.serialize) を行う。 json/jsonbカラムの場合:Type::Jsonが値を JSON 文字列にエンコードするため、すでに暗号化済みの文字列がさらに JSON 文字列として包まれる。- 結果として DB には
"{\"p\":...}"のような「JSON 文字列としての暗号文」が保存される。
- レコード読込時、Active Record の復号処理は「JSON オブジェクトの中身」を期待しているのに、実際には「JSON カラムの中に入ったただの文字列」が渡されるため、
ActiveRecord::Encryption::Errors::Decryptionが発生していた。
文字列カラム (
string) はどうだったか:Type::Stringのシリアライズは「文字列をそのまま返す(恒等写像)」なので、暗号化済み文字列をさらに壊さず、そのまま保存されて問題が表面化しなかった。
今回の修正方針
EncryptedFixturesnow hands the payload back in the column's cast form (column.cast_type.deserialize), sobuild_fixture_sqlserializes it once and stores the same JSON object a regular save would.
ポイントは「EncryptedFixtures がフィクスチャ値として返すオブジェクトの型」を、各カラム型の「キャスト済みオブジェクト」に揃えたことです。
変更前(概念的な流れ):
EncryptedFixtures:- フィクスチャの「論理値」 → 暗号化 → 「暗号化済み文字列」をそのまま返す
build_fixture_sql:- 受け取った文字列を
column.cast_type.serializeに通す jsonカラムなら JSON 文字列化され、結果として二重ラップされる
- 受け取った文字列を
変更後(概念的な流れ):
EncryptedFixturesが暗号化ペイロードを、そのカラムのcast_typeに対する deserialize 済み の形にして返す:column.cast_type.deserialize(encrypted_payload)
build_fixture_sqlは、いつも通りcolumn.cast_type.serializeを一度だけ呼ぶ。- その結果、
json/jsonbカラムでは、通常の save と同じ JSON オブジェクト構造が DB に保存されるようになる。
ここでのキモは:
- フィクスチャ → 暗号化 →
column.cast_type.deserialize→build_fixture_sqlでserialize - という 「deserialize → serialize の 1 往復」 によって、「通常のモデル保存と同じ型変換ルート」を通るようにしたことです。
コードレベルの変更点(概要)
activerecord/lib/active_record/encryption/encrypted_fixtures.rb
- 暗号化済みペイロードを返す処理で、生の文字列を返さず、カラムの
cast_type.deserializeを通した値を返すように変更。 - これにより、フィクスチャローダ側は「既にカラム型としてキャスト済みの値」として扱える。
テスト関連:
activerecord/test/cases/encryption/encrypted_fixtures_test.rb- JSON カラムを
encryptsしたモデルのフィクスチャが、エラーなく読み書きでき、かつ期待どおりの JSON 形式で保存されることを確認するテストを追加。
- JSON カラムを
activerecord/test/fixtures/encrypted_book_with_json.yml- 暗号化 JSON カラム用のフィクスチャを追加。
activerecord/test/models/book_encrypted.rb- JSON カラムを持ち
encryptsを宣言したモデルを追加。
- JSON カラムを持ち
activerecord/test/schema/schema.rb- 上記モデル用に JSON カラムをスキーマに追加。
- 影響範囲・注意点
影響を受けるケース:
Active Record Encryptionを使用し、config.active_record.encryption.encrypt_fixtures = trueを有効にしており、encrypts対象の属性が DB 上でjson/jsonbカラムになっている場合。
この PR による挙動の変化:
- これまで:
- 暗号化 JSON カラムのフィクスチャ読み込み時に
ActiveRecord::Encryption::Errors::Decryptionが発生し得た。 - DB 内のデータ形式が
"\"{...}\""のような形になる可能性があった。
- 暗号化 JSON カラムのフィクスチャ読み込み時に
- これから:
- 復号エラーが解消される。
- DB に保存されるデータ形式が「通常の保存時」と同じ JSON 構造になる。
- これまで:
既存データへの影響:
- すでに「二重シリアライズされた状態」で DB に入っているデータは、この修正で自動変換されるわけではありません。
- そうしたデータがあれば、以下のような対応を検討する必要があります:
- データをダンプして再インポートする
- 手動/スクリプトで値を正しい JSON 形式に変換する
- ただし、これは「テスト用フィクスチャ」の利用が主なケースであり、本番 DB に影響することは少ないはずです。
文字列 / バイナリカラム:
- PR の説明にもある通り、
string/binaryカラムは「serialize / deserialize が恒等変換」であるため、挙動は従来と変わりません。
- PR の説明にもある通り、
カスタム型:
json/jsonb以外にも、独自のcast_typeを定義しているカラムがある場合、EncryptedFixtures は「そのカスタム型の deserialize → serialize」のラウンドトリップを通るようになります。- 通常は「通常保存と同じ挙動になる」ので望ましい変更ですが、もし独自型がフィクスチャ処理を前提に特殊な実装をしている場合は挙動が変わる可能性があります(かなりレアケース)。
- 参考情報 (あれば)
関連 Issue:
- #48601 – JSON / JSONB カラム +
encrypt_fixtures = trueでの復号エラー報告
- #48601 – JSON / JSONB カラム +
関連設定:
config.active_record.encryption.encrypt_fixtures = true- テストフィクスチャをロードする際に、暗号化対象属性を自動で暗号化してから DB に保存するための設定。
実務的な確認ポイント:
- 暗号化 JSON カラムを使っている場合、Rails をこの修正を含むバージョンに上げた後、次を確認すると安全です:
- フィクスチャロード(
rails test,rails db:fixtures:load等)がエラーなく完走すること - DB に保存されている JSON カラムのフォーマットが期待どおりであること(
"{\"p\":...}"のような「さらにクォートされた文字列」になっていないこと)
- フィクスチャロード(
- 暗号化 JSON カラムを使っている場合、Rails をこの修正を含むバージョンに上げた後、次を確認すると安全です:
#58612 Fix NameError when ActiveRecord::Coders::JSON is given encode options
マージ日: 2026/8/31 | 作成者: @lazerg
- 概要 (1-2文で)
ActiveRecord のActiveRecord::Coders::JSONにencode_optionsを渡したときにNameErrorが発生していた回 regresson を修正した PRです。イニシャライザ引数名のリネーム漏れによるバグを直し、encode オプション付きの利用を担保するテストが追加されています。
- 変更内容の詳細
バグの内容
以下のようなコードを書くと:
coder = ActiveRecord::Coders::JSON.new(encode_options: { escape: true })実行時に:
NameError: undefined local variable or method `options'というエラーが発生していました。
原因は、以前の PR #58601 で ActiveRecord::Coders::JSON のイニシャライザ引数が
def initialize(options = {})のような形から、キーワード引数 encode_options: に変更された一方で、内部でデフォルト値とマージするコードがまだ options という古い変数名を参照していたためです。
イメージとしては、元がこんな感じだったのに対し:
def initialize(encode_options: {})
@encode_options = DEFAULT_ENCODE_OPTIONS.merge(options) # ← ここが古い名前
endoptions というローカル変数が存在しないため NameError になっていました。
修正内容
この PR では、そのマージ処理を正しく encode_options に合わせるように 1 行だけ修正しています。
@encode_options = DEFAULT_ENCODE_OPTIONS.merge(encode_options)これにより、以下のような「デフォルトの JSON エンコードオプションに対して、呼び出し側で上書きする」という本来の挙動が復活します。
coder = ActiveRecord::Coders::JSON.new(
encode_options: { escape: true }
)
# DEFAULT_ENCODE_OPTIONS をベースに { escape: true } が上書きされるテストの追加
これまでのテストは:
- デフォルトのエンコードオプションでの動作
- デコード処理側
のみをカバーしており、「encode_options を利用して JSON コーダを生成するケース」がテストされていませんでした。
そこで、この PR では activerecord/test/cases/coders/json_test.rb に、新たに encode_options を指定した場合のテストが追加されています。内容としてはおおむね:
ActiveRecord::Coders::JSON.new(encode_options: {...})が例外を出さずに初期化できること- そのオプションが内部に反映されていること
を確認するものです。
- 影響範囲・注意点
- 対象ブランチ:
mainおよび8-1-stableにのみ存在する regression であり、まだリリース版には入っていないため、現時点の正式リリースを使っているアプリには影響しません。 - 影響範囲:
ActiveRecord::Coders::JSONを 直接 使い、かつencode_options:キーワード引数を渡しているコードが対象です。- Rails 内部でこのクラスを使っている箇所については、encode_options を明示的に指定していなければ影響はありません。
- 挙動の変化:
- 既存の挙動(デフォルトオプション+呼び出し側オプションのマージ)が本来のとおりに復活するだけで、API 仕様の変更や互換性破壊はありません。
- 注意点:
- まだ未リリースのため CHANGELOG には記載されていません。
main/8-1-stableを追いかけている場合は、この PR を取り込むか、該当期間のコミットで回避されているか確認する必要があります。 - 自前で monkey patch している場合は、この修正と二重にパッチを当てていないか確認してください。
- まだ未リリースのため CHANGELOG には記載されていません。
- 参考情報 (あれば)
- 該当 Issue: #58609 (
ActiveRecord::Coders::JSON.new(encode_options: ...)でのNameError報告) - 関連 PR: #58601 (
options→encode_optionsへのイニシャライザ変更を行った PR。今回の regression の原因)