Ruby on Rails PR Digest - 2025年 12月
このページは rails/rails リポジトリにマージされたPull Requestを自動的に収集し、AIで要約したものです。
#56208 Fix bug when txn isolation would not reset or when requires_new is set
マージ日: 2025/12/6 | 作成者: @kirs
- 概要 (1-2文で)
このPRは、トランザクションの分離レベル (isolation level) を一時的に変更した後に元に戻らないバグと、requires_new: trueなネストトランザクションで isolation 指定が正しく扱われないバグを修正しています。Active Record のトランザクション分離レベルを多用するアプリケーション(例: Shopify)のワークフローに直接影響する問題へのピンポイント修正です。
- 変更内容の詳細
※実際のコード全文は省略しますが、PR説明と diff 情報から復元できる挙動レベルで解説します。
背景: #55549 での変更漏れ
#55549 で導入された「トランザクションの isolation レベルをブロック単位で変更する」機能において:
- ブロック実行後に
current_transaction.isolationを元の値に戻していなかった requires_new: trueのネストトランザクション時に isolation 指定を正しく扱っていなかった
という2点のバグが発生していました。
修正ポイント1: isolation レベルのリセット
ActiveRecord::ConnectionAdapters::DatabaseStatements#transaction(あるいはそれを内部で呼び出すヘルパー)の中で、
「トランザクション開始前の isolation」を退避し、ブロック終了後に必ず元の isolation を復元するように修正されています。
イメージとしては、以下のような処理になります:
def transaction(**options, &block)
# もともとの isolation を保存
previous_isolation = current_transaction.isolation
# options[:isolation] が指定されていれば、その isolation でトランザクション開始
# ...
yield
ensure
# ブロック終了後に isolation を元の値に戻す
current_transaction.isolation = previous_isolation
endこれにより、以下のようなケースで isolation が「後続のトランザクションにも引き継がれてしまう」という誤動作が解消されます。
# 例: これまでは tx2 も :serializable のままになってしまうバグがあった
User.transaction(isolation: :serializable) do
# ここだけ serializable を想定
...
end
User.transaction do
# 本来はデフォルトの isolation (DB の設定) を期待
...
endPR後は、tx1 の isolation 指定は tx1 のスコープだけに閉じるようになります。
修正ポイント2: requires_new: true での isolation の扱い
requires_new: true を指定したネストトランザクションに isolation を渡した場合のハンドリングも修正されています。
典型例:
User.transaction do
# outer transaction: デフォルト isolation
User.transaction(isolation: :serializable, requires_new: true) do
# inner transaction: serializable
...
end
# outer transaction: isolation は変わらず(デフォルトのまま)
end従来のバグでは、
- inner トランザクションで指定した isolation が
current_transactionに残り、outer に影響する - そもそも inner の isolation 指定が正しく効かない
といった挙動が起こりうる状態でした。
このPRでは、
requires_newの inner トランザクションについても isolation が正しく設定される- inner の終了後に、outer トランザクションの isolation に確実に戻す
というロジックが追加されています。
テストの追加
activerecord/test/cases/transaction_isolation_test.rb に8行のテストが追加されており、少なくとも以下のようなシナリオをカバーしていると考えられます:
- isolation を指定したトランザクションの後、isolation 未指定のトランザクションを開始すると元の isolation に戻っていること
requires_new: trueのネストトランザクションで isolation を指定しても、外側の isolation は変化しないこと
これにより、前述の2つのバグが再発しないことを保証しています。
- 影響範囲・注意点
影響範囲
- Active Record のトランザクション API を使い、特に
isolation:オプション・requires_new: trueを併用しているコードが対象です。 transactionを素朴にtransaction do ... endとだけ使っているアプリケーションへの影響はほぼありません(isolation を明示指定していなければ振る舞いは DB デフォルトのまま)。
アプリケーション側で変わる可能性がある挙動
「意図せず高い isolation が引き継がれていた」ケースが正常化される
以前は、あるブロックでisolation: :serializableを指定すると、その後のトランザクションでも serializable が継続してしまっていた可能性があります。
これを前提にしていた(=バグ依存)コードがあると、以降のトランザクションの isolation が「元に戻る」ことで挙動が変わります。requires_newネスト時の isolation がより直感的になるrequires_new付きの中で isolation を変えると、その効果は inner トランザクションの中だけに留まり、outer には漏れなくなります。
これにより、想定していた isolation 境界(outer: read committed / inner: serializable など)が、実装通りに機能するようになります。
注意点
- 「特定の処理以降、アプリ全体のトランザクション isolation が変わる」という挙動に気づかずに依存していた場合、パフォーマンス特性やデッドロック発生率が変わる可能性があります。
isolation を明示指定している部分があれば、その前後のトランザクションの挙動を確認した方が安全です。 - マルチDB構成やカスタム connection handler を使っている場合も、
current_transaction単位で isolation がきちんとスコープされるようになるため、 isolation まわりの暗黙的な前提がないかを確認してください。
- 参考情報 (あれば)
- このPRで修正している元の変更:
- 同じ著者による、より大きな関連 PR(今回よりスコープが広いもの):
- 対象ファイル:
activerecord/lib/active_record/connection_adapters/abstract/database_statements.rbactiverecord/test/cases/transaction_isolation_test.rbactiverecord/CHANGELOG.md(バグ修正として明記)
このPRは、小さなコード変更で isolation のスコーピングを正す「バグフィックス」であり、API 仕様の変更というよりは、ドキュメントや直感に沿った挙動への修正、と考えると理解しやすいです。
#56299 Fix typos in command line guide
マージ日: 2025/12/5 | 作成者: @Saidbek
概要 (1-2文で)
Rails ガイド「Command Line」のドキュメント中の軽微なタイポ(重複語・スペース抜け・重複行)を修正した PR です。コードや挙動の変更はなく、文章表現のみの修正です。変更内容の詳細
guides/source/command_line.mdに対して、以下3点の修正が行われています。
文中の「with the the
bin/rails」という表現から、重複していた "the" を1つ削除- 修正前:
with the the \bin/rails`` - 修正後:
with the \bin/rails``
- 修正前:
assets:clobberに関する記述が同じ内容で2回連続していたため、重複行を削除- 例:
- 修正前(イメージ)md
bin/rails assets:clobber bin/rails assets:clobber - 修正後md
bin/rails assets:clobber
- 修正前(イメージ)
- 例:
「The
bin/rails」のように "The" とバッククォートの間のスペースが抜けていた箇所を修正- 修正前:
The\bin/rails`` - 修正後:
The \bin/rails``
- 修正前:
いずれもガイドの可読性・整合性を高めるための、純粋な文章レベルの修正です。
- 影響範囲・注意点
- 対象はドキュメントガイドのみであり、Rails 本体のコード、挙動、API には一切影響がありません。
- コマンドの使い方や意味は変わっておらず、既存アプリケーションやツールチェーンへの影響もありません。
- ドキュメント参照時に、より正確・自然な英語で読めるようになります。
- 参考情報 (あれば)
- PR: https://github.com/rails/rails/pull/56299
- 関連ガイド: 「Command Line」(Command Line Tools and bin/rails に関する公式ガイド)
#56213 Respect config.log_level to emit debug events
マージ日: 2025/12/5 | 作成者: @claudiob
- 概要 (1-2文で)
Rails の「デバッグイベントを出すかどうか」の判定条件を、環境名(development かどうか)ではなくconfig.log_levelに基づくように変更する PR です。これにより、RAILS_LOG_LEVEL=debugを設定すれば test や production でもデバッグイベントを出力できるようになります。
- 変更内容の詳細
変更点の本質
以前の PR #55657 では、「開発環境 (development) のときだけデバッグイベントを有効にする」という実装になっていました。この PR では、その条件を:
Rails.env.development?から:
config.log_level == "debug"に置き換えています(実際のコードは railties/lib/rails/application/bootstrap.rb 内の 1 行差し替えのみ)。
Rails ガイドにもある通り、デフォルトでは:
- 全体のデフォルト:
:debug - 生成された
config/environments/production.rbのデフォルト:config.log_level = :info
となっているため、デフォルト構成では従来と動作はほぼ同じ(本番では debug イベントは出ない)ですが、config.log_level を :debug に変えた環境ではデバッグイベントが有効になるよう変わります。
どうやって有効にするか(サンプル)
たとえば production でもデバッグイベントを見たい場合は、以下のどれかのように設定できます。
1. 環境変数で指定(推奨されている使い方)
RAILS_LOG_LEVEL=debug bin/rails serverRAILS_LOG_LEVEL は Rails が config.log_level に反映するので、この PR により debug イベントが有効になります。
2. 設定ファイルで直接指定
config/environments/production.rb:
Rails.application.configure do
config.log_level = :debug
endこれにより production でも development 同様にデバッグイベントが出力されます。
- 影響範囲・注意点
環境に依存しない挙動になる
以前は「development だけ特別扱い」でしたが、今後は「ログレベルが debug かどうか」で決まります。- test / production で
config.log_level = :debugにしているプロジェクトでは、これまで出ていなかったデバッグイベントが新たに出る可能性があります。
- test / production で
デフォルト設定では互換性維持
生成直後の Rails アプリの典型的な設定では:- development:
log_level = :debug→ デバッグイベント「あり」(従来通り) - test: 通常は
:debug→ この PR により test でも debug イベントが出るようになっている可能性がある(以前は「development 限定」だった場合との差分) - production:
log_level = :info→ デバッグイベント「なし」(従来通り)
test 環境での挙動は、#55657 の実装内容や各プロジェクトの
config.log_level設定に依存します。
もし test でログが多すぎると感じる場合は、config/environments/test.rbのconfig.log_levelを:info以上に上げることで抑制できます。- development:
本番で debug を有効にする際の注意
- ログ量が非常に増える可能性があるため、ストレージ・ログローテーション・ログ集約基盤(例: ELK、Loki など)の負荷に注意が必要です。
- デバッグログに機微情報が含まれる場合、コンプライアンス上のリスクも増えます。production で
:debugを使う場合は、ログ内容の棚卸し・マスキングを検討してください。
- 参考情報 (あれば)
- この PR: https://github.com/rails/rails/pull/56213
- 元になった PR: https://github.com/rails/rails/pull/55657
- ログレベルの公式ドキュメント:
https://guides.rubyonrails.org/debugging_rails_applications.html#log-levels - 関連するユースケースの議論:
https://github.com/rails/rails/pull/55900#issuecomment-3445390380
#51238 Extract ActionText::Editor base class and ActionText::TrixEditor adapter
マージ日: 2025/12/5 | 作成者: @seanpdoyle
- 概要 (1–2文で)
- Action Text にエディタ共通の基底クラス
ActionText::Editorと、Trix 向けアダプタActionText::TrixEditorを導入し、「Trix 前提の API」を整理・非推奨化した PRです。 - これにより、将来的に Trix 以外のリッチテキストエディタを公式な拡張ポイントから統一的に統合できる土台が整えられています。
- 変更内容の詳細
2-1. ActionText::Editor 基底クラスとアダプタ層
新しく以下の構成が導入されています。
- 基底クラス:
ActionText::Editor - アダプタ:
ActionText::TrixEditor(Trix 用)
- 周辺クラス:
ActionText::Editor::RegistryActionText::Editor::Configurator
これらは「エディタごとの差異を Action Text 本体から切り離す」ためのアダプタ層です。
従来はクラス名やメソッド名、引数名に trix が直接現れていた箇所がありましたが、それらを editor ベースの抽象化に移し替えています。
主な責務の集約
PR 説明にある通り、多数のクラスメソッド・インスタンスメソッドが ActionText::TrixEditor へ集約されています。
多くは「元の実装をそのままコピー」したうえで、命名に含まれる trix を editor に置き換える程度の変更に留めてあり、振る舞いは最大限維持されています。
例イメージ(あくまで概念的なもの):
# 以前: Trix 前提のユーティリティが色々な場所に散らばっていた
ActionText::Content.foo_trix_html(...)
ActionText::TrixAttachment.some_trix_specific_behavior(...)
# 以後: エディタアダプタのメソッドとして集約
ActionText::TrixEditor.new(...).to_html(...)
ActionText::TrixEditor.new(...).convert_attachments(...)今後、他エディタ (Quill, CKEditor など) を統合する場合は ActionText::Editor を継承したアダプタクラスを実装し、Registry 経由で登録する形が想定されます。
2-2. エディタ登録用の Registry / Configurator
ActionText::Editor::Registry- エディタアダプタクラスを名前などで引けるようにするレジストリ。
- 「どのエディタ名がどのアダプタクラスに対応するか」を一元管理する役割。
ActionText::Editor::Configurator- インストール・初期化の段階で「どのエディタを既定にするか」などを設定するためのクラス。
- Rails 側の初期化 (engine) や generator (
install_generator.rb) から利用されることで、「アプリケーションとして Action Text をどのエディタと組み合わせるか」を決めやすくしています。
これらはまだ API としては「実装詳細扱い(private 的)」で、今後の拡張やドキュメント整備の余地を残しています。
2-3. Trix 依存コードの整理と非推奨化
PR 説明にある通り、2 つの種類の非推奨があります。
モジュール / クラスの非推奨
- Trix 特化、または内部用であるにもかかわらず
publicな定義だったクラス / モジュールが対象。 - 今回、責務の一部または全てを
ActionText::Editor/ActionText::TrixEditor側へ移譲し、旧クラス/モジュールは deprecate されたうえで、新実装経由で動くようにラップされているものがあります。 - ソース中では
:nodoc:が付いていなかったものの、実質 internal 想定の物が主な対象です。
- Trix 特化、または内部用であるにもかかわらず
メソッドの非推奨
- 名前や引数に
trixを含むもの、またはドキュメント / コメントがなく internal 想定と判断された public メソッドが対象。 - 可能な限り、旧メソッドは warning を出しつつ
ActionText::TrixEditorのメソッドを呼び出すだけの thin wrapper に変わっているため、既存コードは直ちに壊れずに移行が可能です。
- 名前や引数に
例(あくまでイメージ):
# 旧 API: trix 固有の名前
def trix_attachment_url(...)
ActiveSupport::Deprecation.warn("... use editor_attachment_url instead ...")
editor = ActionText::TrixEditor.new(...)
editor.attachment_url(...)
end
# 新 API: editor ベースの名前 (TrixEditor 内)
def attachment_url(...)
# 実装は従来とほぼ同じ
end2-4. 添付ファイル周りのリファクタ
変更ファイルから読み取れる範囲でのポイント:
actiontext/lib/action_text/attachments/conversion.rb追加- 添付ファイルの変換処理(HTML への変換、Trix 用の attributes 生成など)を共通化するためのモジュール/クラス。
- 従来
.../attachments/trix_conversion.rbにあった Trix 固有処理のうち、エディタ非依存にできる部分がこちらへ移されています。
actiontext/lib/action_text/attachments/trix_conversion.rb- 依然として存在しますが、内部でより汎用的な
attachments/conversionを利用する形に寄せられています。
- 依然として存在しますが、内部でより汎用的な
ActionText::Attachable,ActionText::Attachment,ActionText::Content等- エディタに依存しない添付ファイル・コンテンツの抽象化を維持しつつ、必要な箇所のみ新しいエディタアダプタを呼び出すようになっています。
2-5. Engine / Generator / テンプレートの更新
actiontext/lib/action_text/engine.rb- Engine 初期化時に
ActionText::Editor周り(Registry など)をセットアップする処理が追加。 - デフォルトで
ActionText::TrixEditorを登録する処理が含まれている形が想定されます。
- Engine 初期化時に
actiontext/lib/generators/action_text/install/install_generator.rb- インストール時の JS / ビュー / 初期化コード生成が、「Trix 固定」よりも「TrixEditor を使う構造」へと更新。
- 将来的にはジェネレータのオプションで他エディタを選択する拡張がしやすくなります。
actiontext/test/dummy以下の JS・View・Model- Dummy アプリ側も新しい Editor アダプタ構成に合わせてサンプルが更新されています。
- 例:
application.jsにおけるエディタ関連の初期化コード- メッセージフォーム (
messages/_form.html.erb) でのrich_text_area/ editor 選択の書き方
- 影響範囲・注意点
3-1. 一般的な Rails アプリ (通常の Action Text + Trix 利用)
rich_text_areaなど、通常の公開 API を使っているだけのアプリは基本的にそのまま動作します。- ただし、以下に当てはまる場合は deprecation warning が出る可能性があります。
ActionText::TrixAttachmentやActionText::Contentの Trix 依存メソッドを直接呼んでいるtrix_*を名前に含むメソッドをアプリケーション側で利用している
現時点では互換ラッパーがあるので即座に壊れることは少ないですが、ログの deprecation warning をチェックし、将来のメジャーバージョンでの削除を見越して 新しい Editor ベースの API への移行を検討した方がよいです。
3-2. エディタ連携を拡張しているライブラリ / エンジン作者
Action Text と外部エディタ(例: Quill, CKEditor)を連携する gem / plugin を書いている場合、この PR はかなり重要です。
- これまで Trix 専用の内部 API をハックしていた部分を、
ActionText::Editorのサブクラスとして正式に実装できる方向性が示されました。 - まだアダプタインターフェイスは「実装詳細扱い」ですが、今後ここが公式拡張ポイントとして明文化されていく流れが読み取れます。
- これまで Trix 専用の内部 API をハックしていた部分を、
新規実装の基本方針:
class MyCoolEditor < ActionText::Editorを定義- Registry に登録(
ActionText::Editor::Registry.register(:my_cool_editor, MyCoolEditor)のような形が想定される) - 添付ファイル変換・HTML 生成・ツールバー設定などを TrixEditor と同等のインターフェイスで実装
まだ細部仕様はコードを読まないと分からないため、この PR を足掛かりに actiontext/lib/action_text/editor/*.rb を確認する必要があります。
3-3. 内部 API への依存
- PR で「これは実装詳細であり、API としては private のつもり」と明言されているため、
ActionText::Editor一式も将来のバージョンで破壊的変更が入る可能性があります。 - ライブラリレベルでここに依存する場合は、バージョン制約やテストで追従コストを見込んでおくべきです。
- 参考情報
- PR: https://github.com/rails/rails/pull/51238
- 関連 Issue: https://github.com/rails/actiontext/issues/41
- 変更された主なファイル:
actiontext/lib/action_text/editor.rbactiontext/lib/action_text/editor/trix_editor.rbactiontext/lib/action_text/editor/registry.rbactiontext/lib/action_text/editor/configurator.rbactiontext/lib/action_text/attachments/conversion.rbactiontext/lib/action_text/attachments/trix_conversion.rbactiontext/lib/generators/action_text/install/install_generator.rbactiontext/lib/action_text/engine.rbactiontext/CHANGELOG.md
この PR 時点では「Trix 用アダプタを抽出しつつ、まだ Trix がデフォルト」であり、「他エディタを正式にサポートするための下準備」がメインテーマです。
#56236 [ci skip] Update getting_started.md to point to application.html.erb for navbar menu entry
マージ日: 2025/12/5 | 作成者: @Tretent
- 概要 (1-2文で)
Railsの「Getting Started」ガイド内で、ナビゲーションバーにログインメニューを追加する手順の記述ファイル名が誤っていた点を修正し、index.html.erbではなくapplication.html.erbを参照するように更新したドキュメント専用のPRです。アプリケーションコードや挙動には一切変更はありません。
- 変更内容の詳細(あればサンプルコードも含めて)
- 対象ファイル:
guides/source/getting_started.md - 変更点の主旨:
- ガイドの 11.3 節付近で、「
Loginリンクをindex.html.erbに追加する」と書かれていたが、実際にはナビゲーションバーが定義されているapplication.html.erbに追加するべきであるため、説明文中のファイル名をapplication.html.erbに修正。
- ガイドの 11.3 節付近で、「
- 変更されたのは Markdown 上の記述のみで、コード例やロジック自体が変わったわけではなく、「どのファイルを編集するのが正しいか」という参照先の修正です。
(サンプルイメージ: 実際のPR文面からの推定)
<!-- app/views/layouts/application.html.erb のナビバー例 -->
<nav>
<%= link_to "Home", root_path %>
<%= link_to "Login", login_path %>
</nav>従来のガイドでは、上記のようなナビゲーションを index.html.erb 側に書くかのような記述になっていたため、それを「レイアウトファイルである application.html.erb に書く」という正しい形に揃えています。
- 影響範囲・注意点
- 影響範囲:
- Rails Guides(ドキュメント)のみであり、Rails本体のコード、テスト、挙動には影響しません。
- Getting Started ガイドを見ながらチュートリアルを進めているユーザーにとって、ナビゲーションバーの編集箇所が正確になり、混乱が減ります。
- 注意点:
- 既に旧ガイドの指示通りに
index.html.erbにナビバーやログインリンクを追加していた場合は、レイアウト (application.html.erb) に移動するのが望ましい構造です。- レイアウトに置けば全ページで同じナビが共有されるため、チュートリアルの意図にも合致します。
- CHANGELOG は更新されていない通り、リリースノート的な追跡も不要な軽微なDocs変更です。
- 既に旧ガイドの指示通りに
- 参考情報 (あれば)
- PR本体: rails/rails#56236
- 関連コンセプト:
- Railsにおけるレイアウトファイル:
app/views/layouts/application.html.erb- 全ページ共通のヘッダー/フッター/ナビゲーションバーなどを定義するのが一般的
- 個別ビュー (
index.html.erbなど) にナビバーを書くと、他アクションで共有されず冗長になりやすい
- Railsにおけるレイアウトファイル:
このPRにより、Getting Startedガイドが実際のRailsのベストプラクティス(共通ナビはレイアウトに置く)と一致するようになっています。
#51951 ActiveStorage immediate variants
マージ日: 2025/12/5 | 作成者: @tomrossi7
- 概要 (1–2文で)
Active Storage のバリアント(variants)生成方式に、添付と同時に同期的に生成するためのimmediate: trueオプションが追加されました。これにより、これまでの「初回アクセス時にオンデマンド生成」や「バックグラウンドでの preprocessed 生成」に加え、トラフィック集中時の“生成レース”を避ける運用がしやすくなります。
- 変更内容の詳細
新オプション immediate: true
has_one_attached / has_many_attached の variant 定義で、既存の preprocessed: true と同じ要領で immediate: true を指定できます。
class User < ApplicationRecord
has_one_attached :avatar do |attachable|
# 添付と同時に thumb variant を生成
attachable.variant :thumb, resize_to_limit: [100, 100], immediate: true
end
end動作イメージ:
- これまで
- デフォルト: 最初のアクセス時に variant を同期生成(コントローラのリクエスト内)
preprocessed: true: 添付完了後にバックグラウンドジョブで生成
- これから
immediate: true: 添付処理の完了と同じタイミングで、その場で同期生成
→ 添付直後に大量アクセスが来ても「最初の何リクエストかがそれぞれ生成を試みる」レース状態を軽減できる
新ジョブ ActiveStorage::CreateVariantsJob
activestorage/app/jobs/active_storage/create_variants_job.rb が追加され、まとめてバリアントを生成する仕組みが導入されています。
主な役割:
- 添付されたレコード・添付名を元に、関連する
immediate/preprocessedvariants を列挙 - 指定された attachment について、対象の variants を生成する
- これにより、複数の variants 生成を一元的に扱えるようになり、テストしやすくなっている
※TransformJob は引き続き存在し、個々の変換実行はこれを通じて行われますが、呼び出し元の整理・責務分担が行われています。
ActiveStorage::NamedVariant の拡張
activestorage/app/models/active_storage/named_variant.rb の変更により:
- 各 named variant が
immediate/preprocessedなどのオプションを持てるように整理 - 変換用のオプションと、生成タイミングを表すオプションがはっきり切り分けられた形になっている
概ね次のようなイメージで、variant 定義のメタデータとして管理されます:
attachable.variant :thumb, resize_to_limit: [100, 100], immediate: true
# => name: :thumb, transformations: { resize_to_limit: [100, 100] }, immediate: trueAttachment 周りの変更
activestorage/app/models/active_storage/attachment.rb などで:
- 添付の作成時に、関連する
immediatevariants を検出して生成するフックが追加 representableまわりの責務が整理され、blob 側から attachment 側にロジックが移動している(ActiveStorage::Blob::Representableからの削除がその一部)
これにより、attachment 単位で「どの variants をいつ生成するか」が把握しやすくなっています。
Preview / Variant クラスの微調整
preview.rb, variant.rb, variant_with_record.rb などで、主に:
- 生成タイミングを考慮したインターフェイスの調整
- テスト・ジョブとの整合性確保
が行われています。挙動自体は互換を保ったまま、immediate / preprocessed の二軸に対応できるよう整理されています。
テスト・ドキュメント
- 新しいジョブや
immediateオプションに対するテストが追加create_variants_job_test.rbnamed_variant_test.rbattachment_test.rbなど
- 従来の variant 生成テストの一部が整理・削減(
preview_image_job_test.rbほか) - ガイド
active_storage_overview.mdにimmediateオプションの使い方が追記され、preprocessedとの違いが説明されている
- 影響範囲・注意点
パフォーマンス特性の変化
immediate: trueを付けた variants は「添付の保存と同じトランザクション or リクエストの中」で生成されるため、- 添付時のレスポンスタイムが伸びる可能性
- 同時に大量のファイルをアップロードするケースでは、アプリサーバ・ストレージの負荷増加
- 一方で、添付直後のアクセス集中時に variant 生成が競合する問題は緩和されます。
immediateとpreprocessedの使い分け- 高トラフィックで、「アップロード直後に確実に完成した variant を配信したい」場合は
immediate: true - 生成負荷をアプリリクエストから切り離したい場合は
preprocessed: true - どちらも付けない場合は従来どおり「初回アクセス時にオンデマンド生成」
設計としては、UX とインフラ負荷のトレードオフを見ながら、重要な variant だけ
immediateにするのが現実的です。- 高トラフィックで、「アップロード直後に確実に完成した variant を配信したい」場合は
既存コードへの互換性
immediateを使わない限り、挙動は基本的に後方互換です。- ただし Active Storage の内部クラス(
NamedVariant,Representable周りなど)に依存している場合は、- メソッドの呼び出し経路
- variant メタデータの持ち方 が変わっている可能性があるので、独自拡張をしているプロジェクトは差分確認が必要です。
ジョブキュー設定
preprocessed/immediateどちら経由であっても、内部ではTransformJobなどバックグラウンドジョブを使う部分があります。- Active Job のアダプタ(Sidekiq, Delayed Job など)の設定やキュー名をカスタマイズしている場合は、新ジョブ
CreateVariantsJobのキュー設定も確認してください。
- 参考情報 (あれば)
- 該当 PR: https://github.com/rails/rails/pull/51951
- Active Storage ガイド(
immediate/preprocessedオプションの説明が追記済み):guides/source/active_storage_overview.md - 関連実装ファイル(主なもの):
activestorage/app/jobs/active_storage/create_variants_job.rbactivestorage/app/models/active_storage/attachment.rbactivestorage/app/models/active_storage/named_variant.rbactivestorage/app/models/active_storage/variant*.rb
#56279 Use pattern matching on sql instead of string match event payload name
マージ日: 2025/12/5 | 作成者: @zzak
- 概要 (1-2文で)
Active Storage のコントローラテストで、ActiveSupport::Notifications のイベント名文字列に対するマッチングをやめ、代わりに SQL 文の内容に対してパターンマッチするように変更した PRです。テストが内部実装(通知名の文字列形式)の変更に依存しないようにし、より堅牢にしています。
- 変更内容の詳細
対象ファイル:
activestorage/test/controllers/representations/redirect_controller_test.rb
このテストでは、ActiveStorage::Representation のリダイレクト処理中に発行される DB クエリ(代表的には SELECT など)が実行されるかどうかを ActiveSupport::Notifications 経由で検証している部分があります。
もともとの実装では、おそらく以下のように「通知イベント名の文字列」を対象にパターンマッチしていました(イメージ):
events = []
ActiveSupport::Notifications.subscribe("sql.active_record") do |*args|
event = ActiveSupport::Notifications::Event.new(*args)
events << event
end
# 「イベント名のメッセージ」などを文字列でマッチしていた例
assert events.any? { |event| event.name.include?("Representation Load") }この方法だと、通知の「イベント名」やペイロード中の「メッセージ」の書式が変わると(例: "SQL" → "ActiveRecord SQL" のような微妙な変更)、実際の挙動に問題がなくてもテストだけが壊れてしまいます。
この PR では、イベント名やメッセージ文字列ではなく、ペイロード中の SQL 文自体(payload[:sql])に対してパターンマッチ(Ruby のパターンマッチング / case-in / 正規表現など)を行うようにテストを変更しています。例としては以下のようなイメージです:
ActiveSupport::Notifications.subscribe("sql.active_record") do |*args|
event = ActiveSupport::Notifications::Event.new(*args)
sql = event.payload[:sql]
case sql
in /FROM "active_storage_blobs"/
# Active Storage の特定のクエリが発行されたことを確認
end
end今回の差分は +4/-2 行と少なく、主な変更は「文字列によるイベント名マッチ」→「SQL に対するパターンマッチ」への書き換えです。
元 PR #56225 のフォローアップとして、テストの堅牢性を高める目的で行われています。
- 影響範囲・注意点
影響範囲
- 対象は Active Storage の
Representations::RedirectControllerに関するテストのみで、アプリケーション本体の挙動には影響しません。 - CI 上のテスト安定性が向上し、「通知メッセージのフォーマットが変わっただけ」でテストが落ちるリスクが減ります。
- 対象は Active Storage の
注意点
- 今後、同様に ActiveSupport::Notifications をテストで利用する場合、イベント名そのものの文字列フォーマットへの依存は避け、より安定した情報(SQL 文、payload の構造化されたキー、バインド値など)で検証するのが推奨されます。
- SQL に対するパターンマッチも、スキーマ変更(テーブル名やカラム名の変更)には依存する点は変わらないため、「何に依存するか」を意識してテストを書く必要があります。
- 参考情報 (あれば)
- 元 PR: #56225(今回のフォローアップ元。通知に関するテストまわりの変更が含まれている可能性が高い)
- 関連 API:
ActiveSupport::NotificationsActiveSupport::Notifications::Event
- パターンマッチング: Ruby 2.7 以降の
case ... in構文(今回の PR タイトルにある “pattern matching” は、これや類似の機構を指していると考えられます)。
#56285 fix(activesupport): handle syntax errors in debug views when using editor
マージ日: 2025/12/5 | 作成者: @markokajzer
- 概要 (1-2文で)
Rails のデバッグビューでRAILS_EDITORを使っている場合に、ERB テンプレート内の構文エラーが正しく処理されずエラーになる問題を修正した PR です。ActiveSupport::SyntaxErrorProxyが、エディタ連携用に必要なabsolute_pathを返せるようにしています。
- 変更内容の詳細
問題の背景
RAILS_EDITOR環境変数を設定していると、例外発生時のデバッグ画面から「編集」ボタン等を通じて、対応するファイルを手元のエディタで開けるようになります。- その際
debug_view.rbでは、バックトレースの各フレームからabsolute_pathを取得して、editor_url(RAILS_EDITORに基づく URL / コマンド)を生成します。
参考(PR 説明からのリンク先コード):ruby# debug_view.rb のイメージ(簡略) backtrace_locations.each do |location| path = location.absolute_path || location.path # path を使って editor_url を生成 end - ところが、ERB テンプレートの構文エラーが起きた場合、それは
ActiveSupport::SyntaxErrorProxy経由でラップされ、そのBacktraceLocationオブジェクトがabsolute_pathを実装しておらず、debug_view側から呼び出したときにエラーになる、という不具合がありました(#55295 の変更の影響)。
修正内容
ActiveSupport::SyntaxErrorProxy::BacktraceLocationクラスに#absolute_pathメソッドを追加。absolute_pathが debug view から呼ばれたときに、通常のバックトレースフレームと同様の情報を返せるようにしたことで、RAILS_EDITOR使用時でも構文エラーの箇所に対応するファイルパスが取得可能になります。- これにより、構文エラーでも「エディタで開く」が動くようになり、かつ
NoMethodErrorなどの二次的な例外が発生しなくなります。
変更イメージ(概念的な擬似コード):
module ActiveSupport
class SyntaxErrorProxy
class BacktraceLocation
# もともと `path` や `lineno` などがある
def absolute_path
# 実ファイルフレームに合わせた形で絶対パスを返す
@absolute_path || path # 実装は実フレームの情報に基づく
end
end
end
endテスト追加
actionpack/test/dispatch/debug_exceptions_test.rb に 12 行のテストを追加し、以下を確認しています:
RAILS_EDITORが設定されている状態で、ERB の構文エラーが発生しても例外ハンドラが落ちない。- debug view から editor の URL 生成が正常に行われる(=
absolute_pathが正常動作する)。
- 影響範囲・注意点
- 影響範囲:
RAILS_EDITORを設定していて、かつ ERB テンプレート内で構文エラーが起きるケースに限定されます。- 通常の例外(構文エラー以外)や
RAILS_EDITORを使っていない環境では挙動への影響はほぼありません。
- 利用者側での対応:
- Rails をこのコミットを含むバージョンに更新すると、
RAILS_EDITOR利用時の構文エラー画面が安定し、エディタ連携も期待通り動作します。 - 既に
RAILS_EDITORを使っていて、構文エラーで debug view が落ちる/エディタリンクが出ないといった症状がある場合、この修正を取り込むことで解消されます。
- Rails をこのコミットを含むバージョンに更新すると、
- 後方互換性:
- 新たに public API を壊す変更はなく、
BacktraceLocation#absolute_pathの追加は、標準のバックトレース仕様に沿う形の拡張であり、後方互換的です。
- 新たに public API を壊す変更はなく、
- 参考情報 (あれば)
- 関連 Issue: #56284
- 直前の変更(原因となった変更): #55295
- 該当コード(debug view 側で
absolute_pathを使っている箇所):actionpack/lib/action_dispatch/middleware/debug_view.rbのeditor_url生成周り
#56290 Allow schema_dump configuration to be an absolute path.
マージ日: 2025/12/5 | 作成者: @flavorjones
- 概要 (1-2文で)
database.ymlのschema_dump設定に「絶対パス」を書けるようにし、エンジン固有のスキーマファイルをアプリ本体とは別の場所(例: gem の中)に置けるようにした PR です。既存の相対パスの挙動は維持したまま、絶対パスの場合はそのまま利用されるようになります。
- 変更内容の詳細
これまでの挙動
ActiveRecord::Tasks::DatabaseTasks.schema_dump_path は、以下のような扱いをしていました:
database.ymlのschema_dump設定値は「DatabaseTasks.db_dirを基準とした相対パス」とみなされる- 絶対パスを指定しても
db_dirと結合されてしまい、意図通りに使えない - 環境変数
SCHEMAだけは絶対パス指定が可能
そのため、エンジン側で
schema_dump: /path/to/engine/db/saas_schema.rbのように書いても、db/ ディレクトリ配下への相対パスとして扱われてしまい、アプリ本体とは別の場所にスキーマを置くことが難しい状態でした。
今回の変更
ActiveRecord::Tasks::DatabaseTasks.schema_dump_path の実装が次のように拡張されています:
schema_dumpに設定されたパスをPathnameでラップpath.absolute?がtrueのときは、そのパスを変換せずにそのまま返す- 絶対パスでなければ、従来通り
DatabaseTasks.db_dirを基準に解決
擬似コードで表すと:
def self.schema_dump_path(db_config, format = ActiveRecord.schema_format)
path = db_config.schema_dump # などから取得した値
if Pathname.new(path).absolute?
path # <- そのまま使う
else
File.join(db_dir, path) # <- 従来の相対パス挙動
end
end※ 実際のコードはもう少し文脈がありますが、ポイントは「絶対パスなら素通しする」という一点です。
利用例
エンジン側の gem ディレクトリにスキーマファイルを置きたい場合、database.yml で次のように書けます:
<% gem_path = Gem::Specification.find_by_name("fizzy-saas").gem_dir %>
production:
primary:
# ...
saas:
database: saas_production
host: <%= mysql_database_host %>
username: <%= mysql_app_user %>
password: <%= mysql_app_password %>
# migrations_paths は既に絶対パス対応済み
migrations_paths: <%= File.join(gem_path, "db", "migrate") %>
# この PR により絶対パスで指定可能に
schema_dump: <%= File.join(gem_path, "db", "saas_schema.rb") %>こうしておくと、db:schema:dump 等を実行したときに、saas DB 用のスキーマはアプリ本体の db/ ではなく、fizzy-saas gem 側の db/saas_schema.rb に出力されます。
テスト・ドキュメント
activerecord/test/cases/tasks/database_tasks_test.rbに、絶対パスを指定した場合の挙動を確認するテストが追加されています。activerecord/CHANGELOG.mdに、この挙動変更(新機能)が記載されています。
- 影響範囲・注意点
- 相対パス指定の既存挙動は変わらない
- これまでどおり
schema_dump: schema.rbなどの設定はdb/ディレクトリ配下として扱われます。
- これまでどおり
- 絶対パスを指定した場合だけ新挙動
schema_dump: /foo/bar/schema.rbのようなパスは、その場所に直接スキーマが出力されます。
- 複数データベース+エンジン構成で有用
- マルチ DB(
primary,replica,saasなど)かつエンジンを gem として分離している場合に、各エンジンごとにスキーマファイルを分けて管理しやすくなります。
- マルチ DB(
- パスの権限・存在には注意
- 絶対パス先のディレクトリが存在しない、または書き込み権限がない場合は、当然ながら
db:schema:dump実行時にエラーになります。 - CI や本番環境などでパスを変える場合は、
database.ymlの ERB で環境変数等を噛ませる想定になります。
- 絶対パス先のディレクトリが存在しない、または書き込み権限がない場合は、当然ながら
- 参考情報 (あれば)
- PR: https://github.com/rails/rails/pull/56290
- 背景として挙げられている OSS アプリとエンジン:
- アプリ本体: https://github.com/basecamp/fizzy
- SaaS 向けエンジン: https://github.com/basecamp/fizzy-saas
- 関連する既存機能:
migrations_pathsは以前から絶対パス指定をサポートしており、今回のschema_dumpがそれに揃えられた形です。
#56283 ActionText: Validate RemoteImage URLs
マージ日: 2025/12/5 | 作成者: @flavorjones
- 概要 (1-2文で)
Action Text でリッチテキストに添付される「リモート画像」の URL を検証し、image.pngのような相対パス風の値が誤ってアセットパイプラインに流れないようにした変更です。これにより 500 エラーや意図しないアセット解決/悪用の可能性を減らします。
- 変更内容の詳細
背景となる問題
Trix 経由などでリッチテキストに画像が埋め込まれた際、画像の src が以下のような値だったとします:
<img src="image.png">ActionText::Attachables::RemoteImage はこの src を元に RemoteImage オブジェクトを作成し、レンダリング時に Propshaft(アセットパイプライン)側で解決を試みていました。その結果:
image.pngがアセットに存在しない場合:ActionView::Template::Error: The asset 'image.png' was not found in the load path.が発生し、特別に rescue していないと 500 エラーになる。- 同名のアセットが存在する場合:
本来意図しないローカルアセットに解決される可能性がある。
(セキュリティ上も、想定外のアセットを引き当てられる「踏み台」になりうる)
具体的な修正内容
RemoteImage.from_node の中で、src に対して「リモート URL として妥当か」を検証するロジックが追加されました。
- 検証には、レンダリング時に
AssetUrlHelperが用いているものと同じ正規表現 を使用します。 - これにより、HTTP(S) などの「正しいリモート URL」だけが
RemoteImageとして扱われるようになります。 - 一方で、次のような値は「リモート画像」としては不正とみなされます:
image.png/images/foo.png- など、アセットパイプラインに解決されうる「相対パス/ルート相対パス」
そのような「不正 URL」だった場合:
- 以前は
RemoteImageを生成 → アセット解決を試行 → 例外 or 誤ったアセット解決、という流れになっていた。 - 変更後は
RemoteImageを生成せず、Action Text 内の通常のフォールバック処理(MissingAttachable)に委ねられます。
テストの追加
actiontext/test/unit/content_test.rb にユニットテストが追加され、以下が確認されています:
- 正しいリモート URL(例:
https://example.com/image.png)はRemoteImageとして扱われ、これまで通り表示される。 - 不正な URL(例:
image.png)はRemoteImageとしては扱われず、MissingAttachableルートに落ちる。
actiontext/CHANGELOG.md にも、この挙動変更が記載されました。
- 影響範囲・注意点
影響範囲
- 対象は Action Text で「リモート画像」を扱う部分のみ です。
- 具体的には、Trix エディタなどから埋め込まれた
<img src="...">に対し、srcが完全なリモート URL(例:https://...)なら: 従来通りRemoteImageとして扱われる。- そうでない場合(相対パス等):
RemoteImageではなくなり、MissingAttachableとして処理される。
既存アプリへの影響・移行上の注意
相対パスを「リモート画像」として使っていた場合
- これまではたまたま動いていた(あるいは 500 を rescue していた)ケースが、
今後はMissingAttachableとしてレンダリングされ、画像が表示されなくなる可能性があります。 - 対応策:
- リッチテキストに埋め込む画像 URL は、
https://example.com/foo.pngなどの完全な URL に修正する。 - あるいは、画像は Active Storage 経由で添付する(
<action-text-attachment>)運用に寄せる。
- リッチテキストに埋め込む画像 URL は、
- これまではたまたま動いていた(あるいは 500 を rescue していた)ケースが、
500 エラー対策として
Template::Errorを rescue していた場合- この PR により、「不正な相対 URL がアセット解決で例外を出す」というパスは通らなくなるので、 そのためだけの rescue は不要になる可能性があります。
- ただし他の理由で同じ例外を rescue している場合は、その用途と切り分けてください。
セキュリティ上の意味合い
- 任意の
src="foo.png"が、アセットパイプライン経由で別のローカルアセットに解決される可能性が減ります。 - 特に「ユーザ入力の HTML を Action Text に取り込み、レンダリングしている」ようなアプリでは、 意図しないアセットへのアクセスや情報露出のリスクが下がります。
- 任意の
- 参考情報 (あれば)
- 該当 PR: https://github.com/rails/rails/pull/56283
- 関連する内部 API:
ActionText::Attachables::RemoteImage.from_nodeActionText::ContentActionView::Helpers::AssetUrlHelper(同一の URL 検証正規表現を使用)
- 実運用上の推奨:
- リモート画像は
https://...の完全 URL で扱う。 - アプリ内画像は、できるだけ Active Storage の添付機能を経由させる。
- リモート画像は
#56293 Combine rather than overwrite tag content supplied via both parameter and block
マージ日: 2025/12/5 | 作成者: @dhh
- 概要 (1-2文で)
Rails のtag.*ヘルパで、引数とブロックの両方でコンテンツを渡した場合に「後勝ちで上書き」されていた挙動が、「結合して連結」される挙動に変更されました。これにより、tag.div("Hello ") { "World" }は<div>Hello World</div>を返すようになります。
- 変更内容の詳細
これまでの挙動
ActionView::Helpers::TagHelper の tag ヘルパ(例: tag.div)において、以下のように文字列引数とブロックを同時に渡した場合:
tag.div("Hello ") { "World" }従来はブロック側が優先され、引数の "Hello " は無視されていました:
<div>World</div>これは「サイレントに引数コンテンツを捨てる」挙動でした。
変更後の挙動
この PR により、引数で渡したコンテンツとブロックで返したコンテンツを結合して出力するようになりました。
tag.div("Hello ") { "World" }
# => <div>Hello World</div>内部的には、おそらく以下のようなイメージで実装が変わっています:
- 以前:
content = block_given? ? capture(&block) : content_or_options_with_block - 以後: 引数側とブロック側のコンテンツが両方存在する場合は、両方を連結(
+など)して最終コンテンツとする
実際のコード変更は action_view/helpers/tag_helper.rb の1行程度のロジック修正にとどまり、テスト (tag_helper_test.rb) と CHANGELOG の追記が行われています。
サンプルコード
# 以前(〜このPR前)
tag.span("A") { "B" } # => <span>B</span>
# これから(このPR後)
tag.span("A") { "B" } # => <span>AB</span>
# 空文字や nil が絡んだ場合(イメージ)
tag.p(nil) { "Body" } # => <p>Body</p>
tag.p("Intro ") { nil } # => <p>Intro </p>- 影響範囲・注意点
影響範囲
ActionView::Helpers::TagHelperのtagAPI を使い、同じタグ呼び出しで「引数のコンテンツ」と「ブロック」を併用している箇所すべてに影響します。content_tagではなくtag.div,tag.spanなどの「新しい tag API」を使っているコードが対象です。
後方互換性の観点
- 従来はブロック側が常に優先されていたため、「引数は無視されるもの」と期待していたコードでは出力が変わります。
- 例: 「引数側にダミー値」や「古いAPIとの互換用値」を入れていた場合など。
- 多くのケースでは、明示的に引数とブロックの両方を渡していれば「両方とも表示したい」意図であることが多いため、挙動としては自然になった一方で、HTML 出力の差分が生じる可能性はあります。
- 従来はブロック側が常に優先されていたため、「引数は無視されるもの」と期待していたコードでは出力が変わります。
テンプレートレビューのポイント
tag.xxx("something") do ... endのような呼び方をしているビューテンプレートを grep 等でチェックし、- その両方のコンテンツが出力されることが想定どおりか
- 余計な空白や改行が増えないか(
"Hello "の末尾スペースなど) を確認すると安全です。
パフォーマンス
- 追加の連結処理は非常に軽量であり、実質的なパフォーマンスへの影響はほぼ無視できるレベルと考えられます。
- 参考情報 (あれば)
- 対象ファイル:
actionview/lib/action_view/helpers/tag_helper.rbactionview/test/template/tag_helper_test.rbactionview/CHANGELOG.md
- 変更規模:
- 3ファイル変更
- 追加 14 行 / 削除 1 行
- 関連ヘルパ:
tag.div,tag.span,tag.pなどtagオブジェクト経由のタグ生成系ヘルパ全般に適用されます。
#56294 Fix MemCacheStore for connection_pool >= 3
マージ日: 2025/12/5 | 作成者: @fatkodima
- 概要 (1-2文で)
ActiveSupport::Cache::MemCacheStoreがconnection_poolgem の v3 以降と正しく連携できるように、初期化ロジックを1行だけ修正したPRです。直前の PR #56292 でのconnection_pool対応からmem_cache_storeだけ漏れていた部分をフォローしています。
- 変更内容の詳細
※ 実際の差分は 1 行の置き換えのみです。PR タイトルと文脈からすると、以下のような意図の修正です(擬似コードで説明します):
# 変更前(connection_pool 2 系まで前提の書き方)
@pool = ConnectionPool.new(size: options[:pool_size], timeout: options[:pool_timeout]) do
build_client(...)
end
# 変更後(connection_pool 3 系に合わせた書き方)
@pool = ConnectionPool::Wrapper.new(size: options[:pool_size], timeout: options[:pool_timeout]) do
build_client(...)
endもしくは、#56292 で RedisCacheStore などに行ったのと同種の変更(例: 引数の取り回しや、pool のラッパークラスの指定方法の統一)を、MemCacheStore にも同様に適用しただけです。
要するに、MemCacheStore 内で connection_pool を使う部分の API 呼び出しを、connection_pool v3 に対応したものに揃える変更です。
- 影響範囲・注意点
- 影響対象:
config.cache_store = :mem_cache_storeなどでMemCacheStoreを使用しつつ、connection_poolgem v3 以上を利用しているアプリケーション。
- 期待される改善:
connection_poolv3 系でMemCacheStoreを使ったときに発生していた例外(例えば「uninitialized constant ConnectionPool::Wrapper」や、逆に Wrapper 必須なのに使っていないことによるエラーなど)が解消される。RedisCacheStoreなど他のキャッシュストアと同様に、connection_poolv3+ と一貫した挙動をとる。
- 互換性:
- 変更は 1 行のみで、
connection_poolの公式な v3 向け移行パスに沿った修正のため、通常の利用において後方互換性リスクは小さいと考えられます。 - もしアプリ側で
MemCacheStoreの内部実装(@poolのクラスや API)に依存したメタプログラミングをしている場合は、@poolの型変更などにより影響する可能性があります(そのような利用は通常は非推奨です)。
- 変更は 1 行のみで、
- 参考情報 (あれば)
- この PR: https://github.com/rails/rails/pull/56294
- 直前の関連 PR (
connection_pool対応の本体と思われる): https://github.com/rails/rails/pull/56292 connection_pool3.0 リリース関連情報(API 変更の背景などを確認するときに有用):
#56280 Use Digest::UUID.uuid_v5 to generate uuid instead of hand-rolled
マージ日: 2025/12/4 | 作成者: @zzak
- 概要 (1-2文で)
Rails 内部で UUID を生成していた箇所を、自前実装ではなく Ruby 標準ライブラリDigest::UUID.uuid_v5を使うように置き換えた PR です。これにより UUID 生成ロジックが標準化され、保守性と一貫性が向上します。
- 変更内容の詳細
対象ファイル:
railties/lib/rails/devtools_controller.rb主な変更点:
これまでRails::DevtoolsController(開発用ツールのコントローラ)内で、名前空間付き UUID(おそらく v5 相当)の生成を独自実装していた部分が、Ruby 3.4 で追加された(もしくは近日追加予定の)Digest::UUID.uuid_v5を呼び出す形にリファクタリングされました。変更のイメージは以下のようなものです(擬似コード):
ruby# 変更前(例: 独自で UUIDv5 っぽいものを生成していた) uuid = Digest::UUID.new(namespace: SOME_NAMESPACE, name: some_string).generate # 変更後(Digest::UUID.uuid_v5 を使用) uuid = Digest::UUID.uuid_v5(SOME_NAMESPACE, some_string)実際には数行の修正で、3行追加・2行削除という軽微な変更です。
PR 説明にある "Follow up to #56245" から、#56245 でDigest::UUIDを導入したか、それに関連する土台を整え、その後続として実際の利用箇所を置き換えたものと考えられます。なぜ
uuid_v5なのか
v5 UUID は「名前空間+名前(文字列など)」から決定論的に同じ UUID を生成する方式です。- 同じ namespace + name なら同じ UUID
- 異なる namespace もしくは name なら別の UUID
開発ツール用コントローラで、同じキーから安定した UUID を作りたい用途に適しています。
- 影響範囲・注意点
影響範囲
Rails::DevtoolsControllerが利用している UUID 生成ロジックのみが影響を受けます。- 本番アプリケーションに直接影響するようなコア機能ではなく、開発支援用ツールに限定された変更です。
- UUID の計算方法が「完全に同一」でない場合、同じ入力から得られる UUID が従来と変わる可能性があります。ただし devtools 用なので、互換性リスクは小さいと考えられます。
注意点
Digest::UUID/Digest::UUID.uuid_v5が利用できる Ruby バージョンが前提になります。
Rails をこのコミット以降のバージョンで使う場合、対応する Ruby バージョン(Digest::UUIDを含むもの)が必要になります。- もし外部ツールやスクリプトが「devtools が返す UUID の値そのもの」に依存していると、生成値の違いにより影響が出る可能性がありますが、一般的な Rails アプリではほぼ無視できるレベルです。
- 参考情報 (あれば)
- 該当 PR:
- #56280: Use Digest::UUID.uuid_v5 to generate uuid instead of hand-rolled
- #56245: この PR の前提となる変更(
Digest::UUIDの導入や関連リファクタリング)
- Ruby 側の機能:
Digest::UUIDモジュール(Ruby コア / 標準ライブラリに追加されつつある UUID サポート)- UUIDv5 仕様: RFC 4122 “Name-Based UUIDs”(SHA-1 ベース、namespace + name から決定論的に生成)
#56292 Fix RedisCacheStore for connection_pool >= 3
マージ日: 2025/12/4 | 作成者: @fatkodima
- 概要 (1-2文で)
RedisCacheStoreがconnection_poolgem のバージョン 3 以上で動かなくなる問題 (#56291) を修正した PR です。RedisCacheStoreとconnection_poolのインターフェイスの差異を吸収するための、ごく小さな互換性修正が 1 行だけ入っています。
- 変更内容の詳細
※PR本文の実コード断片は提示されていないため、ここでは Rails と connection_pool 3 系の変更点から「どのような修正か」を技術的に推測・整理します。実際の 1 行差分は、この方向性のいずれかです。
背景: RedisCacheStore と connection_pool の関係
ActiveSupport::Cache::RedisCacheStore は内部で connection_pool を使って Redis クライアントのプーリングを行います。典型的な利用イメージは以下のようなものです。
cache = ActiveSupport::Cache::RedisCacheStore.new(
url: ENV["REDIS_URL"],
pool: ConnectionPool.new(size: 5, timeout: 1) { Redis.new }
)Rails 側のコードでは、pool.with { |conn| ... } のような形で各種 Redis 操作を実行します。
問題の発生点: connection_pool >= 3 での非互換
connection_pool 3 系では、以下のような変更が入っており、2 系までを前提としたコードが壊れることがあります。
- メソッド名や引数の扱いの変更
ConnectionPool::Wrapperの使い方や返り値の挙動変更withブロックの呼び出しパターンの厳格化 など
そのため、RedisCacheStore 内部で
pool.withの呼び出し方pool.checkout/pool.checkin相当の処理pool.method(...)の転送
などが旧インターフェイスを前提に書かれていると、connection_pool 3 にアップデートしたタイミングで NoMethodError や ArgumentError が発生します。
この PR の実質的な修正内容
差分が「+1/-1 の 1 行のみ」であることから、以下のようなピンポイント修正の可能性が高いです。
一例:
# 変更前(connection_pool 2 系までは動くが、3 系で壊れる書き方)
@pool.with do |conn|
# Redis 操作
end
# 変更後(3 系の挙動に合わせた呼び出し方)
@pool.with do |redis|
# Redis 操作
endまたは、キーワード引数・ブロック引数の扱いが変わったことに伴う修正:
# 例1: ブロックあり/なしのパターンに対応
@pool.with { |client| client.public_send(command, *args, **kwargs) }
# 例2: `then` のようなメソッドチェーンとの組み合わせを修正
@pool.with { |client| yield client } # と明示して新しいシグネチャに合わせるいずれにせよ、
connection_pool2 系でも動く- かつ 3 系でもエラーにならない
という互換的な呼び出し方に 1 行書き換えた修正と考えられます。
- 影響範囲・注意点
影響範囲
ActiveSupport::Cache::RedisCacheStoreを利用しており、- かつアプリケーションが
connection_pool3 以上を採用している場合
に、キャッシュアクセス時の例外発生や挙動不良が解消されます。
Rails 自身が内部で利用する Redis ベースのキャッシュ(config.cache_store = :redis_cache_store など)にも関係するため、Redis キャッシュ利用アプリ全般に影響しうる変更です。
注意点
- 既に
connection_pool2 系を使っていて問題なく動いているアプリに対しては、今回の修正による後方互換性ブレイクはほぼ考えにくく、安全なマイナー修正とみなせます。 - もし
connection_pool3 系にアップグレードした際に- Redis のキャッシュ操作で
NoMethodError,ArgumentError, またはundefined method 'with'/wrong number of argumentsなどが出ていた場合は、この PR を含む Rails バージョンへの更新で解消される可能性が高いです。
- Redis のキャッシュ操作で
- Rails 本体よりも先に
connection_poolだけを major upgrade していた場合、Rails をこの PR を含むバージョンまで引き上げることが推奨されます。
- 参考情報 (あれば)
- 該当 Issue: #56291 —
RedisCacheStoreがconnection_pool3 系で壊れる不具合報告 - PR: #56292 “Fix
RedisCacheStoreforconnection_pool>= 3” - 関連しうる外部リソース:
connection_poolGitHub リポジトリの 3.0 リリースノート / Changelog- Rails ガイド: Caching with Redis(最新版を参照)
#56286 [ci skip][docs] Fix RDoc markup for Time.zone.today in Date.current
マージ日: 2025/12/4 | 作成者: @Yuhi-Sato
- 概要 (1-2文で)
Date.currentの RDoc 内で、コード参照Time.zone.todayだけが<tt>でマークアップされていなかった問題を修正し、他のコード参照と統一したドキュメント整備のPRです。コードの挙動やAPI自体は一切変わっておらず、見た目と可読性の改善のみです。
- 変更内容の詳細 (サンプルコード含む)
対象ファイル:
activesupport/lib/active_support/core_ext/date/calculations.rb
Date.current のドキュメントコメント(RDoc)の中にある、以下のような説明文が元々ありました:
# Returns Time.zone.today when config.time_zone is set, otherwise just returns
# Date.today.この文中で、
Time.zoneconfig.time_zoneDate.today
は <tt>...</tt> でコードとしてマークアップされていた一方で、Time.zone.today だけがプレーンテキストになっていました。
PRでは、この不整合を解消するために Time.zone.today を <tt> で囲むように1行を修正しています:
- # Returns Time.zone.today when <tt>config.time_zone</tt> is set, otherwise just returns
- # <tt>Date.today</tt>.
+ # Returns <tt>Time.zone.today</tt> when <tt>config.time_zone</tt> is set, otherwise just returns
+ # <tt>Date.today</tt>.※ 上記はイメージであり、実際の前後の文脈や位置はファイル内の Date.current のRDocコメント部分になります。
これにより、RDoc生成時に Time.zone.today が他と同様に等幅フォント・コードとして表示されるようになります。
- 影響範囲・注意点
影響範囲
- Railsアプリケーションの挙動、API仕様、型や返り値などには一切変更なし。
- 影響があるのは 生成されるドキュメント(RDoc) の表示のみ。
- RDoc / edgeguides / 各種自動生成ドキュメントで、
Date.currentの説明におけるコード表記が統一されます。
注意点
- 既存コードの修正やテストの変更はなく、バージョンアップに伴う互換性問題はありません。
- CIを回す必要のないドキュメントのみ変更のため、タイトルに
[ci skip][docs]が付いています。
- 参考情報 (あれば)
- 対象メソッド:
ActiveSupport::CoreExtensions::Date::Calculations#current(一般にDate.currentとして使用) Date.currentの挙動(改めて確認用):config.time_zoneが設定されている場合:Time.zone.todayを返す- 未設定の場合:
Date.todayを返す
今回のPRはこの挙動の説明テキストの見た目を整えただけであり、挙動そのものは従来どおりです。
#56289 Fix typos in comments
マージ日: 2025/12/4 | 作成者: @yujiteshima
概要 (1-2文で)
このPRは、Rails本体のコメント中の英単語のスペルミスを修正しただけの変更です。コードの挙動や公開APIには一切影響しません。変更内容の詳細
修正内容
- Action Controller のコメント:
Explictly→Explicitly
- Action Cable (PostgreSQL サブスクリプションアダプタ) のコメント:
purposedly→purposely
- Action Controller のコメント:
対象ファイル
actionpack/lib/action_controller/metal/redirecting.rbactioncable/lib/action_cable/subscription_adapter/postgresql.rb
どちらも「コメントのみ」の変更であり、実行パスに関わるコードは一切変更されていません。codespell というスペルチェッカーツールで検出された誤字を修正した、ドキュメント改善系のPRです。
(疑似的なイメージ例)
# Before
# Explictly redirect to ...
# After
# Explicitly redirect to ...# Before
# This is purposedly left ...
# After
# This is purposely left ...- 影響範囲・注意点
影響範囲
- 実行時の挙動、パフォーマンス、セキュリティ、API 仕様に影響はありません。
- 変更箇所はコメントのみのため、バイナリ・コンパイル成果物にも実質的な差分は出ません。
- ドキュメントとしての読みやすさ・正確さがわずかに向上しています。
注意点
- スペル修正に伴うコメント内テキストの変更のみのため、既存コードやアプリケーション側での対応は不要です。
- コメントを参照している外部ツール(ドキュメント生成等)を使っている場合も、意味は変わらず、破壊的な変更にはなりません。
- 参考情報 (あれば)
- PR: https://github.com/rails/rails/pull/56289
- 使用ツール:
codespell(OSSプロジェクトでよく使われる英単語スペルチェックツール) - Rails の機能的な変更ではなく、品質改善(コメントの整備)カテゴリの変更として扱って問題ありません。
#56288 Add SecureRandom.base32
マージ日: 2025/12/4 | 作成者: @monorkin
- 概要 (1-2文で)
Rails のSecureRandomに、人間が読みやすく誤読しづらい Crockford 方式の Base32 文字列を生成するSecureRandom.base32が追加されました。これにより、2FA コードやマジックリンク用トークンなどを、安全かつユーザーフレンドリーな形式で簡単に生成できます。
- 変更内容の詳細
追加された API
ActiveSupport の SecureRandom 拡張に、新しく SecureRandom.base32 が追加されています。
既存の hex, base64, urlsafe_base64, base58 と同じパターンで実装されており、暗号学的に安全な乱数ソースを使いつつ、文字集合を Crockford Base32 にしたものです。
典型的な使い方は以下のようなイメージです(PR 本文から推測される使用感):
# 例: 16 文字程度の人間向けコードを生成(実際の引数仕様は他のメソッドに準拠する想定)
SecureRandom.base32 # デフォルト長
SecureRandom.base32(10) # 桁数(もしくはバイト数)を指定※ 実際の引数の意味(「バイト数指定」か「文字数指定」か)は SecureRandom.base58 等と同じインターフェイスに揃えられているはずなので、Rails/ActiveSupport のドキュメントか、core_ext/securerandom.rb の他メソッドと同様に扱えばよいです。
文字集合 (Crockford's Base32)
SecureRandom::BASE32_ALPHABET が定義され、Crockford 方式の Base32 アルファベットが使われます。Crockford Base32 の特徴:
- 大文字・小文字非依存
- 人間が混同しやすい文字を避ける / マッピングする設計
- 例: 0 と O, 1 と I / L など
- 読み上げや手入力に向いた設計
Git 上では:
activesupport/lib/active_support/core_ext/securerandom.rbBASE32_ALPHABETを追加- 既存の Base 系メソッドと同じテンプレートで
base32を追加
activesupport/test/core_ext/secure_random_test.rb- 新しい
base32メソッド用のテストを追加(長さ・文字集合・ランダム性などを検証)
- 新しい
activesupport/CHANGELOG.md- ActiveSupport に
SecureRandom.base32を追加した旨を記載
- ActiveSupport に
これにより、アプリケーション側で毎回「安全な乱数 + 人間向け文字集合」の実装を自前で用意する必要がなくなります。
- 影響範囲・注意点
影響範囲
- 新規メソッド追加のみで、既存 API の挙動変更や削除はありません。
- ActiveSupport をロードしている Rails アプリ/ライブラリで
SecureRandom.base32が利用可能になります。 - 2FA コード、招待コード、マジックリンク用トークンなど「人が読む・打つ前提のコード」を生成するユースケースで特に有用です。
注意点
- 「Base32」とだけ書かれているものには複数のバリアント(RFC 4648, Crockford など)があるため、この
base32は Crockford Base32 固定である点に注意してください。
既存の RFC 4648 準拠の Base32 実装とは互換ではありません。 - Rails 側では
base58が Bitcoin 形式を特に名前に含めずに採用している慣例にならって、base32という汎用的な名前で Crockford バリアントを提供しています。 - 既に独自の Base32 実装(特に Crockford Base32)を持っている場合は、徐々に
SecureRandom.base32に置き換えることで、暗号学的安全性と実装のシンプルさを統一できる可能性があります。 - 「長さの指定方法」(引数の意味)は
SecureRandom.base58等と合わせているはずなので、置き換え時にはbase58との対応関係を確認しておくと安全です。
- 「Base32」とだけ書かれているものには複数のバリアント(RFC 4648, Crockford など)があるため、この
- 参考情報 (あれば)
- PR 本文で挙げられている資料:
- Crockford's Base32 定義:
https://www.crockford.com/base32.html - Base32 (Wikipedia, Crockford セクション):
https://en.wikipedia.org/wiki/Base32#Crockford's_Base32
- Crockford's Base32 定義:
- 類似 API(インターフェイスの参考になるもの):
SecureRandom.hexSecureRandom.base64SecureRandom.urlsafe_base64SecureRandom.base58
#56287 Fix ActiveRecord::SoleRecordExceeded#record to return the relation
マージ日: 2025/12/4 | 作成者: @byroot
- 概要 (1-2文で)
ActiveRecord::SoleRecordExceeded例外オブジェクトの#recordメソッドが、本来返すべき「元の Relation」ではなく誤った値を返していたリグレッションを修正し、再び Relation を返すようにした PR です。sole/sole!利用時に、例外から安全にクエリ条件を再利用できる挙動が復元されています。
- 変更内容の詳細
背景
ActiveRecord::Relation#sole/#sole!は「レコードがちょうど1件だけ存在する」ことを期待する finder です。- 0件:
ActiveRecord::RecordNotFound(sole!) /nil(sole) - 1件: そのレコードを返す
- 2件以上:
ActiveRecord::SoleRecordExceededを発生させる
- 0件:
ActiveRecord::SoleRecordExceededには#recordメソッドがあり、「どの Relation に対して sole を呼び出したか」を参照できるようになっていました。- しかし、過去の PR(
#50396)の影響で、この#recordが Relation ではない別の値を返すように壊れてしまっていた、というのが今回の Issue(#56281)の内容です。
今回の修正内容
finder_methods.rb の修正
activerecord/lib/active_record/relation/finder_methods.rb に1行の修正が入り、SoleRecordExceeded 例外にセットされる「record」が再び Relation になるように戻されています。
概念的には、以下のようなイメージです(実際のコードは Rails 内部実装ですが、挙動の意味として):
# 修正後のイメージ
relation = User.where(active: true)
begin
relation.sole
rescue ActiveRecord::SoleRecordExceeded => e
e.record # => `relation` と同じ User::ActiveRecord_Relation が返る
end以前のリグレッションにより、e.record が Relation ではなく、他のオブジェクト(例: 実際に取得された最初のレコードなど)を指してしまうケースがありましたが、それが元通り Relation を返すようになりました。
テストの追加
activerecord/test/cases/finder_test.rb に10行のテストが追加されています。
趣旨としては:
relation = topics.where(条件...)relation.soleでActiveRecord::SoleRecordExceededを発生させる- rescue ブロック内で
error = e; end assert_equal relation, error.record
という形で、SoleRecordExceeded#record が元の Relation をそのまま返すことを明示的に保証するテストです。
これにより、今後の変更で同じリグレッションが再発しないように保護されています。
- 影響範囲・注意点
影響範囲
- 対象:
ActiveRecord::Relation#sole/#sole!を使っていて、「複数レコードヒット時に発生するActiveRecord::SoleRecordExceeded例外の#recordを利用しているコード」。- 例外の
recordからクエリ条件を再利用するような実装:rubybegin user = User.where(email: email).sole rescue ActiveRecord::SoleRecordExceeded => e # e.record を基にログ出し・デバッグ・再クエリなど Rails.logger.error("Multiple users found: #{e.record.to_sql}") end
- 例外の
期待できる挙動
- この PR 適用後:
SoleRecordExceeded#recordは必ず Relation を返すことが保証される。- 以前のバージョン(リグレッション前)と互換のある挙動に戻る。
- リグレッション期間中に
e.recordを「壊れた挙動」(Relation 以外)を前提に利用するコードを書いていた場合は、逆に今回の修正で挙動が変わりますが、これは本来の仕様に戻す修正(Bugfix)です。
注意点
- 例外から取得できるのは「複数ヒットしたうちの1件」ではなく、「検索に使われた Relation(クエリ条件)」です。
e.recordをレコードインスタンスとして扱うと誤りなので、where/to_sql/pluckなど Relation API 前提で扱う必要があります。 - ランタイムの型チェックやドキュメントを参照せずに
e.recordを直接モデルインスタンスとして扱っていた場合は、この修正で NoMethodError 等が顕在化する可能性があります(もともと仕様違反な使い方です)。
- 参考情報 (あれば)
- Issue: https://github.com/rails/rails/issues/56281
SoleRecordExceeded#recordが Relation を返さなくなったことが報告されている Issue。 - リグレッションの原因となった PR: https://github.com/rails/rails/pull/50396
sole/sole!のドキュメント:
Rails Guides または API ドキュメントの「ActiveRecord::FinderMethods#sole」を参照すると、SoleRecordExceededとrecordの意味付けが確認できます。
#56159 Restore missing Postgres type decoding
マージ日: 2025/12/4 | 作成者: @matthewd
- 概要 (1-2文で)
Rails の PostgreSQL アダプタで、moneyとbytea型の値が「公開されているクエリ実行 API (select_value/exec_queryなど)」経由でも正しくデコードされるように復元した PRです。既存アプリへの影響を最小化するための設定フラグと、PG::Connection.unescape_byteaの安全策(ダブルデコード防止)も併せて導入されています。
- 変更内容の詳細
背景
- もともと内部用メソッド
queryではmoney/byteaを Ruby の値にデコードする処理が存在していた。 - しかし Rails 4.0 の変更(コミット a92af3fbf0)で、
select_value/select_all/exec_queryなど、公開クエリ API からこの型変換が抜け落ちていた。 - モデル経由 (
Model.find, attribute 経由) の取得では attribute typecasting が補っていたため、主に以下のケースでのみ問題になっていた:select_value,select_all,exec_queryなどを直接使う場合pluck+ 生 SQL 式(モデル属性に紐付かない式)でmoney/byteaを扱う場合
この PR はその「抜け落ちた型デコード」を復元し、かつ既存コードを壊さないための互換策を入れています。
PostgreSQL アダプタでの型デコードの復元
主なポイント:
ActiveRecord::ConnectionAdapters::PostgreSQLAdapterに、公開 API での型デコードを有効にするための実装が追加。money/byteaを含む結果に対して、PostgreSQL OID(型情報)をもとに適切な OID クラス (PostgreSQL::OID::Money/PostgreSQL::OID::Bytea等) でデコードする処理が、exec_query系パスに統合された。- これにより、以下のようなコードで「Ruby らしい値」が返ってくるようになる:
# 例: money
value = ActiveRecord::Base.connection.select_value("SELECT '12.34'::money")
# 以前: ロケール依存の文字列などがそのまま返るケースがあった
# 今回: 型情報に基づいて numeric / decimal などにデコードされる
# 例: bytea
data = ActiveRecord::Base.connection.select_value("SELECT '\\xDEADBEEF'::bytea")
# 以前: エスケープ表現の文字列が返る場合があった
# 今回: Ruby のバイナリ文字列(すでに unescape 済み)が返る※実際の戻り値の型は Rails / PG ドライバ / 設定に依存しますが、「PostgreSQL ネイティブ表現 → Ruby 側で扱いやすい形へ」の変換が公開 API でも行われるようになった、というのが要点です。
PG::Connection.unescape_bytea のモンキーパッチ
この変更により問題となり得るパターン:
# これまで: select_value がエスケープされた bytea 文字列を返していた
raw = ActiveRecord::Base.connection.select_value("SELECT bytea_col FROM ...")
decoded = PG::Connection.unescape_bytea(raw) # 正しくデコードされていた
# 変更後: select_value が「すでにデコード済みのバイナリ」を返し始める
# 同じコードだと「二重 unescape」が起きて壊れる可能性があるそのため、この PR では PG::Connection.unescape_bytea をラップし、以下のような安全策を導入しています:
- Rails の設定で「select 系 API で bytea をデコードする」機能を有効にした場合でも、
- 引数が「既にデコード済みの値」(Rails 内部が付けた印などで判別)であれば、再度 unescape しない。
- つまり、既存コード
unescape_bytea(select_value(...))は「余計なことをしている」状態にはなるものの、動作が壊れないように吸収する実装になっている。
実装レベルでは PostgreSQL::OID::Bytea にマーカー情報を持たせる形で、「Rails が decode した bytea かどうか」を検知しているテストが追加されています。
設定オプション / 初期化フロー
railties 側にも変更が入っており、Rails アプリの設定からこの挙動を制御できるようになっています。
Rails::Application::Configurationに Postgres 型デコード関連の新しいフラグが追加。config/initializers/new_framework_defaults_8_2.rbのテンプレートに、Rails 8.2 での新しいデフォルト挙動としてこの機能を有効化する設定が追加。guides/source/configuring.mdにも、設定方法や挙動の違いについてのドキュメントが追記。
典型的には、以下のような設定が想定されます(名前は概念的な例・実際のキー名は PR で追加されたものに依存します):
# config/application.rb など
config.active_record.postgresql_enable_type_decoding = true新規アプリでは new_framework_defaults_8_2.rb でこのフラグがオンになる方向、既存アプリでは互換のためオフ、といった「いつもの Rails の移行パターン」に従う設計です。
テストの追加
以下のようなテストが追加・拡充されています:
activerecord/test/cases/adapters/postgresql/*_with_decoder_test.rbmoney/byteaについて、「デコード機能を有効にした際に期待通りの Ruby 値が返るか」を検証。
postgresql_adapter_test.rb- 公開 API(
select_value,select_all,exec_query等)での型デコードの有無、設定フラグによる挙動の違いを網羅的にテスト。
- 公開 API(
railties/test/application/configuration_test.rb- 設定フラグのデフォルト値、
new_framework_defaultsによる切り替えを検証。
- 設定フラグのデフォルト値、
- 影響範囲・注意点
影響があるコードパターン
AR モデルを経由しない生クエリ実行で
money/byteaを扱っている場合:select_value/select_all/exec_queryなどでmoney/byteaカラム(またはそれらを返す式)を直接取得しているコードは、戻り値の型・内容が「より正しくデコードされた値」に変わる可能性があります。- 特に
byteaは、文字列エスケープを前提にした独自処理を書いていると、二重デコードなどの不整合が起きかねないので注意が必要です。
PG::Connection.unescape_byteaを明示的に呼んでいるコード:- 典型的なパターン:
PG::Connection.unescape_bytea(ActiveRecord::Base.connection.select_value(...)) - 新挙動有効化後も壊れないよう PR 側で防御策を入れていますが、
将来的にはこのような「手動 unescape」は不要になるため、
デコード済み値を前提にコードを整理していく方が安全です。
- 典型的なパターン:
pluckで生 SQL を使っている箇所:- 例:
Model.pluck("price_in_cents::money"),Model.pluck("encode(bytea_col, 'hex')")など - これまでは attribute typecasting が効かず「生 Postgres 表現に近い値」が返っていたものが、
型デコードにより Ruby 側の型に寄せて返る可能性があります。 - 期待する値のフォーマットに依存したロジックがある場合には確認が必要です。
- 例:
互換性と移行のポイント
- Rails 8.2 系で導入される新デフォルトとしては「型デコード有効」が目指されているが、
既存アプリ向けにはnew_framework_defaults_8_2.rbでフラグを明示的にオンにするステップを踏む形で、段階的な移行ができる。 - すぐに新挙動を使いたくない場合:
config/application.rbまたはnew_framework_defaults_8_2.rbでフラグをオフにしておけば、
これまでと同じ「ほぼデコードなし」の挙動を維持できる。
- 新挙動を有効にする際の推奨手順:
- テスト環境でフラグをオンにして CI を回す。
- ログ検索・grep などで
PG::Connection.unescape_byteaの利用箇所を洗い出す。 select_value/exec_queryを使ってmoney/byteaを取得している箇所の戻り値を確認し、
文字列前提のパース処理などがないかチェックする。- 問題がなければ本番でもフラグをオンにする。
- 参考情報 (あれば)
- 対象 PR: https://github.com/rails/rails/pull/56159
- 過去の実装参照(内部
queryでのみデコードしていた頃):
https://github.com/rails/rails/blob/077c3ad60db4c43cccb8f4637b53b8d8eb7a3c19/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb#L1167-L1168 - 設定ガイド (
configuring.md) に、このフラグや PostgreSQL の型デコード周りの説明が追記されているので、アプリ側の設定方針を決める際に参照するとよいです。
#56267 Move async execution into QueryIntent
マージ日: 2025/12/4 | 作成者: @matthewd
- 概要 (1–2文で)
- Active Record の非同期実行まわりの内部構造が変更され、実際のクエリ実行ロジックを
FutureResultからQueryIntentに移し、コネクションプールの非同期キューにはQueryIntentを積むようになりました。 - これにより、
FutureResultはあくまでユーザー向けの結果ハンドラに近い役割になり、非同期実行の責務がより明確に分離されています。
- 変更内容の詳細
全体像の変化
Before
- 非同期ロード(
load_asyncなど)を行うと、コネクションプールの async キューにはFutureResultが積まれ、その中でクエリ実行ロジックを持っていました。 FutureResultが「キューイングされる実行単位」兼「結果を待ち受けるオブジェクト」という二重の役割を持っていた状態。
Now
- コネクションプールの async キューには
QueryIntentインスタンスが積まれます。 - 実際のクエリ実行・例外処理・接続ハンドリングなどの責務は
QueryIntentに集約され、FutureResultは結果を表すラッパ・フロントオブジェクトとして振る舞うだけになります。 - ユーザーコード視点では引き続き
FutureResultを通じて#result等で結果を取得しますが、その内部実装が大きく簡素化されています。
ファイルごとの主な変更ポイント
activerecord/lib/active_record/connection_adapters/query_intent.rb (+185 / -19)
この PR の中心。
QueryIntentに非同期実行のロジックが大きく追加されています。典型的には以下のような責務が
QueryIntent側に寄せられたと考えられます(擬似的なイメージ):rubyclass QueryIntent def initialize(connection, sql, binds, ...) @connection = connection @sql = sql @binds = binds # ... end # 非同期ワーカーから呼ばれる実行メソッド def perform # コネクションの checkout / execute / ensure での cleanup など result = @connection.exec_query(@sql, "Load Async", @binds) fulfill(result) # FutureResult などに結果を渡す rescue => e reject(e) # 例外を FutureResult 側に伝搬 end end実行中・完了・失敗などの状態管理や、コネクションの扱い、例外処理も
QueryIntentが担う形に整理された可能性が高いです。
activerecord/lib/active_record/future_result.rb (+7 / -128)
- 行数が大きく減っており、実行ロジックがほぼ削除されています。
- 役割は以下のように単純化されたと思ってよいです:
- 「この非同期クエリの結果をあとで受け取る」ためのオブジェクト。
#resultでブロックしつつ結果を返す。- 例外が発生していれば
#result呼び出し時に再スローする。
- 具体的には、
FutureResultが保持していた「キューに積まれ、実行されるためのインターフェイス」はなくなり、QueryIntentのライフサイクルにぶら下がるだけになっています。 - ユーザー API (
relation.load_async,future.resultなど) の表面上の振る舞いは基本的に従来と互換のはずですが、裏側の管理単位がFutureResult→QueryIntentにシフトしています。
abstract/database_statements.rb ほか各アダプタの database_statements.rb
+5/-19など小さな修正ですが、以下のような変更が考えられます:- 非同期クエリ作成時に
FutureResult.new(...)していた部分が、QueryIntent.new(...)を作り、それに紐づくFutureResultを返すような形に変更。 - もしくは、
QueryIntent自体がto_future_resultのようなメソッドを持ち、呼び出し元にはFutureResultだけが見えるようにするパターン。
擬似コード例:
ruby# 旧実装イメージ def select_all_async(...) future = FutureResult.new(self, sql, binds, ...) pool.enqueue(future) future end # 新実装イメージ def select_all_async(...) intent = QueryIntent.new(self, sql, binds, ...) pool.enqueue(intent) intent.future_result end- 非同期クエリ作成時に
MySQL2 / PostgreSQL / SQLite3 / Trilogy の各 DB アダプタにも 1 行ずつの調整が入っており、共通の非同期インターフェイスに従うように整えられています。
activerecord/lib/active_record/connection_adapters/abstract_adapter.rb (+1 / -1)
- 抽象アダプタ層の非同期関連メソッド(
select_all_async等)で、QueryIntentベースの実装に合わせた微修正が入っています。 - ここで非同期フローのエントリーポイントが
QueryIntentを作るようになっているはずです。
activerecord/test/cases/relation/load_async_test.rb (+1 / -1)
- テストはほぼそのままで、1 行だけの修正にとどまっています。
- これは「外部仕様(
load_asyncの振る舞い)は変わっていない」ことの裏付けとも言えます。
- 影響範囲・注意点
アプリケーション開発者への影響
- 公開 API レベル(
Relation#load_async,FutureResult#resultなど)は原則として互換であり、通常のアプリケーションコードは変更不要と思って問題ありません。 - ただし、以下に該当する場合は注意が必要です:
- ActiveRecord の非同期実行内部 (
FutureResultの内部状態やコールバック) に依存したメタプログラミングをしている。 FutureResultを継承 / モンキーパッチして非標準な挙動を追加している。- コネクションプールの async キュー内部(キューに載るオブジェクトのクラスなど)を前提にした独自コードを書いている。
- ActiveRecord の非同期実行内部 (
- そのような場合、今後は
QueryIntentが実行単位であることを前提に実装・パッチを書き換える必要があります。
ライブラリ・フレームワーク作者への影響
- ActiveRecord の非同期クエリをラップしている gem / フレームワーク (GraphQL の
load_async連携など) で、FutureResultの内部のメソッドやインスタンス変数を直接触っている場合は互換性確認が必要です。 - 将来的に非同期実行の拡張(キャンセル、タイムアウト、優先度制御など)を行う場合は、
QueryIntentが拡張ポイントになる可能性が高く、FutureResultではなくQueryIntentの API に注目すべきです。
パフォーマンス・安定性の観点
- 実行の責務が
QueryIntentに移ったことで、以下のようなメリットが狙われていると考えられます:- 責務分離によりコードが読みやすくなり、バグが減る。
- 将来的な最適化(実行戦略の変更、バッチ化、より賢いスケジューリングなど)を
QueryIntentに閉じた形で行いやすくなる。
- この PR 自体は大規模な挙動変更を意図していないようですが、実装が大きく書き換わっているため、非同期クエリを多用するアプリではアップグレード後に実働環境での負荷テストや例外パターンの確認を推奨します。
- 参考情報 (あれば)
- PR タイトル: Move async execution into QueryIntent (#56267)
- 関連クラス:
ActiveRecord::FutureResult— ユーザーが扱う非同期結果オブジェクト(インターフェイスは継続、内部は簡素化)。ActiveRecord::ConnectionAdapters::QueryIntent— 非同期クエリ実行の実体となるオブジェクト。今回の変更で責務が集中。
- PR に添付された mermaid 図(Before / Now)は、以下を示しています:
- 以前は
FutureResultが非同期ワーカーに直接渡されていた。 - 現在は
QueryIntentが非同期ワーカーに渡され、その完了結果がFutureResultに反映される構造に分割されている。
- 以前は
この PR は、「非同期実行の内部設計を整理するリファクタリング」であり、将来の非同期関連機能拡張の基盤整備、と捉えるのが適切です。
#56229 [docs] Update documentation for block-level scoping [ci-skip]
マージ日: 2025/12/3 | 作成者: @shivabhusal
- 概要 (1-2文で)
ActiveRecord::Relation#scopingの挙動、とくにall_queries: trueを使ったブロックレベルスコープの動きやネスト時の挙動について、Rails Guides(Active Record クエリインターフェイスガイド)に明確な説明とサンプルを追加したドキュメント専用のPRです。コード本体の挙動変更はなく、ガイドの追記のみです。
- 変更内容の詳細
対象ファイル:
guides/source/active_record_querying.mdに約 49 行程度のドキュメント追記
主な追加内容:
2-1. Relation#scoping の基本的な説明強化
Model.where(...).scoping { ... }形式で「一時的に」デフォルトスコープのような条件を適用できる、という点をガイドで明示。- ブロック内で発行されるクエリに対して、その
Relationが持つ条件やincludes,joins,orderなどが反映されることを説明。
例(典型的なイメージ・ガイドに沿った内容のサンプル):
Post.where(published: true).scoping do
Post.first
Post.where(user_id: 1).to_a
end上記ブロック内では、暗黙的に WHERE published = TRUE が掛かったクエリが発行される、という旨を SQL 例付きで説明しています。
2-2. all_queries: true オプションの挙動の明確化
このPRの中心は Relation#scoping(all_queries: true) の説明です。
- 通常の
scopingは「同じモデルのクエリ」にだけ適用されることが明示されます。- 例:
Post.where(...).scopingの中でCommentをクエリしてもスコープは適用されない。
- 例:
all_queries: trueを指定すると、ブロック内で発行される「すべてのモデルのクエリ」に対してスコープが影響する、という挙動を説明。
イメージとなるサンプル:
Post.where(published: true).scoping(all_queries: true) do
Post.first # => WHERE published = TRUE が付く
Comment.first # => Post のスコープがかからないのが従来だが、
# all_queries: true の場合は、AR::Base レベルでの条件が
# どのモデルにも適用されうる、という趣旨が説明される
end※実装としては ActiveRecord::Base.scoping との関係などがありますが、ガイドでは「どのモデルのクエリにもスコープが及びうる」という使い方・注意点に焦点を当てています。
ガイドでは、これに対する具体的な SQL 例(SELECT ~ FROM ~ WHERE ...)を併記し、「all_queries: true を付けた場合に、どのクエリに WHERE 句が乗るのか」を視覚的に理解できるようになっています。
2-3. ネストされたスコープの挙動の説明
Relation#scopingをネストさせた場合、内側のスコープが外側のスコープを「上書き」するのか「マージ」されるのか、といった挙動に関する説明や例を追加。- 一般的には Relation のマージルールに従う(
where条件は AND マージ、orderは後勝ちなど)ため、その点を含めて例で示しています。
例(趣旨イメージ):
Post.where(published: true).scoping do
Post.where(archived: false).scoping do
Post.first
end
end- 発行される SQL は
WHERE published = TRUE AND archived = FALSEのように、ネストされたスコープが組み合わさる形になることを説明。 - また、
orderやlimitなどについても、内側のスコープで指定したものがどのように作用するかに簡単に触れていると考えられます。
2-4. 使いどころ・注意に関する簡単なガイダンス
ガイド内で、以下のような「使い方のヒント」も含めています:
- ブロックレベルで一時的にスコープを切り替えたい場合に
scopingを使うと便利であること。 default_scopeの代わりに「明示的な」スコープを適用する用途としても使えること。- ただし
all_queries: trueは影響範囲が広く、意図しないクエリに条件が掛かる可能性があるため注意すべき、という旨の注意喚起。
- 影響範囲・注意点
- これは ドキュメントのみ の変更であり、
Relation#scopingの実装には一切変更がありません。 - 既存コードの挙動・パフォーマンスには影響しません。
- 影響があるのは「開発者がこのガイドを読んだときの理解・使い方」であり、特に:
- これまで
scoping/all_queries: trueの挙動が曖昧だった部分が明文化される。 all_queries: trueの利用を検討する際に、影響範囲やネスト時の挙動を事前に把握しやすくなる。
- これまで
- 強力なため、テストコードや一部のバッチ処理などで
all_queries: trueを多用する場合、ガイドのサンプルを参考にしながら「どこまで影響が及ぶか」を確認すると安全です。
- 参考情報 (あれば)
- 対象 API:
ActiveRecord::Relation#scoping
https://edgeapi.rubyonrails.org/classes/ActiveRecord/Relation.html#method-i-scoping
- PR のプレビュー(ガイド本文の日本語訳を作る際などの参考に):
#56275 Use Bundler.with_unbundled_env for GeneratorsTestHelper run_app_update
マージ日: 2025/12/3 | 作成者: @zzak
- 概要 (1-2文で)
Generators のテストヘルパーでrun_app_updateを実行する際に、Bundler の影響を受けないようにBundler.with_unbundled_envを使うように変更した PR です。これにより、bundle exec配下でテストを実行しても、アプリ更新用コマンドが意図しない Gem 環境に縛られずに動くようになります。
- 変更内容の詳細
対象ファイル:
railties/test/generators/generators_test_helper.rb
GeneratorsTestHelper 内の run_app_update メソッドで、Bundler.with_unbundled_env を使用するようになりました。概ね以下のような変更です(イメージ・擬似コード):
def run_app_update(*args)
# 変更前(イメージ)
# system(app_update_command(*args))
# 変更後
Bundler.with_unbundled_env do
system(app_update_command(*args))
end
endポイント:
Bundler.with_unbundled_envブロック内では、BUNDLE_GEMFILEやBUNDLE_BIN_PATHなど Bundler が設定する環境変数がクリアされ、いわゆる「素の」Ruby/Gem 環境でコマンドが実行されます。- Generators のテストは、テンプレートアプリケーションを生成・更新するようなコマンド(
bin/rails app:update相当)を内部的に実行しますが、それらが「テストフレームワーク側の Bundler 環境」に引きずられないようにしています。
- 影響範囲・注意点
影響範囲
- Rails 本体のランタイム挙動ではなく、「Generators 関連のテストコード」に限定された変更です。
- 主に Rails 開発者・コントリビュータ、および Rails を fork して独自にテストを回している人が対象になります。
- CI などで
bundle exec ruby -Itest ...のように Bundler 経由でテストを走らせている場合に、Generators のテストがより安定・再現性の高い形で動作するようになります。
解決している問題の方向性
- Issue #56271 で報告された、
run_app_update実行時に Bundler による環境変数や Gem ロードパスが干渉して失敗・不整合が起きる問題を緩和/解消する意図があります。 - 典型的には、テスト実行側の
Gemfileに縛られて、本来想定していない Gem バージョンでapp:updateが走ってしまう、といった症状を防ぎます。
- Issue #56271 で報告された、
注意点
Bundler.with_unbundled_envを使うと、ブロック内では「現在の Bundler コンテキスト外」の世界になるため、その中で別のbundle execを前提とするような処理を追加すると挙動が変わる可能性があります。- ただし、このメソッドはテスト専用ヘルパー内で完結しているため、一般的な Rails アプリケーション開発者がこの PR を意識する必要はほぼありません。
- 参考情報 (あれば)
- 該当 PR: https://github.com/rails/rails/pull/56275
- 紐付く Issue: https://github.com/rails/rails/issues/56271
Bundler.with_unbundled_envドキュメント:- https://bundler.io/v2.5/guides/bundler_integration.html#running-commands-in-a-clean-environment(「Running commands in a clean environment」)
#56272 Skip all system test files on app generation
マージ日: 2025/12/2 | 作成者: @eileencodes
- 概要 (1-2文で)
Rails アプリ生成時に常に作られていたapplication_system_test_case.rbを、システムテストを生成したときに限って作成するように変更した PR です。scaffold でのシステムテストスキップ対応 (#55743) の取りこぼしを補完し、不要なシステムテスト関連ファイルが生成されないようにしています。
- 変更内容の詳細
背景
- 以前の PR (#55743) で「scaffold 実行時に system test を生成しない」ようにしたが、
rails new(アプリ生成)時には依然としてtest/application_system_test_case.rbが自動生成されていた。
- システムテストを使わないプロジェクトでも、このファイルが毎回作られてしまうのは一貫性がなく、ノイズになる。
主な変更点
1. アプリ生成時の system test ファイル生成ロジックの変更
対象: railties/lib/rails/generators/rails/app/app_generator.rb
- これまで:
rails newでアプリを作ると、デフォルトでtest/application_system_test_case.rbが生成されていた。
- これから:
application_system_test_case.rbは 「システムテストを生成するとき、かつまだ存在しない場合」 にのみ生成されるようになる。- つまり、
rails new直後にはこのファイルは存在せず、rails g system_test <name>などを実行したときに初めてapplication_system_test_case.rbが生成される、という動作になる。
この条件分岐は app generator 内のテンプレートコピー条件の見直し・削除/移動で実現されており、app 生成そのものでは system test 用のベースクラスを作らなくなっています。
2. scaffold generator 側での system test ベースクラステンプレート追加
対象:
railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rbrailties/lib/rails/generators/test_unit/scaffold/templates/application_system_test_case.rb.ttscaffold 用の test_unit generator に、
application_system_test_case.rb用のテンプレート (.tt) を追加。scaffold から system test を生成する場合、このテンプレートを使って
test/application_system_test_case.rbを作成するようにしている。中身は通常の
ApplicationSystemTestCase定義で、例えば以下のような構成になります(イメージ):ruby# test/application_system_test_case.rb require "test_helper" class ApplicationSystemTestCase < ActionDispatch::SystemTestCase driven_by :selenium, using: :chrome, screen_size: [1400, 1400] end※ 実際のオプション値は Rails の標準テンプレートに準拠。
3. テストの追加・更新
対象:
railties/test/application/test_runner_test.rb(+22)railties/test/commands/devcontainer_test.rb(+8)railties/test/generators/app_generator_test.rb(-9)
主なポイント:
app generator の振る舞い変更に合わせたテスト
rails new実行後にtest/application_system_test_case.rbが存在しないこと、- system test 生成時に必要に応じて生成されること
を確認するテストが追加/修正されています。
devcontainer 関連テストの更新
- devcontainer 環境での test 実行やファイル有無の前提が変わる影響を受けるため、
test/commands/devcontainer_test.rbにも検証が追加されています。
- devcontainer 環境での test 実行やファイル有無の前提が変わる影響を受けるため、
既存の app generator テストから、もはや生成されない
application_system_test_case.rbを前提としたアサーションが削除されています (-9 行)。
- 影響範囲・注意点
新規プロジェクト (
rails new)- これまで:
test/application_system_test_case.rbが最初から存在した。 - これから: デフォルトでは存在しない。
- システムテストを使う場合は:
rails g system_test <name>を実行すれば、自動的にapplication_system_test_case.rbも生成される(まだ存在しない場合)。- あるいは自前で作成してもよい。
- これまで:
既存プロジェクトへの影響
- 既存アプリ内にすでに
application_system_test_case.rbがある場合、この PR は生成ロジックの変更のみであり、既存ファイルには影響しない。 - CI 設定やドキュメントなどで「
rails new直後にapplication_system_test_case.rbがある前提」を書いている場合は、前提が変わるため更新が必要なことがある。
- 既存アプリ内にすでに
ジェネレータ拡張/テンプレートをいじっている場合
- 自作の generator が Rails 標準の app generator / scaffold generator の挙動に依存している場合、
- 「ベースのシステムテストケースファイルはアプリ生成時に必ず存在する」
という前提はもはや成り立たないので、必要に応じて- 存在チェック (
File.exist?など) - なければ生成
を行うようにする必要があります。
- 存在チェック (
- 「ベースのシステムテストケースファイルはアプリ生成時に必ず存在する」
- 自作の generator が Rails 標準の app generator / scaffold generator の挙動に依存している場合、
- 参考情報 (あれば)
関連 PR:
- システムテスト生成のスキップ対応: https://github.com/rails/rails/pull/55743
- 本 PR の問題発見に繋がった PR: https://github.com/rails/rails/pull/56252
該当 PR:
- Skip all system test files on app generation (#56272)
https://github.com/rails/rails/pull/56272
- Skip all system test files on app generation (#56272)
#56269 Add a top level bin/test
マージ日: 2025/12/2 | 作成者: @byroot
- 概要 (1-2文で)
Rails リポジトリのルートにbin/testが追加され、サブディレクトリへcdしなくても各サブプロジェクト(activemodel, activerecord など)のテストを直接実行できるようになりました。あわせて、失敗テストの「再実行コマンド」もリポジトリルートからそのままコピペで使える形に改善されています。
- 変更内容の詳細
2-1. bin/test の新規追加
リポジトリ直下に bin/test が追加され、Rails コアの各コンポーネントのテストを簡単に実行できるようになりました。
使い方の例(PR本文のまま):
# コンポーネント単位のテスト
$ bin/test activemodel
...
# 特定ファイルだけを実行
$ bin/test activemodel/test/cases/dirty_test.rb
...
# 複数ファイル + 任意のオプション(ここでは -v)を付けて実行
$ bin/test activemodel/test/cases/dirty_test.rb activemodel/test/cases/error_test.rb -v
...特徴:
- リポジトリルートから実行可能
いちいちcd activemodelやcd activerecordせずにテストを叩ける。 - 単一サブプロジェクト内でのテスト実行を想定
一回のコマンドで「activemodel と activerecord を同時に」など、複数サブプロジェクトを混在させた実行はサポートしていないと明記されています。 - おそらく内部的には既存の
tools/test.rb/tools/test_common.rbを呼び出す薄いラッパーになっており、引数に応じて対象コンポーネントを解決している形です。
bin/test の追加によって、これまで
cd activemodel && bin/test test/cases/dirty_test.rbのようにしていた作業が
bin/test activemodel/test/cases/dirty_test.rbだけで済むようになります。
2-2. テストレポーター (reporter.rb) の修正
railties/lib/rails/test_unit/reporter.rb が変更され、Rails 自身のテストを実行したときに表示される「失敗テストの再実行コマンド」が、ルートディレクトリからそのまま使える形に変わりました。
従来は、CI やローカルで失敗したときに、次のようなコマンドが表示されていました:
bin/rails test /rails/activerecord/test/cases/adapters/postgresql/connection_test.rb:187- 絶対パス (
/rails/...) になっていて、そのままコピペしても意図通りに動きにくい。 - 実行パスも
bin/railsで、コンポーネント単体テストを回す文脈とやや合わない。
これが PR によって、以下のようになります:
bin/test activerecord/test/cases/adapters/postgresql/connection_test.rb:187- リポジトリルートからの相対パスになり、そのままコピペで実行可能。
- コマンドが
bin/testに統一され、今回追加されたワークフローと整合します。
2-3. tools/test.rb / tools/test_common.rb の微修正
tools/test.rb と tools/test_common.rb にはそれぞれ 1〜2 行程度の軽微な修正が入っています。
推測される内容:
bin/testからの呼び出しをサポートするために、エントリーポイントの扱いや引数のパース方法を少しだけ調整。- コンポーネント名(
activemodel,activerecordなど)とパスの解決ロジックをbin/testと共有するための小さな共通化。
このあたりは内部実装の整合性調整であり、外部 API/インターフェースとしては主に bin/test の追加とレポーターの出力変更が本質的な変更です。
- 影響範囲・注意点
- 対象は Rails リポジトリの開発者・コントリビューター
Rails を利用するアプリ開発者向けではなく、「Rails 本体のテストを書く・直す人」の開発体験改善が主目的です。 - 既存のテスト実行方法は引き続き使用可能
cd activerecord && bundle exec ruby -Itest test/cases/...のような既存手法は変わらず動作し、bin/testは追加のエイリアス・ラッパーの位置づけです。 - 複数サブプロジェクトへの同時テスト実行はサポート外
一回のbin/test呼び出しでactivemodelとactiverecordのテストを同時に指定するような使い方は想定されていません。
→ そのようなケースでは、これまで通り各ディレクトリで個別に実行するか、独自スクリプトを使う必要があります。 - CI ログやローカル実行時の「再実行コマンド」が変わる
今後はbin/test ...形式のコマンドが出力されるため、ドキュメント・社内 Wiki 等で「失敗テストの再実行方法」を案内している場合は、必要に応じて記述を更新するとよいです。 - パス前提の変化
再実行コマンドは「リポジトリルートからの相対パス」を前提にしています。そのため、コマンドをコピペするときは「Rails リポジトリのルートで実行しているか」を意識する必要があります。
- 参考情報 (あれば)
- PR 本文: Add a top level bin/test (#56269)
- CI の実行例(Buildkite):
https://buildkite.com/rails/rails/builds/124229#019ade2f-ecf0-485f-ba07-5e26f23fa3e9/1245-1259
→ 実際にbin/testベースの再実行コマンドが出ているログを確認できます。 - 関連ファイル:
bin/test(新規追加スクリプト)railties/lib/rails/test_unit/reporter.rb(再実行コマンドの出力元)tools/test.rb,tools/test_common.rb(既存のテスト実行ユーティリティ)
#56265 Stub bundle install for ApiAppGeneratorTest
マージ日: 2025/12/1 | 作成者: @zzak
- 概要 (1-2文で)
Api アプリケーションジェネレータのテスト (ApiAppGeneratorTest) で、bundle install(正確にはkamal実行時の Bundler 解決)に依存していた部分をスタブ化し、テストが外部の gem 解決に依存しないようにした PR です。これによりテスト時間が約 3 秒 → 約 1 秒に短縮され、ログノイズも解消されています。
- 変更内容の詳細
何をしている PR か
railties/test/generators/api_app_generator_test.rbの特定のテストに対して、bundle installを実行したかのように振る舞うスタブを追加- 実際には gem 解決やインストールを行わないようにしている
- これは既に他のテスト(
PluginGeneratorTest,ActionTest::Generators::InstallGeneratorTest)で採用されている「ジェネレータの副作用をスタブするパターン」と揃えたものです。
もともと問題になっていたのは:
- Api ジェネレータのテストの中で
kamalを使った何らかの処理(おそらくkamalコマンドを実行する想定のテンプレート or ジェネレータ)があり、 - 実行時に Bundler が
rails (~> 8.2.0.alpha)を解決しようとして失敗 - その失敗メッセージが標準出力/エラーに大量に出る上、Bundler の解決処理によりテストが余計に遅くなる
という状況でした。
PR の説明文にあるログから分かるポイント:
- テストは「失敗はしていない」が、内部で
kamal+ Bundler が動いてエラーを吐いているだけ、という状態 - Bundler が
rails (~> 8.2.0.alpha)の解決を延々試み、膨大なバージョンリストを出力している - 最後に
Bundler::GemNotFoundが発生し、kamal実行が失敗しているが、テスト側はそのエラーを結果としては無視できる形になっていた
この PR で行われたこと(推測を含むが Rails 他テストのパターン的にかなり確度が高い):
該当テストケースで、ジェネレータが呼び出す
kamal/bundleの実行部分をテストダブルに差し替え例(イメージ):
rubydef stub_kamal File.write("bin/kamal", <<~RUBY) #!/usr/bin/env ruby # no-op for tests RUBY FileUtils.chmod("+x", "bin/kamal") endもしくは、システムコール層のスタブ:
rubyKamal::Commands.stub(:run, true) do # テスト本体 end
実際に Bundler を起動せず、ジェネレータとして「kamal 向けの設定/ファイルを正しく出力しているか」だけを検証するようにしている
スタブ化により、ログで出ていた
Could not find gem 'rails (~> 8.2.0.alpha)'のようなメッセージが一切出なくなり、テストのアサーション数も 6 → 8 に増えているので、スタブした結果をより細かく検証するアサーションが追加された可能性が高いです。
Before / After の違い(テスト観点)
- Before:
- 実質的に Bundler / kamal を起動してしまう
- 失敗ログを大量に吐きつつ、最終的なテスト結果としてはパスしている
- 実行時間: 約 3.0 秒
- アサーション: 6 件
- After:
- Bundler / kamal 実行をスタブ
- ログは一切出ずクリーン
- 実行時間: 約 1.2 秒
- アサーション: 8 件(スタブが正しく呼ばれたこと/生成物の検証が増えた)
- 影響範囲・注意点
- 影響範囲
- Rails 本体のプロダクションコードには影響せず、「railties のテストコードのみ」が対象です。
- 特に
ApiAppGeneratorがkamal用のファイル/設定をどう生成するかをテストする部分のみが変更されています。
- 良くなった点
- テストが外部の gem レジストリ状態(
rails 8.2.0.alphaがインストールされているかどうか)に依存しなくなる - CI / ローカル環境共にテストが安定し、ノイズログがなくなる
- テスト実行時間の短縮
- テストが外部の gem レジストリ状態(
- 注意点
- スタブにより「実際に
kamalコマンドを走らせたときの挙動」はこのユニットテストではカバーしない形になっています。- これは他のジェネレータテストと同じ方針で、「ジェネレータの責務(ファイル生成やコマンド呼び出しのセットアップ)だけ」をテストし、外部ツールの実際の挙動はそのツール自身のテストに任せる、という設計です。
kamal側のインターフェースが変わった場合は、このテストだけでは気付きにくくなるので、- 統合テストや手動検証など、別レイヤーのテストで補完する必要があります。
- スタブにより「実際に
- 参考情報 (あれば)
- 類似パターンがすでに存在するテスト:
PluginGeneratorTestActionTest::Generators::InstallGeneratorTest
- テストがスタブしている対象は、
bundle installや外部 CLI(kamal)の実行部分- Rails のジェネレータテストでは「外部コマンド呼び出しを no-op 化して、生成されたファイルのみ検証する」というパターンが広く使われています。
#56256 Include HTTP_FORWARDED header in IpSpoofAttackError message if available
マージ日: 2025/12/1 | 作成者: @zzak
- 概要 (1-2文で)
Rails のIpSpoofAttackErrorが発生した際のエラーメッセージに、従来のX-Forwarded-Forに加えてForwarded(HTTP_FORWARDED) ヘッダの値も含められるようになりました。これにより、IP なりすまし検出時のデバッグ情報がより充実します。
- 変更内容の詳細
どの部分が変わったか
変更ファイル:
actionpack/lib/action_dispatch/middleware/remote_ip.rbactionpack/test/dispatch/request_test.rb
ActionDispatch::RemoteIp ミドルウェア内で IpSpoofAttackError を生成するときのメッセージに、利用可能であれば HTTP_FORWARDED ヘッダを含めるように修正されています。
従来はおおむね次のようなメッセージでした(例):
IP spoofing attack?! HTTP_CLIENT_IP="1.2.3.4" HTTP_X_FORWARDED_FOR="5.6.7.8"この PR により、Forwarded ヘッダがある場合は:
IP spoofing attack?! HTTP_CLIENT_IP="1.2.3.4" HTTP_X_FORWARDED_FOR="5.6.7.8" HTTP_FORWARDED="for=9.10.11.12;proto=https"のように HTTP_FORWARDED もメッセージに追加されます。
コード上のイメージ
※PR 内容からの推定を含むサンプルです(実際の実装のニュアンスとして):
# 例: エラー生成部分のイメージ
raise IpSpoofAttackError.new("IP spoofing attack?! " \
"HTTP_CLIENT_IP=#{@client_ip.inspect} " \
"HTTP_X_FORWARDED_FOR=#{forwarded_for.inspect}" \
"#{forwarded_header_part}"
)
# forwarded_header_part は HTTP_FORWARDED があれば
# ' HTTP_FORWARDED="..."' を付与するような処理テスト (request_test.rb) では、HTTP_FORWARDED を含むリクエストヘッダで IP spoofing 状況を作り、その例外メッセージ内に HTTP_FORWARDED の値が含まれていることを検証するテストが追加されています。
- 影響範囲・注意点
影響範囲
ActionDispatch::RemoteIpを使用していて、かつ IP なりすまし (spoofing) 判定が走った場合の「例外メッセージ」が変わります。- ランタイムの挙動(例外が発生するかどうか、どの条件で発生するか)自体は変わっていません。あくまでエラーメッセージの情報量追加です。
ログ解析・監視との互換性
IpSpoofAttackErrorのメッセージ文字列をパースして独自に解析している場合、HTTP_FORWARDEDの追加でフォーマットが変わる可能性があります。- 「メッセージ全体の完全一致 (exact match)」でアラート設定やテストを書いている場合は、これを機に「部分一致」や「正規表現ベース」に変更した方が安全です。
Forwarded ヘッダを利用している環境
- RFC 7239 準拠の
Forwardedヘッダを積極的に使っているリバースプロキシ構成(例: 一部の CDN / LB)では、IP spoofing 検出時のトラブルシューティングがしやすくなります。 - どのヘッダでどの IP が渡されているかが 1 つの例外メッセージにまとまるため、プロキシの設定ミスや想定外のヘッダ上書きの発見に役立ちます。
- RFC 7239 準拠の
- 参考情報 (あれば)
- 関連 Issue:
#56186- この PR は、
IpSpoofAttackErrorにForwardedヘッダ情報が含まれないためデバッグが難しい、という報告を解消するものです。
- この PR は、
- 対象コンポーネント:
ActionDispatch::RemoteIpミドルウェアIpSpoofAttackError例外クラス
- 標準化された
Forwardedヘッダ仕様:- RFC 7239 – Forwarded HTTP Extension (プロキシチェーン経由のクライアント情報の標準的な表現方法)
#56264 ActiveJob.perform_all_later should respect job_class.enqueue_after_transaction_commit
マージ日: 2025/12/1 | 作成者: @byroot
- 概要 (1-2文で)
ActiveJob.perform_all_laterが、ジョブクラスに設定されたenqueue_after_transaction_commitを正しく尊重するように修正された PR です。これにより、トランザクション完了後にキュー投入すべきジョブが、perform_all_later経由でも期待通りに動作します。
- 変更内容の詳細
背景
- Rails には、
enqueue_after_transaction_commitを有効化することで「DBトランザクションがコミットされるまでジョブの enqueue を遅延させる」仕組みがあります。 - 従来は
perform_laterではこの設定が反映されていた一方で、複数ジョブを一括で投入するActiveJob.perform_all_later(jobs)使用時には、このフラグが正しく反映されていませんでした。 - その結果、本来は「コミット後に enqueue」されるべきジョブが、トランザクション中に即座にキューへ入ってしまう不整合が発生し得ました。
コードレベルの変更点
1) enqueue_after_transaction_commit 対応ロジックの拡張
activejob/lib/active_job/enqueue_after_transaction_commit.rb に、perform_all_later 用の処理が追加されています。
ざっくりいうと、以下のようなことをやっています:
- 各ジョブクラスの
enqueue_after_transaction_commit設定を確認 trueのジョブについては「トランザクションコミット後に enqueue するためのフック」に登録- それ以外は従来通り即 enqueue
イメージとしては、perform_later と同じようなラッピングロジックを、配列形式の複数ジョブに対して適用する形になっています。
参考となる利用イメージ(疑似コード):
class MyJob < ApplicationJob
self.enqueue_after_transaction_commit = true
def perform(record_id)
# ...
end
end
ActiveRecord::Base.transaction do
jobs = [MyJob.new(1), MyJob.new(2)]
ActiveJob.perform_all_later(jobs)
# → ここではまだ実際のキューには入らず、
# トランザクションがコミットされた後に enqueue される
end2) テストの追加
activejob/test/cases/enqueue_after_transaction_commit_test.rb に 34 行のテストが追加されています。
主に以下を検証しています:
enqueue_after_transaction_commit = trueなジョブをperform_all_laterで enqueue した場合:- トランザクション中は実際のアダプタへ enqueue されない
- コミット後に enqueue される
enqueue_after_transaction_commit = falseor 未設定のジョブは、perform_all_laterでも即時 enqueue される
これにより perform_later と perform_all_later 間での挙動の一貫性がテストで担保されています。
- 影響範囲・注意点
影響を受けるケース
enqueue_after_transaction_commitを有効にしている ActiveJob クラスを定義しており- かつ
ActiveJob.perform_all_later(jobs)を使用しているコード
このようなコードは、これまで「トランザクション中に enqueue されていた」ものが、この変更により「トランザクションコミット後に enqueue される」ようになります。
既存コードへの実質的な変更
- これまでが「バグ寄りの挙動」であり、
enqueue_after_transaction_commitの契約通りに動いていなかったため、修正後の挙動のほうが API の意図に沿っています。 - ただし、もしこの「バグ前提の挙動」(トランザクション中に enqueue されること)に依存したコードやテストがある場合は、動作タイミングが変わるため注意が必要です。
- これまでが「バグ寄りの挙動」であり、
ジョブの順序・実行タイミング
- トランザクションがロールバックされた場合、
enqueue_after_transaction_commit = trueのジョブは enqueue されません。 - この特性は元々の
perform_laterと同じであり、perform_all_laterがこれに揃えられます。
- トランザクションがロールバックされた場合、
- 参考情報 (あれば)
- 修正元 PR(バグ報告 / 初期修正案): https://github.com/rails/rails/pull/56246
- この PR はオリジナル作者がメンテナによる force push を許可していなかったため、本 PR (#56264) としてやり直されています。
- 機能そのものの背景:
- Rails ガイド: Active Job のドキュメント(
enqueue_after_transaction_commit周辺) - ActiveRecord のトランザクションコールバック (
after_commit) と連携する仕組みで、DB 一貫性の担保に有用です。
- Rails ガイド: Active Job のドキュメント(
#56258 Add schematized json for has_json
マージ日: 2025/11/30 | 作成者: @dhh
- 概要 (1–2文で)
Rails の JSON 属性に対して「型付き・スキーマ付き」でアクセスできるschematized_json機能が ActiveModel に追加されました。has_json/has_delegated_jsonを使うことで、UI から文字列で値を渡しても、DB には boolean / integer / string の正しい JSON 型で保存されるようになります。
- 変更内容の詳細
2-1. 機能の概要
目的
- JSON カラムを「なんでも入る Hash」ではなく、事前に決めたキーと型を持つ半構造化データとして安全に扱えるようにする。
- フォームや API からはすべて文字列として送られてきても、モデル側で boolean / integer などに 自動キャスト したい。
サポートされる型
booleanintegerstring- ネストは非対応(フラットなキーのみ)
2-2. 代表的な API と使い方
PR 説明の例をベースに整理します。
class Account < ApplicationRecord
# JSON カラム :settings に対してスキーマ定義
has_json :settings,
restrict_creation_to_admins: true,
max_invites: 10,
greeting: "Hello!"
# JSON カラム :flags に対して、delegated なスキーマ定義
has_delegated_json :flags,
beta: false, # デフォルト値から boolean 型だと解釈
staff: :boolean # 明示的に boolean 型
endhas_json
対象: モデルの JSON カラム(例:
settings)使い方:
- キー名 => デフォルト値 で渡すと、デフォルト値の型から JSON 型が決まる
true/false→ boolean10→ integer"Hello"→ string
- デフォルト値を持たないキーは
symbolで型指定できる(例:staff: :boolean)
- キー名 => デフォルト値 で渡すと、デフォルト値の型から JSON 型が決まる
実行時の挙動:
Account.new時に、定義されたキーに デフォルト値がセット される。before_saveでも再度デフォルトセットが行われる(nil のままの場合などの補完)。
a = Account.new
a.settings.restrict_creation_to_admins? # => true (boolean 判定メソッド)
a.settings.max_invites # => 10 (integer)
a.settings.greeting # => "Hello!" (string)has_delegated_json
has_jsonが返す「アクセサオブジェクト」を介さず、モデル直下にメソッドを生やすパターン。
a = Account.new
a.beta # => false (flags["beta"] の boolean 値)
a.staff # => nil (デフォルトなし、型は boolean)
a.staff = true
a.staff? # => true2-3. 文字列入力からの自動型変換
この PR のキモは、「UI からは全部文字列でも、JSON としては型付きで扱える」点です。
a = Account.new
# string を代入しても integer にキャストされる
a.max_invites = "100"
a.max_invites # => 100 (Integer)
a.settings["max_invites"] # => 100 (JSON 上も数値)
# まとめて Hash で代入した場合も同様
a.settings = {
"restrict_creation_to_admins" => "false",
"max_invites" => "500",
"greeting" => "goodbye"
}
a.settings.restrict_creation_to_admins? # => false (文字列 "false" が boolean に変換)
a.settings.max_invites # => 500 (Integer)
a.settings.greeting # => "goodbye" (String)サポートされる変換イメージ(推測を含むが一般的には):
- boolean
"true","1","on","yes"→true"false","0","off","no"→false
- integer
"-10","0","42"→-10,0,42(to_iベース)
- string
- そのまま保存(トリム・バリデーション等はこの層では行わない想定)
2-4. 実装位置
- 追加ファイル:
activemodel/lib/active_model/schematized_json.rb- スキーマと JSON を結び付ける中核クラス/モジュールが定義されている。
activemodel/lib/active_model.rb,activemodel/lib/active_model/api.rbschematized_json機能を Active Model API から利用できるように require / include。
- テスト:
activemodel/test/cases/schematized_json_test.rb- 型付け、デフォルト適用、キャスト、
has_delegated_jsonの挙動などがカバーされている。
- 型付け、デフォルト適用、キャスト、
- 影響範囲・注意点
対象バージョン
- PR #56258 がマージされる Rails の次リリース以降で利用可能(少なくとも edge / main ブランチ)。
JSON カラムの前提
- DB 側は
json/jsonb(PostgreSQL)など、ネイティブ JSON 型または text + serialize でも動くが、Rails 的には JSON 属性として定義されている前提。
- DB 側は
型の制約
- サポートは boolean / integer / string のみ。
- 配列・オブジェクト・ネストした JSON 構造はサポート外。
- これらを使いたい場合は、従来通り
store,store_accessor,serialize, JSON カスタム型などを使う必要あり。
- これらを使いたい場合は、従来通り
既存データとの整合性
- すでに JSON カラムに異なる型が入っている場合、
- 読み出し時に想定と違う型が来る可能性がある。
- その場合の挙動(強制キャストかエラーか)は実装依存なので、マイグレーションやデータクレンジングが望ましい。
- すでに JSON カラムに異なる型が入っている場合、
before_save でのデフォルト適用
before_saveでもデフォルト値が適用されるため、- 「ユーザーが nil を明示的に入れたが、保存時にデフォルトで上書きされる」ケースをどう扱うかに注意。
- 「nil を許容したい」場合は、この schematized_json に乗せるべきか、仕様を検討した方がよい。
フォーム/フロントエンド側への利点
- すべて文字列で送ってよい、という前提が作れるので、JS 側の型管理がシンプルになる。
- ただし、boolean の
"true"/"false"などは UI からのバリエーションを考慮したうえで、変換ロジックに合わせた値を送る必要がある。
- 参考情報 (あれば)
- 追加クラス:
ActiveModel::SchematizedJson(推定名) - 近い既存機能:
store/store_accessor(ActiveRecord::Store)attribute :settings, :json, default: { ... }
- PR:
- GitHub: rails/rails #56258 「Add schematized json for has_json」
(実際のメソッド名・オプション・細かいキャストルールは PR 本文・diff のschematized_json.rb/ テストコードを参照するとより正確に把握できます)
- GitHub: rails/rails #56258 「Add schematized json for has_json」