Skip to content

Ruby on Rails PR Digest - 2025年 11月

このページは rails/rails リポジトリにマージされたPull Requestを自動的に収集し、AIで要約したものです。

#56255 Bump bundler-audit version 0.9.3 to support Ruby/Bundler 4

マージ日: 2025/11/29 | 作成者: @yahonda

  1. 概要 (1-2文で)
    bundler-audit のバージョンを 0.9.3 に上げることで、Ruby/Bundler 4 に対応するためのメンテナンス PR です。機能追加ではなく、依存ツールの更新によるセキュリティチェック環境の追随が目的です。

  1. 変更内容の詳細

何をしたか

  • Gemfile / Gemfile.lockbundler-audit のバージョンを 0.9.3 に更新しただけの、非常に小さな変更です。
  • それ以外の gem やアプリケーションコードには一切手を入れていません。

(おおよそのイメージ)

ruby
# Gemfile(イメージ)
gem "bundler-audit", "0.9.3"

Gemfile.lock 側では、これに伴って bundler-audit (0.9.3) とその依存関係のバージョン情報が更新されています。

背景

これらを踏まえ、Rails リポジトリで利用している bundler-audit を対応済みの 0.9.3 に揃えた、という位置づけです。


  1. 影響範囲・注意点

影響範囲:

  • bundler-audit を使った依存関係の脆弱性チェック(CI など)の実行環境にだけ影響します。
  • Rails 本体のランタイム挙動や API 仕様には影響しません。

想定される注意点:

  • Bundler 4 を使っている/使う予定の環境で bundle audit コマンドを問題なく動かせるようになります。
  • bundler-audit のマイナーバージョンアップにより、
    • 依存 gem の脆弱性データベースの扱い
    • Bundler との連携部分
      などに細かな挙動変更が入っている可能性があります。
      ただし PR 上はテスト追加なし・CHANGELOG 更新なしであり、「挙動変更を伴う大きな機能追加」ではなく「対応修正」に近いアップデートとみなされています。

開発者として確認しておくと良い点:

  • CI/ローカルで bundle exec bundler-audit check(あるいは bundle audit check)を実行し、
    • エラーなく実行できるか
    • 以前のバージョンと比べて想定外の検出結果(誤検出や検出漏れ)が出ていないか
      を確認しておくと安心です。

  1. 参考情報 (あれば)

#56254 Update puma gem constraint from >= 5.0 to >= 7.1

マージ日: 2025/11/29 | 作成者: @joshuay03

  1. 概要 (1–2文で)
    Rails のアプリケーションジェネレータが生成する puma のバージョン制約を、>= 5.0 から >= 7.1 に引き上げた PRです。これにより、新規に生成される Rails アプリは Puma 7.1 以降を前提として動作するようになります。

  1. 変更内容の詳細

この PR で変わったのは 2 ファイルのみです。

2.1 railties/lib/rails/generators/app_base.rb

Rails のアプリケーションテンプレート(rails new 時に Gemfile などを生成するベースクラス)で、puma の gem 要件を指定している部分が変更されています。

変更前(イメージ):

ruby
# Gemfile を生成するテンプレート内など
gem "puma", ">= 5.0"

変更後:

ruby
gem "puma", ">= 7.1"

実際には上記のような文字列(テンプレート)の 1 行が >= 5.0 から >= 7.1 に書き換わっています。

2.2 railties/test/generators/app_generator_test.rb

ジェネレータのテストも、期待される Gemfile の内容を確認するためのアサーションが 1 行だけ変更されています。

変更前(イメージ):

ruby
assert_file "Gemfile", /gem "puma", ">= 5.0"/

変更後:

ruby
assert_file "Gemfile", /gem "puma", ">= 7.1"/

2.3 背景・モチベーション

  • 別 PR(https://github.com/rails/rails/pull/56249)での変更が Puma 6.5 に依存している。
  • それに加えて、Puma 側で「Puma::DSL#workersWEB_CONCURRENCY を混在させた場合の紛らわしい挙動バグ」が 7.0 系で修正されている。
  • そのため、単に「6.5 以上」ではなく、バグ修正が入っている 7.x 系を前提にする方がユーザにとって安全・一貫した挙動を提供できる。
  • レビューの過程で 7.0 ではなく 7.1 以上に引き上げる形に再度調整(PR 本文の Edit 参照)。

要するに、「Rails が提供する Puma 連携まわりの挙動と整合した、バグ修正版 Puma を確実に使わせる」ことが狙いです。


  1. 影響範囲・注意点

3.1 影響を受けるケース

  • 新しく rails new で生成するアプリ
    生成される Gemfilepuma 行が >= 7.1 に変わるため、bundle install 時に Puma 7.1 以上がインストールされます。

  • 既存アプリへの直接の影響はない
    既に存在するアプリの Gemfile は自動で書き換えられないため、この PR を含む Rails にアップデートしても、既存アプリは従来通りの Puma バージョン制約のままです。

3.2 注意点・移行上のポイント

  • Puma 7.1 以降が利用可能な環境が前提

    • Ruby やプラットフォームのサポート状況は Puma 7.1 の要件に従います。
    • 非常に古い Ruby や環境を使っていると Puma 7.1 が入れられない可能性があるため、その場合は手動で Gemfile のバージョン制約を調整する必要があります。
  • Puma の設定方法 (workers / WEB_CONCURRENCY) に関する挙動差

    • この PR のモチベーションの一つが、「Puma::DSL#workers と環境変数 WEB_CONCURRENCY の組み合わせによる紛らわしいバグの回避」です。
    • Puma 7.1 以上を前提にすることで、Rails ガイドや generator が想定する挙動と Puma の実挙動がより一致し、ワーカー数まわりの予期せぬ不整合に遭遇しにくくなります。
  • ライブラリ作者 / Template 作者向け

    • Rails アプリを生成する独自テンプレート(API-only テンプレート、社内テンプレートなど)で Puma バージョンを明示している場合は、7.1 以上を前提にするかどうか再検討する価値があります。
    • Rails 本体は Puma 7.1 以上での動作を前提に前進していく可能性があるため、エコシステム全体としても 7.x 系への移行が進むことが想定されます。

  1. 参考情報 (あれば)

補足として、既存プロジェクトで Puma 7.x へ上げる場合は、config/puma.rb の設定(特に workers や thread 数、environment 周辺)に非推奨オプションや挙動差がないか、Puma のリリースノートを一度確認するのがおすすめです。


#56249 Simplify setting of threads in Puma config template

マージ日: 2025/11/28 | 作成者: @joshuay03

  1. 概要 (1-2文で)
    Rails が生成する config/puma.rb テンプレートで、スレッド数設定の記述を、Puma 6.5.0 以降で使える「引数1つ形式」に簡略化する変更です。機能的な挙動は変えずに、コードをシンプルかつ Puma の現仕様に沿った形にしています。

  1. 変更内容の詳細

元々の config/puma.rb テンプレートでは、Puma のスレッド数設定を「最小値」と「最大値」を同じ値で2回渡す形で書いていました:

ruby
# 変更前のイメージ
max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
min_threads_count = max_threads_count
threads min_threads_count, max_threads_count

Puma 6.5.0 で以下の変更が入りました:
https://github.com/puma/puma/pull/3309

Puma::DSL#threads に単一引数を渡した場合、その値を min / max 両方に適用する

Rails では元々 min / max を同じ値にしているため、この Puma の仕様を利用して、以下のように1引数だけを渡す形に簡略化しました:

ruby
# 変更後のイメージ
threads ENV.fetch("RAILS_MAX_THREADS") { 5 }

つまり:

  • Rails が内部で「min と max を同じ値に設定する」ためだけに用いていた変数や二重指定をやめ
  • Puma の「1引数 => min=max」という挙動に任せるようにした

というリファクタリングです。


  1. 影響範囲・注意点
  • 対象: rails new で生成される config/puma.rb のみ
    既存アプリの config/puma.rb は自動で書き換えられないため、既存アプリには即影響はありません。

  • 挙動:

    • threads の min / max は依然として同じ値のままです。
    • したがってスレッド数やパフォーマンス特性に変化はありません。
  • Puma バージョン依存性:

    • このシンプルな 1 引数形式は Puma 6.5.0 以降が前提です。
    • Rails の Gemfile テンプレートでは Puma の依存関係がすでにこれを満たすバージョン帯(6.5.0+、現時点最新は 7.1.0)を想定しているため、新規生成アプリでは問題になりません。
    • もし独自に Puma を「6.4.x 以前」に固定したい場合、この生成された記述は非対応になるので、min/max 2引数の書き方に戻す必要があります。
  • カスタマイズしているケース:

    • もともと config/puma.rb を編集して min と max を変えて使っている既存アプリにはこの変更は適用されませんが、
      「min = 0, max = 5」のような使い方をしたい場合は、引き続き 2 引数で指定する必要があります。

  1. 参考情報 (あれば)

#56245 Introduce DevToolsController for chrome workspaces

マージ日: 2025/11/28 | 作成者: @coorasse

  1. 概要 (1-2文で)
    Rails に Rails::DevtoolsController が追加され、Chrome DevTools の「自動ワークスペース検出」機能が期待する .well-known/appspecific/com.chrome.devtools.json へのレスポンスを、簡単に提供できるようになりました。これにより、Chrome の「ワークスペース」と Rails プロジェクトルートを自動連携しやすくなります。

  1. 変更内容の詳細

追加された機能の目的

  • Chromium/Chrome DevTools は、特定の URL:
    • .well-known/appspecific/com.chrome.devtools.json にアクセスして、「このアプリケーションのワークスペースフォルダ」を自動検出します。
  • Rails に標準でそれに対応するエンドポイントがなかったため、今回の PR で「標準のコントローラ + ルート候補」が提供されました。

主な変更点

1) Rails::DevtoolsController の追加

railties/lib/rails/devtools_controller.rb に新しいコントローラが追加されています。
役割:

  • Chrome からの .well-known/appspecific/com.chrome.devtools.json リクエストに対して JSON を返す。
  • JSON には:
    • Rails.root(Rails アプリケーションのルートパス)
    • そのルートに基づいて決まる UUID(ルートが変わらない限り不変) を含める。

この UUID は「プロジェクトを識別する ID」として機能し、フォルダパスが変わったときだけ変化するようになっているため、Chrome 側で「同じプロジェクトかどうか」を安定して判定しやすくなります。

(実装イメージ: 正確なコードは省略されますが、概ね以下のような形です)

ruby
module Rails
  class DevtoolsController < ActionController::Base
    def show
      root = Rails.root.to_s

      render json: {
        # Chrome DevTools が期待するキーに応じて
        path: root,
        uuid: stable_uuid_for(root) # ルートパスに依存した UUID
      }
    end

    private

    def stable_uuid_for(path)
      # パスから安定した UUID を生成する仕組み(例: Digest + UUID 化)
    end
  end
end

※ 実際のキー名・生成方法は PR 内の実装に依存しますが、説明によれば「Rails.root を返し、Rails.root が変わったときだけ変わる UUID」を返す実装です。

2) ルーティング設定のサンプルが追加

routes.rb に以下のような行を「コメントアウトされた状態」で追加する形が想定されています:

ruby
get ".well-known/appspecific/com.chrome.devtools.json", to: "rails/devtools#show"
  • 新規アプリのテンプレートなどにこの行がコメントとして入ることで:
    • 開発者はコメントアウトを外すだけで機能を有効化できる
    • 不要であればそのまま放置または削除すれば良い
  • ルートは「エンジンが勝手に登録する」形ではなく、アプリ側で明示的に有効化する前提です。

3) Rails 起動フロー等への組み込み

  • railties/lib/rails.rb
  • railties/lib/rails/application/finisher.rb

などが 1 行ずつ程度変更されており、Rails::DevtoolsController を正しく autoload / 使用できるようにしています。 (典型的にはコントローラのロードや、エンジン内クラスの読み込みに関する微調整。)

4) テストの追加

railties/test/rails_devtools_controller_test.rb が追加され、以下を確認しています:

  • エンドポイントにアクセスすると JSON が返る。
  • 返されるパスが Rails.root である。
  • UUID が期待どおりの安定した振る舞いをする(同じ root では同一、root が変わると変更される)。

5) CHANGELOG の更新

  • railties/CHANGELOG.md に、この新機能の追加が明記されています。

  1. 影響範囲・注意点
  • 既存アプリへの即時の挙動変更はほぼなし

    • ルートはコメントアウトされた状態で追加される想定のため、既存アプリの挙動が勝手に変わることはありません。
    • 有効化したい場合のみ、routes.rb でコメントを外す必要があります。
  • セキュリティ・情報露出の観点

    • 返す情報は基本的に Rails.root と UUID であり、プロジェクトの絶対パスが JSON で公開される可能性があります。
    • 本番環境で有効化する場合、ホスト OS のパス情報を外部に見せて問題がないかを検討する必要があります。
      • 開発専用エンドポイントとして if Rails.env.development? ガードを付けるなどの運用が望ましいです。

    例:

    ruby
    if Rails.env.development?
      get ".well-known/appspecific/com.chrome.devtools.json", to: "rails/devtools#show"
    end
  • カスタマイズ・拡張性

    • コントローラはエンジンから提供されますが、通常の Rails コントローラと同様にアプリ側でオーバーライド可能です。
      • 例: app/controllers/rails/devtools_controller.rb を定義して独自実装に差し替え。
    • 将来 Chrome 側の仕様が変わっても、アプリ内で対応を上書きしやすい構成です。
  • 代替実装パスも想定済み
    PR 説明でも述べられているように、以下のような実装方針もありえましたが、今回は採用されていません:

    • Rack ミドルウェアでのレスポンス
    • アプリ側で変更不可能な固定ルート
    • 各アプリで独自にコントローラを定義する この PR の方針は「Rails が良いデフォルトを提供しつつ、アプリ側の制御性も担保する」折衷案です。

  1. 参考情報 (あれば)
  • Chromium DevTools の「自動ワークスペース検出」仕様:
    https://chromium.googlesource.com/devtools/devtools-frontend/+/main/docs/ecosystem/automatic_workspace_folders.md

  • 実務的な使い方のイメージ:

    1. routes.rb に以下を追加(開発環境のみがおすすめ):

      ruby
      if Rails.env.development?
        get ".well-known/appspecific/com.chrome.devtools.json", to: "rails/devtools#show"
      end
    2. Chrome DevTools を開き、Sources > Filesystem/Workspace などからプロジェクトを開くときに、自動的に Rails.root を認識してくれるようになる(仕様に準拠していれば)。


#56248 Test for becomes method when passing the same class

マージ日: 2025/11/28 | 作成者: @bogdan

  1. 概要 (1-2文で)
    このPRは、Active Record の becomes メソッドに対して「同じクラスを渡した場合」の挙動を確認するテストを追加したものです。実装変更はなく、将来的なリグレッションを防ぐためのテスト補強が目的です。

  1. 変更内容の詳細
  • 変更ファイル: activerecord/test/cases/persistence_test.rb
  • 行数: 7行のテストコード追加のみ

becomes は、ある Active Record オブジェクトを別のクラスとして扱う(ラップし直す)ためのメソッドですが、このPRでは「becomes にそのオブジェクト自身と同じクラスを渡した場合」に関するテストが追加されています。

典型的には、以下のようなテストが追加されていると考えられます:

ruby
def test_becomes_with_same_class
  topic = Topic.create!(title: "foo")
  same = topic.becomes(Topic)

  # 同じクラスを渡したときの期待挙動を明示
  assert_equal Topic, same.class
  assert_equal topic.id, same.id
  assert_equal topic.attributes, same.attributes
  # あるいは object_id が変わる/変わらないなどの仕様を固定するアサーション
end

実際のコードは多少異なる可能性がありますが、ポイントは:

  • becomes(SomeClass) に「すでに SomeClass であるレコード」を渡したとき、
    • 新しいインスタンスを返すのか
    • 同じインスタンスを返すのか
    • 属性や ID の整合性はどうあるべきか
      をテストで明文化している、という点です。

PR 説明文で https://github.com/rails/rails/pull/56228#discussion_r2563856098 に言及されていることから、以前の PR (#56228) のレビュー議論の中で「同じクラスを渡した場合もテストしておこう」という指摘があり、それに対応するフォローアップとしてテストを追加した、という位置づけです。


  1. 影響範囲・注意点
  • 実装コード(本体機能)の変更はなく、テスト追加のみのため、挙動の後方互換性には影響しません。
  • ただし、このテストによって「becomes に同じクラスを渡したときの現在の挙動」が仕様として固定化されます。
    将来、その挙動を変えようとした場合には、このテストが失敗し、意図的な仕様変更か単なるバグかを検討するトリガーになります。
  • becomes を活用して STI(Single Table Inheritance)やフォームオブジェクト的な使い方をしているプロジェクトでは、
    • 「同じクラスを渡した場合も呼び出されうる」
    • 「そのときどういうオブジェクトが返ってくるか」
      をこのテストを通じて Rails 本体側の期待値として確認できるようになります。

  1. 参考情報 (あれば)
  • 関連 PR:
    • #56228 — この PR 内のレビューコメント (discussion_r2563856098) への対応として本 PR が作成された。
  • becomes メソッドの用途:
    • STI で Post < ApplicationRecordSpecialPost < Post のような継承関係がある場合に、 post.becomes(SpecialPost) のようにして同じレコードを別クラスとして扱うために使用されるメソッド。
    • バリデーションやフォームの取り回しの都合で、一時的にクラスを変えて扱いたい場面で利用されることが多い。

#56247 Ensure PluginTestRunnerTest and TestRunnerInEngineTest use clean Bundler env

マージ日: 2025/11/28 | 作成者: @zzak

  1. 概要 (1-2文で)
    Plugin 用テストランナー (PluginTestRunnerTest) と Engine 内テストランナー (TestRunnerInEngineTest) が、Bundler の状態に影響されない「クリーンな Bundler 環境」で実行されるように修正した PR です。これにより、手元の Gemfile / Bundler 設定に依存したテストの不安定さを解消し、#56234 の問題を修正しています。

  1. 変更内容の詳細

変更ファイルはどちらも Rails 本体のテストコードです。

  • railties/test/generators/plugin_test_runner_test.rb
  • railties/test/generators/test_runner_in_engine_test.rb

行数的にはそれぞれ 1 行の置き換え(+1/-1)ですが、ポイントは「テストを Bundler のクリーン環境で実行するようにした」ことです。

Rails のテストスイートでは、実行時の Bundler の状態に依存しないようにするために、例えば以下のようなパターンがよく使われます:

ruby
Bundler.with_unbundled_env do
  # ここで bundle exec やコマンド実行を行う
end

この PR では、PluginTestRunnerTestTestRunnerInEngineTest 内でテスト用に外部コマンド(rails testbin/test 相当)を実行している箇所を、このような「クリーンな Bundler 環境」でラップするように変更したものと考えられます。
(実際の差分は 1 行の変更ですが、envBundler.with_unbundled_env のようなヘルパに差し替えた可能性が高いです。)

結果として、テストプロセス内でロードされている Gem / Bundler 設定が、テスト対象の生成物(plugin / engine 内のテストランナー)が期待する環境に干渉しないようになります。


  1. 影響範囲・注意点
  • 影響範囲

    • Rails コアのテスト (railties の generator テスト) のみです。
    • Rails を利用する通常のアプリケーションやプラグインの挙動には影響しません。
    • CI やローカルで Rails 本体のテストを実行している開発者にとって、テストが Bundler の状態に左右されにくくなります。
  • 注意点

    • Rails コアにパッチを送る際、generator 系テストで外部コマンド / テストランナーを実行する場合は、「クリーンな Bundler 環境」で実行するパターンにそろえるべき、という指針が補強されます。
    • Gemfile に独自の Gem を大量に追加している開発環境でも、この 2 つのテストがその影響を受けづらくなります(#56234 で報告されていたようなテスト失敗が解消される)。

  1. 参考情報 (あれば)
  • 該当 Issue: #56234
    → Plugin / Engine のテストランナー生成テストが、手元の Bundler 環境に依存して不安定になる問題を報告していたと考えられます。
  • Bundler のクリーン環境実行の典型例:
    • Bundler.with_unbundled_env { ... }
    • 古いバージョンでは Bundler.with_clean_env { ... }(非推奨)
  • Rails の generator / integration テストでは、外部コマンド実行時に Bundler の影響を避けるテクニックが複数箇所で使われているため、それらと今回の 2 テストが整合する形になったとみなせます。

#56244 Remove whitespace to make note not part of code example [ci skip]

マージ日: 2025/11/27 | 作成者: @danieldevries

  1. 概要 (1–2文で)
    このPRは、ActiveRecord::Sanitization にあるコード例とその注記の間の不要な空白を削除し、ドキュメント上で注記がコードブロックの一部としてレンダリングされてしまう問題を修正するものです。挙動やAPIには一切影響せず、ドキュメント体裁のみの変更です。

  1. 変更内容の詳細

対象ファイル:

  • activerecord/lib/active_record/sanitization.rb (+1 / -1)

このファイルには、sanitize_sql, sanitize_sql_for_conditions, sanitize_sql_for_assignment などに関するコメント/RDocが記述されています。PRタイトルの通り、

「注記(note)」がコード例の一部として扱われないようにするために、空白(ホワイトスペース)を1箇所調整

しています。

典型的には以下のような修正です(イメージ):

diff
-  #   User.where("name = 'foo'") # Note: this is unsafe
+  #   User.where("name = 'foo'")
+  # Note: this is unsafe

あるいは:

diff
-  #   User.where("name = 'foo'")  # Note: this is unsafe
+  #   User.where("name = 'foo'")
+  #   # Note: this is unsafe

実際には RDoc/YARD などのドキュメント生成において、

  • 「コード例 (Example)」として表示したい部分
  • 「注記 (Note)」としてコードブロックの外に強調して表示したい部分

の境界を、行頭の空白やコメントの位置を調整することで明示しています。

変更行数が 1 行 (+1 / -1) のため、実装ロジックには触れておらず、コメント(RDoc)の体裁だけが変わっています。


  1. 影響範囲・注意点
  • 実行時挙動:
    • メソッドの挙動・SQLサニタイズロジックには一切変更がありません。
    • テストや本番環境への影響もありません([ci skip] も付与されています)。
  • 影響範囲:
    • Rails APIドキュメント・ソースコードコメントの読みやすさが改善されるのみです。
    • ドキュメントを参考にする際に、「これは実際にそのままコピペしてよいコードか」「説明用メモか」がより明確になります。
  • 注意点:
    • ライブラリ利用者が何か対応を行う必要はありません。
    • ドキュメント生成ツール(rdoc, yard など)を自前で実行している場合、次回生成時に当該箇所の表示が少しだけ変わる可能性がありますが、意味内容は変わりません。

  1. 参考情報 (あれば)
  • PRタイトル: 「Remove whitespace to make note not part of code example [ci skip]」
    ⇒ ドキュメント生成時に「注記 (note)」がコードブロックとして解釈されないよう、インデント/ホワイトスペースを微調整した、ドキュメント体裁の微修正PRです。
  • CIスキップタグ [ci skip] からも分かる通り、コードロジックには影響しない軽微な変更です。

#56240 Handle closed PostgreSQL connections in retryable_query_error?

マージ日: 2025/11/27 | 作成者: @george-ma

  1. 概要 (1-2文で)
    PostgreSQL アダプタの retryable_query_error? が「コネクションオブジェクトは存在するが、既に close 済み」のケースを扱えず PG::ConnectionBad で落ちてしまう問題を修正し、その場合でもリトライ機構が動くようにしました。これにより、予期せぬ PG コネクション断が発生しても、Rails の自動リトライが正しく働くようになります。

  1. 変更内容の詳細

何が問題だったか

ActiveRecord::ConnectionAdapters::PostgreSQLAdapter#retryable_query_error? は内部で

ruby
@raw_connection&.transaction_status == ::PG::PQTRANS_INERROR

を使って「いま壊れたトランザクション中かどうか」を判定しています。

ただし、PG コネクションが「オブジェクトはまだあるが、すでに raw_connection.close 済み」の状態になると、transaction_status 呼び出し自体が PG::ConnectionBad (connection is closed) を投げます。

元の実装は以下 2 パターンのみを想定していました:

  1. @raw_connectionnil
  2. @raw_connection がオープンな接続で、transaction_status が正常に呼べる

しかし実際には、

  1. @raw_connection は存在するが、内部的にはクローズされていて transaction_statusPG::ConnectionBad を投げる

というケースがあり、この 3 つ目のケースで retryable_query_error? 自体が例外で落ち、上位の「クエリリトライ処理」まで到達せずに異常終了していました。

修正内容

この PR は、上記 3 つ目のケースを明示的に扱うため、transaction_status 呼び出しの周囲で PG::ConnectionBad を rescue し、「トランザクションが壊れているかどうかの判定には失敗したが、少なくともここでは致命的ではない」とみなして super にフォールバックするようにしています。

概念的には、以下のような変更です(説明用の擬似コード):

ruby
def retryable_query_error?(exception)
  in_transaction = begin
    @raw_connection&.transaction_status == ::PG::PQTRANS_INERROR
  rescue PG::ConnectionBad
    # コネクションがすでに閉じていて transaction_status が呼べない
    # 「壊れたトランザクション中」とは判定できないので false 扱い
    false
  end

  # 壊れたトランザクション中ではなく、かつ上位ロジック的にリトライ可能なら true
  !in_transaction && super
end

ポイント:

  • transaction_status 呼び出しで PG::ConnectionBad が発生したら、「壊れたトランザクション中ではない(= false)」として扱う。
  • そのうえで super(共通のリトライ判定ロジック)に処理を委譲することで、「接続断を検知してコネクションを捨て、ActiveRecord::ConnectionNotEstablished 経由で再接続してから再実行する」という既存のリトライメカニズムに乗せられるようになる。

PR の説明にある再現スクリプトでは、修正前後の違いが以下のように確認できます。

  • 修正前: 閉じたコネクションで connection.execute("SELECT 1") を呼ぶと PG::ConnectionBad がそのまま外に出てしまう(リトライされない)。
  • 修正後: retryable_query_error? 内で PG::ConnectionBad を吸収し、ConnectionNotEstablished が発生 → Rails のリトライメカニズムが動き、接続を張り直してクエリが再実行される。

テスト

activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb にテストが追加され、次のようなシナリオがカバーされています(要旨):

  • PG コネクションを取得
  • raw_connection.close して「閉じたコネクションオブジェクト」を用意
  • その状態でクエリを実行
  • 例外が PG::ConnectionBad で落ちるのではなく、Rails の「接続再確立の流れ」に乗ることを確認

これにより、今回追加したケースが将来の変更で壊れないよう回帰テストされています。


  1. 影響範囲・注意点
  • 対象: PostgreSQL アダプタ (ActiveRecord::ConnectionAdapters::PostgreSQLAdapter) 利用時のみ。
  • 影響する場面:
    • アプリケーションが PostgreSQL を使っていて、
    • 何らかの理由で PG コネクションが「オブジェクトは生きているが内部的にはクローズ済み」になり、
    • その直後のクエリ実行時に Rails の「接続リトライ機構」が発動するケース。
  • 期待される挙動の変化:
    • これまで: PG::ConnectionBad がそのままアプリケーション層に伝播していた。
    • 変更後: リトライ機構が働き、ConnectionNotEstablished → 再接続 → クエリ再実行という、Rails の他の接続障害と同様のパスを通る。
  • 互換性:
    • 「壊れたトランザクション中かどうか」の判定ロジックは変えていない(例外時は「壊れていない扱い」とする)。
    • 故意に raw_connection を直接触って close しているようなコードでも、これまでより落ちにくくなるだけで、既存の API コントラクトを壊す変更ではありません。
  • 注意点:
    • rescue されるのは transaction_status 呼び出しでの PG::ConnectionBad のみであり、他のタイミングでの PG::ConnectionBad まで無条件に握りつぶすわけではありません。
    • 「本来ここで失敗してほしい」ような、アプリ固有の例外ハンドリングに依存しているコードがあれば、挙動を確認したほうが安全です(とはいえ、基本的には「より安全になった」という方向の変更です)。

  1. 参考情報 (あれば)
  • 該当 PR: https://github.com/rails/rails/pull/56240
  • 関連コンポーネント:
    • ActiveRecord::ConnectionAdapters::PostgreSQLAdapter#retryable_query_error?
    • PG::ConnectionBad
    • PG の transaction_statusPQTRANS_INERROR(壊れたトランザクション状態)
  • 類似パターン:
    • MySQL アダプタでは既に「閉じた接続」でのエラーをリトライ対象として扱うパスが存在しており、PG もそれに近づけた形です。
  • 実運用でのヒント:
    • コネクションプールや PG 側のタイムアウト・接続切断を積極的に行う環境(Kubernetes, PaaS, クラウドマネージド DB など)では、この種の「オブジェクトはあるが中身は閉じている」状態が起こりがちで、この修正の恩恵を受けやすいです。

#56242 Ensure TimeWithZone#as_json always return an UTF-8 string

マージ日: 2025/11/27 | 作成者: @byroot

  1. 概要 (1-2文で)
    ActiveSupport::TimeWithZone#as_json が返す文字列のエンコーディングを常に UTF-8 に統一する修正です。Time#xmlschema が US-ASCII を返すことによって起きるエンコーディング不整合を解消しています。

  1. 変更内容の詳細

※PR本文からは 1 行差分(+1/-1)のみで、主なポイントは「TimeWithZone#as_json の戻り値を UTF-8 にする」ことです。Rails 本体の既存実装と文脈から、概ね次のような変更が入っていると考えられます。

ActiveSupport::TimeWithZone#as_json は内部的に xmlschema(もしくはそれ相当のメソッド)を呼び、ISO8601 形式の文字列を返していますが、Ruby の Time#xmlschema は Encoding::US_ASCII の文字列を返します。そのため JSON シリアライズ時などに、他の UTF-8 文字列と混在するとエンコーディングの不一致が発生する可能性がありました。

この PR では、TimeWithZone#as_json 内で生成した文字列に対して、明示的に UTF-8 エンコーディングを付与するような処理が 1 行追加されています。イメージとしては次のような変更です(実際のコードは若干異なる可能性がありますが、意図は同じです):

ruby
# 変更前(イメージ)
def as_json(*)
  xmlschema
end

# 変更後(イメージ)
def as_json(*)
  xmlschema.encode(Encoding::UTF_8)
end
# または xmlschema.force_encoding(Encoding::UTF_8)

これにより、TimeWithZone を含むオブジェクトを to_json などでシリアライズした場合でも、タイムスタンプ文字列のエンコーディングが必ず UTF-8 になります。

PR 説明にあるとおり、問題の直接の原因は:

  • Time#xmlschema が US-ASCII の文字列を返す

という Ruby 標準ライブラリ側の仕様であり、それを Rails 側で吸収するパッチになっています。


  1. 影響範囲・注意点
  • 影響を受ける主なケース

    • ActiveSupport::TimeWithZone オブジェクトを as_json / to_json 経由で外部に出力しているすべてのコード
    • 例: ActiveRecord の datetime / time 型のカラムを含むモデルを JSON API として返しているアプリケーション
  • 行動変化

    • これまで: TimeWithZone#as_json が US-ASCII な文字列を返す場合があった
    • これから: 常に UTF-8 エンコーディングで返る
    • 文字列内容(「2025-11-27T12:34:56Z」など)は変わらず、エンコーディングのみが統一される変更です。
  • 互換性

    • 通常の Rails アプリにとっては後方互換的で、実害はほぼなく、むしろエンコーディングの不一致による不具合(例: JSON パーサ・外部サービス・フロントエンドでの文字化け/例外)を防ぐ方向の修正です。
    • テストやコードの中で encoding を厳密に検査しているようなケース(expect(time.as_json.encoding).to eq(Encoding::US_ASCII) のようなもの)がある場合は、UTF_8 を期待するように修正が必要です。
  • パフォーマンス

    • 追加される処理は 1 回の as_json 呼び出しあたり 1 回のエンコーディング設定程度で非常に軽量なため、パフォーマンスへの影響は無視できるレベルと考えてよいです。

  1. 参考情報 (あれば)
  • 元 Issue: https://github.com/rails/rails/issues/56241
    • TimeWithZone#as_json の戻り値が US-ASCII になり、UTF-8 ベースのアプリケーションで問題になるケースが報告されています。
  • Ruby Time#xmlschema の仕様(Ruby リファレンス)
    • xmlschema は ISO8601 形式の文字列を返しますが、そのエンコーディングは US-ASCII です。
  • 関連する Rails コード:
    • activesupport/lib/active_support/time_with_zone.rb 内の as_json 実装。

#56228 Optimize ActiveRecord::Base#becomes when no class conversion needed

マージ日: 2025/11/27 | 作成者: @bogdan

  1. 概要 (1-2文で)
    ActiveRecord::Base#becomes が、変換前後でクラスが同一の場合は新しいオブジェクトを生成せず、そのまま self を返すように最適化された PR です。不要なオブジェクト生成を避けることで、軽微ながらパフォーマンスとメモリ効率が改善されます。

  1. 変更内容の詳細 (サンプルコード例付き)

変更の主旨

ActiveRecord::Base#becomes は、ある AR オブジェクトを別のクラス(通常は STI などで関連のあるクラス)として扱うために使うメソッドです。この PR では「クラス変換が不要なケース(元のクラスと引数のクラスが同じ)」の挙動を最適化しています。

従来:

ruby
post = Post.first
post2 = post.becomes(Post)

# post2 は新しいインスタンス
post2.object_id != post.object_id
post2.class == Post

このように、クラスが同じ PostPost であっても、内部的には属性をコピーした新しいオブジェクトを生成していました。

変更後:

ruby
post = Post.first
post2 = post.becomes(Post)

# クラスが同じ場合は self をそのまま返す
post2.object_id == post.object_id
post2.equal?(post) #=> true

今回の PR で追加されたのは、おおよそ以下のような条件分岐です(実際のコードはニュアンス例):

ruby
def becomes(klass)
  return self if klass == self.class
  # 従来通りクラス変換の処理…
end

つまり、「本当に別クラスへの変換が必要なケース」に限定して、新しいインスタンス生成や属性コピーを実行するようになりました。


  1. 影響範囲・注意点

影響範囲

  • 対象メソッド: ActiveRecord::Base#becomes
  • 影響するケース: record.becomes(SomeClass)SomeClassrecord.class と同じ場合

STI やポリモーフィックな設計で becomes を多用していて、かつ「同じクラスを指定している」コードがある場合、挙動の差異が出る可能性があります。

互換性上の注意点

1. オブジェクト ID / 同一性の違い

以前:

ruby
post = Post.first
new_post = post.becomes(Post)

post.equal?(new_post)     #=> false
post.object_id == new_post.object_id #=> false

変更後:

ruby
post = Post.first
new_post = post.becomes(Post)

post.equal?(new_post)     #=> true
post.object_id == new_post.object_id #=> true

becomes を呼ぶと必ず新しいインスタンスになる」という前提でコードを書いていた場合は、挙動が変わります。ただし、こうした前提は本来の becomes の意図とは少し外れているため、通常の利用では問題になりにくいと考えられます。

2. 副作用の数の違い(コールバック等)

becomes 自体は通常 Active Record のコールバック(before_save など)を発火させませんが、「becomes の結果が新しいインスタンスであること」を利用して何らかの副作用ロジックを書いていた場合(例: オブジェクト ID の変化をトリガーにしているなど)、その挙動は変わります。

3. テストの期待値の変更の可能性

RSpec 等で:

ruby
expect(record.becomes(record.class)).not_to equal(record)

のようなテストを書いていた場合は失敗します。代わりに、クラス変換機能自体(属性コピーや validation 上の挙動など)をテストするように書き換える必要があります。


  1. 参考情報 (あれば)
  • 対象メソッド: ActiveRecord::Base#becomes
  • 典型的な利用例(STI):
ruby
class Account < ApplicationRecord
  # STI base
end

class AdminAccount < Account; end
class UserAccount  < Account; end

account = Account.find(1)

admin = account.becomes(AdminAccount)  # Account を AdminAccount として扱いたい
admin.class #=> AdminAccount

この PR による最適化は、「account.becomes(Account) のように無意味な変換をしてしまった場合」に余計なオブジェクトを作らないという、小さながら妥当な性能改善として働きます。


#56129 QueryIntent#execute!

マージ日: 2025/11/26 | 作成者: @matthewd

  1. 概要 (1-2文で)
  • ActiveRecord の QueryIntent が「どのアダプタに属しているか」を把握し、自身でクエリの前処理〜実行までを担えるようになりました。
  • これにより QueryIntent#execute! でクエリを実行し、結果の保持・取り出しまでを一貫してハンドリングする設計にリファクタリングされています。

  1. 変更内容の詳細

全体像の変化

これまで:

  • アダプタ (PostgreSQLAdapter, Mysql2Adapter など) 側のメソッド群が主導で、
    • SQL 組み立て
    • クエリ前処理(バインド、ログ、キャスティング準備など)
    • 実行 (select_all, exec_query…) を行い、QueryIntent は主に「意図(SQL, binds, cast モード)」を保持するだけの受動的なオブジェクトでした。

これから:

  • QueryIntent が「自分はどのアダプタ上のクエリか」を知っており、そのアダプタに処理を依頼しながら、
    • 前処理
    • 実行 (execute!)
    • 結果の保存 を主導する「ドライバー」役になります。
  • アダプタ側の select_all / exec_query などは、QueryIntent を介して実行するスタイルに収斂していきます。

PR 説明にある通り、これで以下が可能になります:

QueryIntent to execute!, storing the result for the caller to then request in whichever raw/cast form they prefer.

つまり execute! 一発でクエリを走らせ、呼び出し側は「生の結果」または「型変換済み結果」など、好きな形で結果を取り出せるようになる方向です。


QueryIntent 側の主な変更点

activerecord/lib/active_record/connection_adapters/query_intent.rb (+107/-16):

※実際のコードは PR 本体を参照する必要がありますが、変更内容から想定できるポイントは以下です。

  1. アダプタインスタンスを保持

    • これまでは「抽象的なクエリ情報(SQL, binds, type casts)」のみを持っていましたが、
      今回から「どのアダプタで実行されるクエリか」を知るようになっています。
    • これにより、intent.adapter 経由で adapter に処理を依頼できます。

    イメージ:

    ruby
    intent = ActiveRecord::ConnectionAdapters::QueryIntent.new(
      adapter: connection,
      sql: "SELECT * FROM users WHERE id = $1",
      binds: [[nil, 1]]
    )
  2. execute! の追加

    • QueryIntent#execute! が新設され、内部でアダプタに実行を委譲します。

    • 実装イメージ:

      ruby
      def execute!
        return @result if @executed
      
        # 前処理(バインド加工、ログ、スキーマキャッシュ確認など)をここで実施し、
        # 必要に応じて adapter のメソッドに処理を依頼する。
        @result = adapter.run_query(self) # 名前は実際とは異なる可能性あり
        @executed = true
        @result
      end
    • 重要なのは「結果を QueryIntent 内に保持する」ことです。
      呼び出し側は intent.raw_result, intent.cast_result のように、後から好きな形でアクセスできるようになる準備が整います。

  3. クエリ前処理のロジックを吸収

    • 以前はアダプタ側 (database_statements.rb 等) に散らばっていた「クエリ前処理」が QueryIntent 主導に寄せられています。
    • たとえば:
      • ログに出す SQL の生成
      • bind parameter のログ用整形
      • prepared statement の扱い
      • schema cache / prepared statement cache との連携
    • 「アダプタの実装差分」+「共通的なクエリ前処理」を、QueryIntent を中心とした API で統一していく設計です。

database_statements / 各アダプタの変更

activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb (+88/-124) など、多くのアダプタファイルが差し替えられています。

共通する方向性:

  1. 「直接 SQL を実行」→「QueryIntent ベースへ」

    • 例えば select_all(sql, name = nil, binds = [], prepare: false) のようなメソッド内で、 直接アダプタ固有の raw_connection.exec 相当を呼んでいた箇所が徐々に QueryIntent を介する形へ寄せられています。

    例: (あくまで構造イメージ)

    ruby
    def select_all(sql, name = nil, binds = [], prepare: false)
      intent = build_query_intent(sql: sql, name: name, binds: binds, prepare: prepare)
      intent.execute!
      intent.cast_result # ActiveRecord::Result を返す
    end

    ※「実行は intent に任せて、自分は結果の最終形式だけ選ぶ」形に寄る。

  2. adapter 固有ロジックは「QueryIntent から呼ばれる側」へ

    • PostgreSQL / MySQL / SQLite 各アダプタの database_statements.rb / schema_statements.rb では、
      • クエリ生成
      • EXPLAIN
      • スキーマ dump など で SQL を組み立てるところは残しつつ、 実行部分を QueryIntent がラップしやすいように抽象化しています。
  3. 差分特徴 (ざっくり)

    • abstract_mysql_adapter.rb / mysql2/database_statements.rb:
      • prepared statement モードや、exec_query / select_rows 周りで QueryIntent 対応。
    • postgresql/database_statements.rb / postgresql_adapter.rb:
      • PostgreSQL 固有の async_exec / extended query / prepared statement 等を QueryIntent 経由で扱いやすくするための整理。
    • sqlite3/database_statements.rb / sqlite3_adapter.rb:
      • 単純な execute / query 系を QueryIntent デザインに合わせてリファクタ。

async queue への特記事項

PR 説明より:

with the exception of the async queue, which needs a little extra handling, QueryIntent instances live and die all within a single select_all-or-similar method call on an adapter, so there's no lifetime issue

  • 通常の同期クエリでは、QueryIntent は 1 回の select_all などの呼び出しの内部で生成〜破棄され、ライフタイムの問題は生じません。
  • ただし「非同期キュー (async queue)」では、クエリが「スケジューリング → 後で実行」という流れになるため、
    • QueryIntent のライフサイクル
    • アダプタとの関連付け に追加のケアが必要になる、という前提が明示されています。
  • 現時点では「例外として注意している」段階で、今後の PR で async まわりをきちんと QueryIntent 対応にする布石と考えられます。

  1. 影響範囲・注意点

ライブラリ開発者 / アダプタ拡張を書いている人向け

  • カスタムアダプタ / 自作 connection adapter を実装している場合:

    • database_statements.rb 相当で select_all / exec_query などをオーバーライドしているコードは、QueryIntent 前提の新しいフローに追随する必要が出てきます。
    • 具体的には:
      • QueryIntent 生成に関与するメソッド
      • QueryIntent から呼ばれる実行メソッド(例: run_query(intent) のようなもの)が増えている可能性があるため、Rails 本体のアダプタ実装をベースに追従するのが安全です。
  • 非同期クエリや独自のクエリキューを使っている場合:

    • QueryIntent のライフタイムとアダプタとの紐付けに注意が必要です。
    • 「意図だけ先に作って後でアダプタに渡す」ような実装をしていると破綻する可能性があります(QueryIntent は「どのアダプタに属するか」を前提に動くため)。

一般的なアプリケーション開発者向け

  • 公開 API (ActiveRecord::Base.connection.select_all, Model.where(...).load 等) の挙動は互換を保つように設計されています。
  • この PR は主に内部実装の責務分担の変更であり、アプリコードから直接 QueryIntent を触っていなければ、基本的にはコード変更なしでそのまま動作することが期待されます。
  • ただし、以下のようなケースでは注意:
    • ActiveRecord::ConnectionAdapters の内部クラス/メソッドを直接呼んでいた
    • 独自パッチで database_statements.rb のメソッドを再定義していた
    • QueryIntent を内部的に利用していた gem を使っている
      → Rails のバージョンアップ時に、その gem 側の追従が必要になる可能性があります。

  1. 参考情報 (あれば)
  • 元 PR: #56129 「QueryIntent#execute!」
  • フォローアップ元の PR: #55897
    • こちらで QueryIntent 周りのリファクタ・導入が進められており、本 PR はその続きとして「実行元ドライバー化 & execute! 追加」が行われています。
  • 変更対象主要ファイル:
    • activerecord/lib/active_record/connection_adapters/query_intent.rb
    • activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
    • 各アダプタ:
      • postgresql_*.rb
      • mysql*/database_statements.rb
      • sqlite3_*.rb

内部 API レベルの変更が多いため、アダプタ実装や ActiveRecord 内部に踏み込む開発をしている場合は、query_intent.rbdatabase_statements.rb を重点的に確認しておくと理解が進みます。


#56237 Add langauge to code examples in asset pipeline guide [ci skip]

マージ日: 2025/11/26 | 作成者: @p8

  1. 概要 (1-2文で)
    Asset Pipelineガイド内のコード例に、シンタックスハイライト用の「言語指定」を追加したドキュメント改善のPRです。機能追加や挙動変更はなく、Rails本体の動作には影響しません。

  1. 変更内容の詳細
  • 対象ファイル:
    guides/source/asset_pipeline.md

  • 変更内容の趣旨:
    ガイド中のコードブロックに、Markdown のフェンスコードの「言語ラベル」を付与することで、

    • ガイドサイト上のシンタックスハイライトを有効にする
    • 読みやすさ・理解しやすさを向上させる
      という目的のドキュメントのみの変更です。
  • 実際の変更イメージ(例)

    具体的な差分はPR情報からは全文が分かりませんが、Rails Guidesでは典型的に以下のような変更になります。

    変更前(言語指定なし):

    ```ruby
    # 言語指定なしの例(実際には何も書かれていないか、誤った指定)
    get "products/:id" => "catalog#show"
    ```

    変更後(言語指定あり):

    ```ruby
    get "products/:id" => "catalog#show"
    ```

    あるいは Sprockets のマニフェスト例などでは:

    変更前:

    ```
    //= link_tree ../images
    //= link_directory ../stylesheets .css
    ```

    変更後:

    ```js
    //= link_tree ../images
    //= link_directory ../stylesheets .css
    ```

    実際にはこのPRでは2行追加・2行削除なので、上記のような「言語ラベルの追加」や「誤ったラベルの修正」が1〜2箇所行われたと考えられます。


  1. 影響範囲・注意点
  • 実行コード・フレームワーク挙動への影響:

    • なし(ドキュメントのみの変更)
  • 影響範囲:

    • Rails Guides(公式ドキュメント)のAsset Pipelineページの表示
    • 主に Web 上でガイドを読む際のコードハイライトが改善されます。
  • 注意点:

    • アプリケーション側の設定やコードを変更する必要はありません。
    • ドキュメントの内容(コード例の意味・構成)は変わっておらず、視覚的な改善のみです。

  1. 参考情報 (あれば)
  • PRのメタ情報から分かること:

    • タイトル: Add langauge to code examples in asset pipeline guide [ci skip]
      • [ci skip] から、CI を回さない軽微なドキュメント変更であることが明示されています。
    • 変更行数(+2/-2)から、1〜2箇所のコードブロックの言語指定を付与・修正しただけの非常に小さな変更であることが分かります。
  • 関連ガイド:


#56231 Handle asynchronous raise in all of reconnect!

マージ日: 2025/11/25 | 作成者: @byroot

  1. 概要 (1-2文で)
    ActiveRecord::ConnectionAdapters::AbstractAdapter#reconnect! 全体を、非同期的な例外(特に TimeoutError など)が途中で発生しても接続状態が中途半端に壊れないようにする変更です。
    以前の PR (#54738) で導入された「非同期例外から状態を保護する仕組み」を reconnect! メソッド全体に拡張しています。

  1. 変更内容の詳細

背景

reconnect! は内部で以下のような処理を行いますが、いずれも時間がかかる可能性があり、タイムアウトや他スレッドからの割り込みなど「非同期に例外が飛んでくる」リスクがあります。

  • reconnect(実際の DB 再接続処理)
  • reset_transaction(トランザクション状態のリセット)
  • clear_cache(クエリキャッシュなどのクリア)

これらの処理の途中で Timeout::Error などが発生すると、
「接続は切れているのにトランザクション状態フラグだけ残っている」
「キャッシュだけ消えていない」
といった、中途半端で不整合な状態になり得ます。

以前の PR (#54738) では同様の問題の一部を解消しましたが、reconnect! 全体をカバーし切れていませんでした。

今回の変更のポイント

  • AbstractAdapter#reconnect! 内の
    • reconnect
    • reset_transaction
    • clear_cache
      といった一連の処理を、「非同期例外を安全に扱うための保護ブロック」で囲むようにリファクタリング。
  • これにより、これらの処理中に TimeoutError などが発生しても、
    • 内部状態が壊れない
    • 少なくとも「壊れた状態で生き続ける」ことを防ぎ、次回以降の操作で致命的な不整合を起こさない
      ようにしています。
  • 実装的には、#54738 で導入された仕組み(例: アダプタ内部の「非同期例外から保護されたセクション」を扱うメソッド)を reconnect! 全体に適用する形になっています。

※ 実際のコードイメージ(簡略化・疑似コード)

ruby
def reconnect!
  protect_from_async_raise do
    disconnect! if active?
    reconnect
    reset_transaction
    clear_cache!
  end
end

protect_from_async_raise は名前は仮ですが、

  • ブロックの途中で非同期例外が発生しても状態が壊れない/少なくとも壊れた状態を外に見せない
    ようにするためのラッパーです(内部的にフラグ管理・例外の再送出タイミングの制御などを行っていると考えられます)。

  1. 影響範囲・注意点
  • 影響範囲
    • ActiveRecord を使っているすべてのアプリケーションで、reconnect! を経由するケース(接続プールの再接続、自動再接続、あるいはユーザーコードから明示的に reconnect! を呼ぶ場合)に影響します。
    • 特に、Timeout.timeout で DB アクセスまわりをラップしているアプリケーションや、スレッド/Fiber ベースの割り込み・キャンセルを多用している環境で恩恵が大きいです。
  • 挙動の変化
    • 正常系の挙動は基本的に変わりません。
    • 異常系(タイムアウト・割り込み発生時)に、
      • 「接続状態・トランザクション状態・キャッシュ状態がより一貫した形で保たれる」
        ようになります。
  • 互換性上の注意点
    • 「reconnect 中に TimeoutError が起きた場合に、たまたま前のコネクションやトランザクション状態が残っていた」というような、これまでの“偶然の”挙動に依存していたコードは、今回より安定した・明示的な失敗パターンに変わる可能性があります。
    • ただし、それは本来依存すべきでない不安定な挙動であり、今回の変更は実質的にバグフィックスの性質が強いものです。
  • パフォーマンス
    • 非同期例外保護のためのラッパー処理が増えますが、reconnect! 自体が頻繁に叩かれるメソッドではないため、一般的なアプリケーションではパフォーマンス影響はほぼ無視できると考えられます。

  1. 参考情報 (あれば)
  • 元 PR: https://github.com/rails/rails/pull/56231
  • 関連 PR(今回のフォローアップ元): https://github.com/rails/rails/pull/54738
  • 非同期例外・Timeout と ActiveRecord 接続に関する一般的な注意点:
    • Ruby の Timeout.timeout はスレッドレベルの非同期例外を投げるため、DB 接続やトランザクションなど「途中で例外を投げられると壊れやすい処理」との相性が悪いことが知られています。
    • Rails 本体側でこうした箇所を保護していく流れが続いており、本 PR もその一環です。

#56226 Address Lint/UselessAssignment: Useless assignment to variable - sql.

マージ日: 2025/11/25 | 作成者: @yahonda

  1. 概要 (1-2文で)
    Rubocop の Lint/UselessAssignment に指摘されていた「使われていない変数 sql の代入」をテストコードから削除した PR です。機能的な挙動は変えず、静的解析の警告を解消するためのリファクタリングです。

  1. 変更内容の詳細(あればサンプルコードも含めて)

対象ファイル:

  • activestorage/test/controllers/representations/redirect_controller_test.rb

Rubocop 実行時に以下の警告が出ていました:

text
test/controllers/representations/redirect_controller_test.rb:96:7: W: [Correctable] Lint/UselessAssignment: Useless assignment to variable - sql.
      sql = event.payload[:sql]
      ^^^

この sql 変数は、その後どこでも参照されておらず、完全に「代入して終わり」になっているため、Rubocop が「無意味な代入」と判断しています。

PR では、この行をまるごと削除しています:

ruby
# 変更前(該当箇所のイメージ)
ActiveSupport::Notifications.subscribed(callback, "sql.active_record") do
  # ...
  sql = event.payload[:sql]  # ← ここが削除された
  # (この後 sql は一切使われていない)
end

変更後は単純にこの代入行だけがなくなり、他のロジック・アサーションには一切手を入れていません。追加行はゼロで、削除行が 1 行のみです。


  1. 影響範囲・注意点
  • 影響範囲

    • 対象は Active Storage のコントローラテスト (representations/redirect_controller_test) のみです。
    • 実際のアプリケーションコード(プロダクションコード)には一切影響しません。
    • 変数 sql はもともと使われていなかったため、この削除によるテストの意味・検証内容の変化もありません。
  • 注意点

    • もし今後このテスト内で event.payload[:sql] を検証したくなった場合は、再度変数を導入するか、直接 event.payload[:sql] を参照する形でアサートを書く必要があります。
    • Rubocop の Lint/UselessAssignment は CI で必須チェックになっている可能性が高いため、同様に「代入して使っていない変数」は他のテストやコードでも警告の対象になります。
      → デバッグ用の一時変数を残したままコミットしないよう注意が必要です。

  1. 参考情報 (あれば)
  • 対応している Rubocop の警告: Lint/UselessAssignment: Useless assignment to variable - sql.
  • 関連 PR: 「Follow up #56225」とあるため、#56225 で行った Rubocop 対応・関連リファクタリングの追加対応と考えられます。
  • 変更統計:
    • 変更ファイル数: 1
    • 追加行数: 0
    • 削除行数: 1

#56225 Ensure variants are loaded and processed synchronously just once

マージ日: 2025/11/24 | 作成者: @rosa

  1. 概要 (1-2文で)
    Active Storage のプレビュー用バリアント生成処理で、同一リクエスト内なのにバリアントを二重にロード・生成しようとしていた問題を解消する PR です。GET /rails/active_storage/representations/redirect 時に、生成直後のバリアントをレプリカDBから再検索して失敗→再生成して競合する挙動を防ぎ、1回の同期処理で済むようにしています。

  1. 変更内容の詳細

背景となる問題

ActiveStorage で「プレビュー可能な blob」(動画・PDF など)に対して GET /rails/active_storage/representations/redirect/... を叩いたときの流れは大まかに以下です。

  1. ActiveStorage::Representations::BaseControllerrepresentation をセットしつつ processed を呼んでバリアントを生成
  2. ActiveStorage::Representations::RedirectController@representation.url を呼び、リダイレクト先 URL を取得

ところが内部実装上:

  • ActiveStorage::Blob#variant を呼ぶたびに、新しい ActiveStorage::VariantWithRecord インスタンスが生成される
  • VariantWithRecord#processed
    • レコードがなければ writer(primary)DB に手動で接続して variant レコードを作成
    • そのインスタンス内では @record をメモ化している
  • しかし 2 回目 (@representation.url の中) は「別インスタンス」の VariantWithRecord#processed が呼ばれ、
    • 今度は replica 側で variant レコードの存在チェックを行う
    • レプリカが primary と同期していないタイミングだとレコードが見つからない
    • その結果、プレビューを再ダウンロード&再加工しようとし、最終的に「レコードは既に存在する」ため作成時に失敗(500)する

という構造的なレースコンディション/二重処理が発生していました。

この PR の主な変更点

1. ActiveStorage::Preview の変更

変更ファイル: activestorage/app/models/active_storage/preview.rb (+1/-1)

ActiveStorage::Preview#image から返すオブジェクト(内部的には VariantWithRecord)の扱いを変えることで、同じリクエスト内では同じバリアントインスタンスを再利用するようにしています。

具体的には(実際のコードはリンク先参照ですが概念的には):

  • 以前:
    • preview.image を呼ぶたびに blob.variant(変換オプション) を呼び、新しい VariantWithRecord インスタンスを生成
  • 変更後:
    • プレビュー用のバリアントを一度生成したら、インスタンスをプレビューオブジェクト側に持たせて再利用する(= 2 回目以降は VariantWithRecord を作り直さない)

これにより、

  • 1 回目 (BaseController での processed 呼び出し) で生成・保存された variant レコードを、そのままメモリ上の同一インスタンスから参照できる
  • 2 回目 (@representation.url) では、既に primary で作られたレコードを持っているインスタンスに対して processed 相当の処理が走るため、レプリカ側で再検索し直す必要がなくなる

結果として:

  • 変換処理(画像生成)は 1 回で済む
  • DB への variant レコード作成も 1 回で済む
  • レプリカ/プライマリ間のレイテンシに起因する 500 エラーを回避

という動作になります。

2. テストの追加

変更ファイル: activestorage/test/controllers/representations/redirect_controller_test.rb (+23/-0)

RedirectController 経由のリクエストで、バリアントの処理が一度だけ行われることと、同じインスタンスが再利用されることを前提とした挙動をテストで担保しています。

想定されるテスト内容(要約):

  • GET /rails/active_storage/representations/redirect/... を叩いたときに:
    • プレビュー用バリアントが同期的に一度だけ生成される
    • 500 エラーにならず、正常にリダイレクト URL が返る
  • (必要なら)2回目のリクエスト時は既存の variant レコードが利用される

  1. 影響範囲・注意点
  • 対象: Active Storage を使っており、かつ「プレビュー可能な blob(動画・PDF 等)」の representation を GET /rails/active_storage/representations/redirect で配信しているアプリ
  • 主な効果:
    • 同一リクエスト内でのバリアント二重生成が抑制される
    • レプリカDBを用いる構成で発生しうる、「variant レコードが primary にあるのにレプリカで見えない」ことによる 500 エラーが減る
    • CPU 負荷(画像/動画変換の重い処理)の削減、S3 等ストレージへの不要な再アップロード・再ダウンロードの削減
  • 注意点:
    • 挙動としてはバグ修正であり、通常の利用では互換性問題はほぼない想定
    • もし独自に ActiveStorage::PreviewVariantWithRecord にパッチを当てている場合、インスタンスの再利用を前提としたこの変更と競合しないか確認が必要
    • 「同じ preview.image 呼び出しなのに毎回別インスタンスであること」を前提にしていた非常に特殊な実装がもしあれば、その前提は崩れます(一般的には想定外)

  1. 参考情報 (あれば)

レプリカ構成で Active Storage のプレビュー周りの 500 が出ている場合、この修正を含む Rails バージョンへアップデートすることで改善が見込めます。


#56216 Fix TimeWithZone#xmlschema when wrapping DateTime local time

マージ日: 2025/11/24 | 作成者: @drymar

  1. 概要 (1-2文で)
    ActiveSupport::TimeWithZone#xmlschema が DateTime を「ローカル時刻」としてラップした場合に不正な ISO-8601 文字列を返してしまうリグレッションが修正されています。タイムゾーン部分の扱いを見直し、Time / DateTime どちらをラップしても正しいオフセット付き文字列を返すようになりました。

  1. 変更内容の詳細 (サンプルコード含む)

問題の内容

ActiveSupport::TimeWithZoneDateTime をローカル時刻として渡した場合、#xmlschema が不正な文字列を生成していました。

ruby
tz  = ActiveSupport::TimeZone["America/New_York"]
twz = ActiveSupport::TimeWithZone.new(nil, tz, DateTime.new(2025, 11, 7, 12))

twz.xmlschema
# 修正前の実際の出力
# => "2025-11-07T12:00:00+00:0-05:00"

本来期待されるのは以下のような、1つの正しいオフセットだけを持つ文字列です:

ruby
"2025-11-07T12:00:00-05:00"

原因は、TimeWithZone#xmlschema の実装が「time.iso8601 は末尾が常に "Z" で終わる」という前提で書かれていたことです。
Time ではその前提が成り立つケースが多い一方、DateTime#iso8601 はすでに +00:00-05:00 などのオフセットを含むため、末尾1文字だけ書き換える処理だと不正な文字列になっていました。

修正内容

TimeWithZone#xmlschema 内の置換ロジックを、末尾1文字ではなく「タイムゾーン部分全体」を対象に変更しています。

変更点の核心は以下の正規表現置換です:

ruby
str.sub(/(Z|[+-]\d{2}:\d{2})\z/, formatted_offset(true, "Z"))

ポイント:

  • / (Z | [+-]\d{2}:\d{2}) \z /
    • 末尾 (\z) にある
    • "Z" もしくは +HH:MM / -HH:MM 形式のオフセット
    • をマッチ対象にする。
  • formatted_offset(true, "Z")
    • TimeWithZone の持つタイムゾーン情報から正しいオフセットを算出し、±HH:MM 形式(もしくは 0 のとき "Z")の文字列を返す既存のヘルパーメソッド。
  • これにより、元の Time / DateTime インスタンスがどのようなオフセット表現を持っていても、末尾のタイムゾーン表現を TimeWithZone のタイムゾーンに基づく正しい値に「置き換える」ようになります。

テストの追加

リグレッションテストが追加されています:

  • test_xmlschema_with_datetime_local_time

テスト内容は概ね次のような形です(説明用の擬似コード):

ruby
tz  = ActiveSupport::TimeZone["America/New_York"]
twz = ActiveSupport::TimeWithZone.new(nil, tz, DateTime.new(2025, 11, 7, 12))

assert_equal "2025-11-07T12:00:00-05:00", twz.xmlschema

このテストにより、DateTime をローカル時刻として扱うケースで期待通りの ISO-8601 文字列が生成されることを確認しています。


  1. 影響範囲・注意点
  • 影響を受けるケース:
    • ActiveSupport::TimeWithZoneDateTime オブジェクトを「ローカル時間」として渡し、xmlschema(あるいは iso8601 相当のシリアライズ)を利用しているコード。
    • 特に、API レスポンスや XML/JSON シリアライズで TimeWithZone#xmlschema を直接/間接的に呼んでいる場合。
  • 振る舞いの変化:
    • バグ修正のみであり、正しい ISO-8601 を生成する方向の変更です。
    • 既に壊れた形式(例: "2025-11-07T12:00:00+00:0-05:00")が出ていた部分は、正しい形式("2025-11-07T12:00:00-05:00")に変わります。
    • それ以外の Time をラップした TimeWithZone については、元々期待されていた動作からの変更はありません。
  • 互換性:
    • 仕様としては「正しい ISO-8601 出力をする」という意図に沿った修正なので、通常は後方互換性上の問題はありません。
    • ただし、もし既存システムが「バグったフォーマット」を前提にパースしているような極端なケースがあれば、その部分は修正が必要です(普通はありえない想定です)。

  1. 参考情報 (あれば)
  • 該当 issue: https://github.com/rails/rails/issues/56112
  • 修正対象メソッド:
    • ActiveSupport::TimeWithZone#xmlschemaactivesupport/lib/active_support/time_with_zone.rb
  • 回帰テスト追加箇所:
    • activesupport/test/core_ext/time_with_zone_test.rb
  • 変更規模:
    • ファイル数: 2
    • 追加 14 行 / 削除 3 行
    • CHANGELOG なしの小規模バグフィックスとして扱われています。

#56212 Fix CI eager loading when rake tasks invoke :environment before tests

マージ日: 2025/11/23 | 作成者: @trevorturk

  1. 概要 (1-2文で) このPRは、テスト環境でのconfig.eager_load設定が、CIにおいて:environment Rakeタスクがテスト前に実行されると意図した通りに機能しない問題を修正します。

  2. 変更内容の詳細

  • 問題点として、config.eager_load = ENV["CI"].present?が、tailwindcss-railsのようなGEMが:environmentタスクをフックする際にconfig.rake_eager_load = falseにオーバーライドされてしまい、CIでeager loadingが無効化されることが挙げられています。
  • 本PRでは、config.rake_eager_load = ENV["CI"].present?を生成されるテスト環境のテンプレートに追加しました。これにより、Rakeタスクがテスト前に実行されても一貫してeager loadingが実行されるようになります。
  1. 影響範囲・注意点
  • この修正は、CI環境でのeager loadingが確実に実行されることを保証し、特にZeitwerkのエラーを持つアプリケーションがbin/rails testbin/rails zeitwerk:checkで異なる結果を出す問題を防ぎます。
  • また、このアプローチはRakeタスク自体の取り扱いを変更するのではなく、設定ファイルを調整することで問題を解決しており、他の部分に影響を与えるリスクが少ないと考えられます。
  1. 参考情報 (あれば)
  • この問題は、Rakeタスクがデフォルトでeager loadingを無効化する方式から来ており、特にRails 5.1以前の遺産問題に関連しています。rake_eager_loadの設定を利用することで、Rakeタスクにおいても一貫したロード順序を保つことができます。
  • 詳細な再現手順と問題の背景については、作成者が用意した最小限の再現ケース(https://github.com/trevorturk/issue_56065)を参照してください。

#56218 [ci skip] Add extension schema to the db/schema.rb example.

マージ日: 2025/11/23 | 作成者: @kakudou3

  1. 概要 このプルリクエストは、Ruby on Railsのdb/schema.rbのサンプルに拡張スキーマ情報を追加することで、Rails 8以降を使用するユーザーの混乱を避けることを目的としています。

  2. 変更内容の詳細 active_record_migrations.mddb/schema.rbのサンプルコードが、スキーマダンパーの更新に伴って不足している情報を反映していなかったため、これを修正しました。具体的には、PostgreSQLのplpgsqlpg_catalogスキーマに存在することを反映し、サンプルをpg_catalog.plpgsqlとして更新しました。これはPostgreSQL 16.1で確認されています。

  3. 影響範囲・注意点 この変更はドキュメントの更新に該当し、直接的なコードの変更やアプリケーションの動作には影響を及ぼしません。しかし、新しいRailsバージョンに移行するユーザーにとって有益であり、拡張機能の有効化に関する理解を助けます。

  4. 参考情報 このPRは、以前のPR #52313で行われたスキーマダンパーの変更を反映する意図があります。また、PostgreSQL 16.1の拡張機能情報を参照しており、plpgsqlpg_catalogスキーマに属することが確認されています。