Skip to content

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. 概要 (1-2文で)
    このPRは、トランザクションの分離レベル (isolation level) を一時的に変更した後に元に戻らないバグと、requires_new: true なネストトランザクションで isolation 指定が正しく扱われないバグを修正しています。Active Record のトランザクション分離レベルを多用するアプリケーション(例: Shopify)のワークフローに直接影響する問題へのピンポイント修正です。

  1. 変更内容の詳細

※実際のコード全文は省略しますが、PR説明と diff 情報から復元できる挙動レベルで解説します。

背景: #55549 での変更漏れ

#55549 で導入された「トランザクションの isolation レベルをブロック単位で変更する」機能において:

  1. ブロック実行後に current_transaction.isolation を元の値に戻していなかった
  2. requires_new: true のネストトランザクション時に isolation 指定を正しく扱っていなかった

という2点のバグが発生していました。

修正ポイント1: isolation レベルのリセット

ActiveRecord::ConnectionAdapters::DatabaseStatements#transaction(あるいはそれを内部で呼び出すヘルパー)の中で、
「トランザクション開始前の isolation」を退避し、ブロック終了後に必ず元の isolation を復元するように修正されています。

イメージとしては、以下のような処理になります:

ruby
def transaction(**options, &block)
  # もともとの isolation を保存
  previous_isolation = current_transaction.isolation

  # options[:isolation] が指定されていれば、その isolation でトランザクション開始
  # ...

  yield
ensure
  # ブロック終了後に isolation を元の値に戻す
  current_transaction.isolation = previous_isolation
end

これにより、以下のようなケースで isolation が「後続のトランザクションにも引き継がれてしまう」という誤動作が解消されます。

ruby
# 例: これまでは tx2 も :serializable のままになってしまうバグがあった

User.transaction(isolation: :serializable) do
  # ここだけ serializable を想定
  ...
end

User.transaction do
  # 本来はデフォルトの isolation (DB の設定) を期待
  ...
end

PR後は、tx1 の isolation 指定は tx1 のスコープだけに閉じるようになります。

修正ポイント2: requires_new: true での isolation の扱い

requires_new: true を指定したネストトランザクションに isolation を渡した場合のハンドリングも修正されています。

典型例:

ruby
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では、

  1. requires_new の inner トランザクションについても isolation が正しく設定される
  2. inner の終了後に、outer トランザクションの isolation に確実に戻す

というロジックが追加されています。

テストの追加

activerecord/test/cases/transaction_isolation_test.rb に8行のテストが追加されており、少なくとも以下のようなシナリオをカバーしていると考えられます:

  • isolation を指定したトランザクションの後、isolation 未指定のトランザクションを開始すると元の isolation に戻っていること
  • requires_new: true のネストトランザクションで isolation を指定しても、外側の isolation は変化しないこと

これにより、前述の2つのバグが再発しないことを保証しています。


  1. 影響範囲・注意点

影響範囲

  • Active Record のトランザクション API を使い、特に isolation: オプション・requires_new: true を併用しているコードが対象です。
  • transaction を素朴に transaction do ... end とだけ使っているアプリケーションへの影響はほぼありません(isolation を明示指定していなければ振る舞いは DB デフォルトのまま)。

アプリケーション側で変わる可能性がある挙動

  1. 「意図せず高い isolation が引き継がれていた」ケースが正常化される
    以前は、あるブロックで isolation: :serializable を指定すると、その後のトランザクションでも serializable が継続してしまっていた可能性があります。
    これを前提にしていた(=バグ依存)コードがあると、以降のトランザクションの isolation が「元に戻る」ことで挙動が変わります。

  2. requires_new ネスト時の isolation がより直感的になる
    requires_new 付きの中で isolation を変えると、その効果は inner トランザクションの中だけに留まり、outer には漏れなくなります。
    これにより、想定していた isolation 境界(outer: read committed / inner: serializable など)が、実装通りに機能するようになります。

注意点

  • 「特定の処理以降、アプリ全体のトランザクション isolation が変わる」という挙動に気づかずに依存していた場合、パフォーマンス特性やデッドロック発生率が変わる可能性があります。
    isolation を明示指定している部分があれば、その前後のトランザクションの挙動を確認した方が安全です。
  • マルチDB構成やカスタム connection handler を使っている場合も、current_transaction 単位で isolation がきちんとスコープされるようになるため、 isolation まわりの暗黙的な前提がないかを確認してください。

  1. 参考情報 (あれば)
  • このPRで修正している元の変更:
  • 同じ著者による、より大きな関連 PR(今回よりスコープが広いもの):
  • 対象ファイル:
    • activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
    • activerecord/test/cases/transaction_isolation_test.rb
    • activerecord/CHANGELOG.md (バグ修正として明記)

このPRは、小さなコード変更で isolation のスコーピングを正す「バグフィックス」であり、API 仕様の変更というよりは、ドキュメントや直感に沿った挙動への修正、と考えると理解しやすいです。


#56299 Fix typos in command line guide

マージ日: 2025/12/5 | 作成者: @Saidbek

  1. 概要 (1-2文で)
    Rails ガイド「Command Line」のドキュメント中の軽微なタイポ(重複語・スペース抜け・重複行)を修正した PR です。コードや挙動の変更はなく、文章表現のみの修正です。

  2. 変更内容の詳細
    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
  • 「Thebin/rails」のように "The" とバッククォートの間のスペースが抜けていた箇所を修正

    • 修正前: The\bin/rails``
    • 修正後: The \bin/rails``

いずれもガイドの可読性・整合性を高めるための、純粋な文章レベルの修正です。

  1. 影響範囲・注意点
  • 対象はドキュメントガイドのみであり、Rails 本体のコード、挙動、API には一切影響がありません。
  • コマンドの使い方や意味は変わっておらず、既存アプリケーションやツールチェーンへの影響もありません。
  • ドキュメント参照時に、より正確・自然な英語で読めるようになります。
  1. 参考情報 (あれば)

#56213 Respect config.log_level to emit debug events

マージ日: 2025/12/5 | 作成者: @claudiob

  1. 概要 (1-2文で)
    Rails の「デバッグイベントを出すかどうか」の判定条件を、環境名(development かどうか)ではなく config.log_level に基づくように変更する PR です。これにより、RAILS_LOG_LEVEL=debug を設定すれば test や production でもデバッグイベントを出力できるようになります。

  1. 変更内容の詳細

変更点の本質

以前の PR #55657 では、「開発環境 (development) のときだけデバッグイベントを有効にする」という実装になっていました。この PR では、その条件を:

ruby
Rails.env.development?

から:

ruby
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. 環境変数で指定(推奨されている使い方)

bash
RAILS_LOG_LEVEL=debug bin/rails server

RAILS_LOG_LEVEL は Rails が config.log_level に反映するので、この PR により debug イベントが有効になります。

2. 設定ファイルで直接指定

config/environments/production.rb:

ruby
Rails.application.configure do
  config.log_level = :debug
end

これにより production でも development 同様にデバッグイベントが出力されます。


  1. 影響範囲・注意点
  • 環境に依存しない挙動になる
    以前は「development だけ特別扱い」でしたが、今後は「ログレベルが debug かどうか」で決まります。

    • test / production で config.log_level = :debug にしているプロジェクトでは、これまで出ていなかったデバッグイベントが新たに出る可能性があります。
  • デフォルト設定では互換性維持
    生成直後の Rails アプリの典型的な設定では:

    • development: log_level = :debug → デバッグイベント「あり」(従来通り)
    • test: 通常は :debug → この PR により test でも debug イベントが出るようになっている可能性がある(以前は「development 限定」だった場合との差分)
    • production: log_level = :info → デバッグイベント「なし」(従来通り)

    test 環境での挙動は、#55657 の実装内容や各プロジェクトの config.log_level 設定に依存します。
    もし test でログが多すぎると感じる場合は、config/environments/test.rbconfig.log_level:info 以上に上げることで抑制できます。

  • 本番で debug を有効にする際の注意

    • ログ量が非常に増える可能性があるため、ストレージ・ログローテーション・ログ集約基盤(例: ELK、Loki など)の負荷に注意が必要です。
    • デバッグログに機微情報が含まれる場合、コンプライアンス上のリスクも増えます。production で :debug を使う場合は、ログ内容の棚卸し・マスキングを検討してください。

  1. 参考情報 (あれば)

#51238 Extract ActionText::Editor base class and ActionText::TrixEditor adapter

マージ日: 2025/12/5 | 作成者: @seanpdoyle

  1. 概要 (1–2文で)
  • Action Text にエディタ共通の基底クラス ActionText::Editor と、Trix 向けアダプタ ActionText::TrixEditor を導入し、「Trix 前提の API」を整理・非推奨化した PRです。
  • これにより、将来的に Trix 以外のリッチテキストエディタを公式な拡張ポイントから統一的に統合できる土台が整えられています。

  1. 変更内容の詳細

2-1. ActionText::Editor 基底クラスとアダプタ層

新しく以下の構成が導入されています。

  • 基底クラス: ActionText::Editor
  • アダプタ:
    • ActionText::TrixEditor(Trix 用)
  • 周辺クラス:
    • ActionText::Editor::Registry
    • ActionText::Editor::Configurator

これらは「エディタごとの差異を Action Text 本体から切り離す」ためのアダプタ層です。
従来はクラス名やメソッド名、引数名に trix が直接現れていた箇所がありましたが、それらを editor ベースの抽象化に移し替えています。

主な責務の集約

PR 説明にある通り、多数のクラスメソッド・インスタンスメソッドが ActionText::TrixEditor へ集約されています。
多くは「元の実装をそのままコピー」したうえで、命名に含まれる trixeditor に置き換える程度の変更に留めてあり、振る舞いは最大限維持されています。

例イメージ(あくまで概念的なもの):

ruby
# 以前: 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 つの種類の非推奨があります。

  1. モジュール / クラスの非推奨

    • Trix 特化、または内部用であるにもかかわらず public な定義だったクラス / モジュールが対象。
    • 今回、責務の一部または全てを ActionText::Editor / ActionText::TrixEditor 側へ移譲し、旧クラス/モジュールは deprecate されたうえで、新実装経由で動くようにラップされているものがあります。
    • ソース中では :nodoc: が付いていなかったものの、実質 internal 想定の物が主な対象です。
  2. メソッドの非推奨

    • 名前や引数に trix を含むもの、またはドキュメント / コメントがなく internal 想定と判断された public メソッドが対象。
    • 可能な限り、旧メソッドは warning を出しつつ ActionText::TrixEditor のメソッドを呼び出すだけの thin wrapper に変わっているため、既存コードは直ちに壊れずに移行が可能です。

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

ruby
# 旧 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(...)
  # 実装は従来とほぼ同じ
end

2-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 を登録する処理が含まれている形が想定されます。
  • 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 選択の書き方

  1. 影響範囲・注意点

3-1. 一般的な Rails アプリ (通常の Action Text + Trix 利用)

  • rich_text_area など、通常の公開 API を使っているだけのアプリは基本的にそのまま動作します。
  • ただし、以下に当てはまる場合は deprecation warning が出る可能性があります。
    • ActionText::TrixAttachmentActionText::Content の Trix 依存メソッドを直接呼んでいる
    • trix_* を名前に含むメソッドをアプリケーション側で利用している

現時点では互換ラッパーがあるので即座に壊れることは少ないですが、ログの deprecation warning をチェックし、将来のメジャーバージョンでの削除を見越して 新しい Editor ベースの API への移行を検討した方がよいです。

3-2. エディタ連携を拡張しているライブラリ / エンジン作者

  • Action Text と外部エディタ(例: Quill, CKEditor)を連携する gem / plugin を書いている場合、この PR はかなり重要です。

    • これまで Trix 専用の内部 API をハックしていた部分を、ActionText::Editor のサブクラスとして正式に実装できる方向性が示されました。
    • まだアダプタインターフェイスは「実装詳細扱い」ですが、今後ここが公式拡張ポイントとして明文化されていく流れが読み取れます。
  • 新規実装の基本方針:

    1. class MyCoolEditor < ActionText::Editor を定義
    2. Registry に登録(ActionText::Editor::Registry.register(:my_cool_editor, MyCoolEditor) のような形が想定される)
    3. 添付ファイル変換・HTML 生成・ツールバー設定などを TrixEditor と同等のインターフェイスで実装

まだ細部仕様はコードを読まないと分からないため、この PR を足掛かりに actiontext/lib/action_text/editor/*.rb を確認する必要があります。

3-3. 内部 API への依存

  • PR で「これは実装詳細であり、API としては private のつもり」と明言されているため、ActionText::Editor 一式も将来のバージョンで破壊的変更が入る可能性があります。
  • ライブラリレベルでここに依存する場合は、バージョン制約やテストで追従コストを見込んでおくべきです。

  1. 参考情報
  • PR: https://github.com/rails/rails/pull/51238
  • 関連 Issue: https://github.com/rails/actiontext/issues/41
  • 変更された主なファイル:
    • actiontext/lib/action_text/editor.rb
    • actiontext/lib/action_text/editor/trix_editor.rb
    • actiontext/lib/action_text/editor/registry.rb
    • actiontext/lib/action_text/editor/configurator.rb
    • actiontext/lib/action_text/attachments/conversion.rb
    • actiontext/lib/action_text/attachments/trix_conversion.rb
    • actiontext/lib/generators/action_text/install/install_generator.rb
    • actiontext/lib/action_text/engine.rb
    • actiontext/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. 概要 (1-2文で)
    Railsの「Getting Started」ガイド内で、ナビゲーションバーにログインメニューを追加する手順の記述ファイル名が誤っていた点を修正し、index.html.erb ではなく application.html.erb を参照するように更新したドキュメント専用のPRです。アプリケーションコードや挙動には一切変更はありません。

  1. 変更内容の詳細(あればサンプルコードも含めて)
  • 対象ファイル: guides/source/getting_started.md
  • 変更点の主旨:
    • ガイドの 11.3 節付近で、「Login リンクを index.html.erb に追加する」と書かれていたが、実際にはナビゲーションバーが定義されている application.html.erb に追加するべきであるため、説明文中のファイル名を application.html.erb に修正。
  • 変更されたのは Markdown 上の記述のみで、コード例やロジック自体が変わったわけではなく、「どのファイルを編集するのが正しいか」という参照先の修正です。

(サンプルイメージ: 実際のPR文面からの推定)

erb
<!-- app/views/layouts/application.html.erb のナビバー例 -->
<nav>
  <%= link_to "Home", root_path %>
  <%= link_to "Login", login_path %>
</nav>

従来のガイドでは、上記のようなナビゲーションを index.html.erb 側に書くかのような記述になっていたため、それを「レイアウトファイルである application.html.erb に書く」という正しい形に揃えています。


  1. 影響範囲・注意点
  • 影響範囲:
    • Rails Guides(ドキュメント)のみであり、Rails本体のコード、テスト、挙動には影響しません。
    • Getting Started ガイドを見ながらチュートリアルを進めているユーザーにとって、ナビゲーションバーの編集箇所が正確になり、混乱が減ります。
  • 注意点:
    • 既に旧ガイドの指示通りに index.html.erb にナビバーやログインリンクを追加していた場合は、レイアウト (application.html.erb) に移動するのが望ましい構造です。
      • レイアウトに置けば全ページで同じナビが共有されるため、チュートリアルの意図にも合致します。
    • CHANGELOG は更新されていない通り、リリースノート的な追跡も不要な軽微なDocs変更です。

  1. 参考情報 (あれば)
  • PR本体: rails/rails#56236
  • 関連コンセプト:
    • Railsにおけるレイアウトファイル: app/views/layouts/application.html.erb
      • 全ページ共通のヘッダー/フッター/ナビゲーションバーなどを定義するのが一般的
    • 個別ビュー (index.html.erb など) にナビバーを書くと、他アクションで共有されず冗長になりやすい

このPRにより、Getting Startedガイドが実際のRailsのベストプラクティス(共通ナビはレイアウトに置く)と一致するようになっています。


#51951 ActiveStorage immediate variants

マージ日: 2025/12/5 | 作成者: @tomrossi7

  1. 概要 (1–2文で)
    Active Storage のバリアント(variants)生成方式に、添付と同時に同期的に生成するための immediate: true オプションが追加されました。これにより、これまでの「初回アクセス時にオンデマンド生成」や「バックグラウンドでの preprocessed 生成」に加え、トラフィック集中時の“生成レース”を避ける運用がしやすくなります。

  1. 変更内容の詳細

新オプション immediate: true

has_one_attached / has_many_attached の variant 定義で、既存の preprocessed: true と同じ要領で immediate: true を指定できます。

ruby
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 / preprocessed variants を列挙
  • 指定された attachment について、対象の variants を生成する
  • これにより、複数の variants 生成を一元的に扱えるようになり、テストしやすくなっている

TransformJob は引き続き存在し、個々の変換実行はこれを通じて行われますが、呼び出し元の整理・責務分担が行われています。

ActiveStorage::NamedVariant の拡張

activestorage/app/models/active_storage/named_variant.rb の変更により:

  • 各 named variant が immediate / preprocessed などのオプションを持てるように整理
  • 変換用のオプションと、生成タイミングを表すオプションがはっきり切り分けられた形になっている

概ね次のようなイメージで、variant 定義のメタデータとして管理されます:

ruby
attachable.variant :thumb, resize_to_limit: [100, 100], immediate: true
# => name: :thumb, transformations: { resize_to_limit: [100, 100] }, immediate: true

Attachment 周りの変更

activestorage/app/models/active_storage/attachment.rb などで:

  • 添付の作成時に、関連する immediate variants を検出して生成するフックが追加
  • 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.rb
    • named_variant_test.rb
    • attachment_test.rb など
  • 従来の variant 生成テストの一部が整理・削減(preview_image_job_test.rb ほか)
  • ガイド active_storage_overview.mdimmediate オプションの使い方が追記され、preprocessed との違いが説明されている

  1. 影響範囲・注意点
  • パフォーマンス特性の変化

    • immediate: true を付けた variants は「添付の保存と同じトランザクション or リクエストの中」で生成されるため、
      • 添付時のレスポンスタイムが伸びる可能性
      • 同時に大量のファイルをアップロードするケースでは、アプリサーバ・ストレージの負荷増加
    • 一方で、添付直後のアクセス集中時に variant 生成が競合する問題は緩和されます。
  • immediatepreprocessed の使い分け

    • 高トラフィックで、「アップロード直後に確実に完成した variant を配信したい」場合は immediate: true
    • 生成負荷をアプリリクエストから切り離したい場合は preprocessed: true
    • どちらも付けない場合は従来どおり「初回アクセス時にオンデマンド生成」

    設計としては、UX とインフラ負荷のトレードオフを見ながら、重要な variant だけ immediate にするのが現実的です。

  • 既存コードへの互換性

    • immediate を使わない限り、挙動は基本的に後方互換です。
    • ただし Active Storage の内部クラス(NamedVariant, Representable 周りなど)に依存している場合は、
      • メソッドの呼び出し経路
      • variant メタデータの持ち方 が変わっている可能性があるので、独自拡張をしているプロジェクトは差分確認が必要です。
  • ジョブキュー設定

    • preprocessed / immediate どちら経由であっても、内部では TransformJob などバックグラウンドジョブを使う部分があります。
    • Active Job のアダプタ(Sidekiq, Delayed Job など)の設定やキュー名をカスタマイズしている場合は、新ジョブ CreateVariantsJob のキュー設定も確認してください。

  1. 参考情報 (あれば)
  • 該当 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.rb
    • activestorage/app/models/active_storage/attachment.rb
    • activestorage/app/models/active_storage/named_variant.rb
    • activestorage/app/models/active_storage/variant*.rb

#56279 Use pattern matching on sql instead of string match event payload name

マージ日: 2025/12/5 | 作成者: @zzak

  1. 概要 (1-2文で)
    Active Storage のコントローラテストで、ActiveSupport::Notifications のイベント名文字列に対するマッチングをやめ、代わりに SQL 文の内容に対してパターンマッチするように変更した PRです。テストが内部実装(通知名の文字列形式)の変更に依存しないようにし、より堅牢にしています。

  1. 変更内容の詳細

対象ファイル:

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

このテストでは、ActiveStorage::Representation のリダイレクト処理中に発行される DB クエリ(代表的には SELECT など)が実行されるかどうかを ActiveSupport::Notifications 経由で検証している部分があります。

もともとの実装では、おそらく以下のように「通知イベント名の文字列」を対象にパターンマッチしていました(イメージ):

ruby
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 / 正規表現など)を行うようにテストを変更しています。例としては以下のようなイメージです:

ruby
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 のフォローアップとして、テストの堅牢性を高める目的で行われています。


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

    • 対象は Active Storage の Representations::RedirectController に関するテストのみで、アプリケーション本体の挙動には影響しません。
    • CI 上のテスト安定性が向上し、「通知メッセージのフォーマットが変わっただけ」でテストが落ちるリスクが減ります。
  • 注意点

    • 今後、同様に ActiveSupport::Notifications をテストで利用する場合、イベント名そのものの文字列フォーマットへの依存は避け、より安定した情報(SQL 文、payload の構造化されたキー、バインド値など)で検証するのが推奨されます。
    • SQL に対するパターンマッチも、スキーマ変更(テーブル名やカラム名の変更)には依存する点は変わらないため、「何に依存するか」を意識してテストを書く必要があります。

  1. 参考情報 (あれば)
  • 元 PR: #56225(今回のフォローアップ元。通知に関するテストまわりの変更が含まれている可能性が高い)
  • 関連 API:
    • ActiveSupport::Notifications
    • ActiveSupport::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. 概要 (1-2文で)
    Rails のデバッグビューで RAILS_EDITOR を使っている場合に、ERB テンプレート内の構文エラーが正しく処理されずエラーになる問題を修正した PR です。ActiveSupport::SyntaxErrorProxy が、エディタ連携用に必要な absolute_path を返せるようにしています。

  1. 変更内容の詳細

問題の背景

  • RAILS_EDITOR 環境変数を設定していると、例外発生時のデバッグ画面から「編集」ボタン等を通じて、対応するファイルを手元のエディタで開けるようになります。
  • その際 debug_view.rb では、バックトレースの各フレームから absolute_path を取得して、editor_urlRAILS_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 などの二次的な例外が発生しなくなります。

変更イメージ(概念的な擬似コード):

ruby
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 が正常動作する)。

  1. 影響範囲・注意点
  • 影響範囲:
    • RAILS_EDITOR を設定していて、かつ ERB テンプレート内で構文エラーが起きるケースに限定されます。
    • 通常の例外(構文エラー以外)や RAILS_EDITOR を使っていない環境では挙動への影響はほぼありません。
  • 利用者側での対応:
    • Rails をこのコミットを含むバージョンに更新すると、RAILS_EDITOR 利用時の構文エラー画面が安定し、エディタ連携も期待通り動作します。
    • 既に RAILS_EDITOR を使っていて、構文エラーで debug view が落ちる/エディタリンクが出ないといった症状がある場合、この修正を取り込むことで解消されます。
  • 後方互換性:
    • 新たに public API を壊す変更はなく、BacktraceLocation#absolute_path の追加は、標準のバックトレース仕様に沿う形の拡張であり、後方互換的です。

  1. 参考情報 (あれば)
  • 関連 Issue: #56284
  • 直前の変更(原因となった変更): #55295
  • 該当コード(debug view 側で absolute_path を使っている箇所):
    actionpack/lib/action_dispatch/middleware/debug_view.rbeditor_url 生成周り

#56290 Allow schema_dump configuration to be an absolute path.

マージ日: 2025/12/5 | 作成者: @flavorjones

  1. 概要 (1-2文で)
    database.ymlschema_dump 設定に「絶対パス」を書けるようにし、エンジン固有のスキーマファイルをアプリ本体とは別の場所(例: gem の中)に置けるようにした PR です。既存の相対パスの挙動は維持したまま、絶対パスの場合はそのまま利用されるようになります。

  1. 変更内容の詳細

これまでの挙動

ActiveRecord::Tasks::DatabaseTasks.schema_dump_path は、以下のような扱いをしていました:

  • database.ymlschema_dump 設定値は「DatabaseTasks.db_dir を基準とした相対パス」とみなされる
  • 絶対パスを指定しても db_dir と結合されてしまい、意図通りに使えない
  • 環境変数 SCHEMA だけは絶対パス指定が可能

そのため、エンジン側で

yaml
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 を基準に解決

擬似コードで表すと:

ruby
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 で次のように書けます:

yaml
<% 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 に、この挙動変更(新機能)が記載されています。

  1. 影響範囲・注意点
  • 相対パス指定の既存挙動は変わらない
    • これまでどおり schema_dump: schema.rb などの設定は db/ ディレクトリ配下として扱われます。
  • 絶対パスを指定した場合だけ新挙動
    • schema_dump: /foo/bar/schema.rb のようなパスは、その場所に直接スキーマが出力されます。
  • 複数データベース+エンジン構成で有用
    • マルチ DB(primary, replica, saas など)かつエンジンを gem として分離している場合に、各エンジンごとにスキーマファイルを分けて管理しやすくなります。
  • パスの権限・存在には注意
    • 絶対パス先のディレクトリが存在しない、または書き込み権限がない場合は、当然ながら db:schema:dump 実行時にエラーになります。
    • CI や本番環境などでパスを変える場合は、database.yml の ERB で環境変数等を噛ませる想定になります。

  1. 参考情報 (あれば)

#56283 ActionText: Validate RemoteImage URLs

マージ日: 2025/12/5 | 作成者: @flavorjones

  1. 概要 (1-2文で)
    Action Text でリッチテキストに添付される「リモート画像」の URL を検証し、image.png のような相対パス風の値が誤ってアセットパイプラインに流れないようにした変更です。これにより 500 エラーや意図しないアセット解決/悪用の可能性を減らします。

  1. 変更内容の詳細

背景となる問題

Trix 経由などでリッチテキストに画像が埋め込まれた際、画像の src が以下のような値だったとします:

html
<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 にも、この挙動変更が記載されました。


  1. 影響範囲・注意点

影響範囲

  • 対象は Action Text で「リモート画像」を扱う部分のみ です。
  • 具体的には、Trix エディタなどから埋め込まれた <img src="..."> に対し、
    • src が完全なリモート URL(例: https://...)なら: 従来通り RemoteImage として扱われる。
    • そうでない場合(相対パス等): RemoteImage ではなくなり、MissingAttachable として処理される。

既存アプリへの影響・移行上の注意

  1. 相対パスを「リモート画像」として使っていた場合

    • これまではたまたま動いていた(あるいは 500 を rescue していた)ケースが、
      今後は MissingAttachable としてレンダリングされ、画像が表示されなくなる可能性があります。
    • 対応策:
      • リッチテキストに埋め込む画像 URL は、https://example.com/foo.png などの完全な URL に修正する。
      • あるいは、画像は Active Storage 経由で添付する(<action-text-attachment>)運用に寄せる。
  2. 500 エラー対策として Template::Error を rescue していた場合

    • この PR により、「不正な相対 URL がアセット解決で例外を出す」というパスは通らなくなるので、 そのためだけの rescue は不要になる可能性があります。
    • ただし他の理由で同じ例外を rescue している場合は、その用途と切り分けてください。
  3. セキュリティ上の意味合い

    • 任意の src="foo.png" が、アセットパイプライン経由で別のローカルアセットに解決される可能性が減ります。
    • 特に「ユーザ入力の HTML を Action Text に取り込み、レンダリングしている」ようなアプリでは、 意図しないアセットへのアクセスや情報露出のリスクが下がります。

  1. 参考情報 (あれば)
  • 該当 PR: https://github.com/rails/rails/pull/56283
  • 関連する内部 API:
    • ActionText::Attachables::RemoteImage.from_node
    • ActionText::Content
    • ActionView::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. 概要 (1-2文で)
    Rails の tag.* ヘルパで、引数とブロックの両方でコンテンツを渡した場合に「後勝ちで上書き」されていた挙動が、「結合して連結」される挙動に変更されました。これにより、tag.div("Hello ") { "World" }<div>Hello World</div> を返すようになります。

  1. 変更内容の詳細

これまでの挙動

ActionView::Helpers::TagHelpertag ヘルパ(例: tag.div)において、以下のように文字列引数とブロックを同時に渡した場合:

ruby
tag.div("Hello ") { "World" }

従来はブロック側が優先され、引数の "Hello " は無視されていました:

html
<div>World</div>

これは「サイレントに引数コンテンツを捨てる」挙動でした。

変更後の挙動

この PR により、引数で渡したコンテンツとブロックで返したコンテンツを結合して出力するようになりました。

ruby
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 の追記が行われています。

サンプルコード

ruby
# 以前(〜この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>

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

    • ActionView::Helpers::TagHelpertag API を使い、同じタグ呼び出しで「引数のコンテンツ」と「ブロック」を併用している箇所すべてに影響します。
    • content_tag ではなく tag.div, tag.span などの「新しい tag API」を使っているコードが対象です。
  • 後方互換性の観点

    • 従来はブロック側が常に優先されていたため、「引数は無視されるもの」と期待していたコードでは出力が変わります。
      • 例: 「引数側にダミー値」や「古いAPIとの互換用値」を入れていた場合など。
    • 多くのケースでは、明示的に引数とブロックの両方を渡していれば「両方とも表示したい」意図であることが多いため、挙動としては自然になった一方で、HTML 出力の差分が生じる可能性はあります。
  • テンプレートレビューのポイント

    • tag.xxx("something") do ... end のような呼び方をしているビューテンプレートを grep 等でチェックし、
      • その両方のコンテンツが出力されることが想定どおりか
      • 余計な空白や改行が増えないか("Hello " の末尾スペースなど) を確認すると安全です。
  • パフォーマンス

    • 追加の連結処理は非常に軽量であり、実質的なパフォーマンスへの影響はほぼ無視できるレベルと考えられます。

  1. 参考情報 (あれば)
  • 対象ファイル:
    • actionview/lib/action_view/helpers/tag_helper.rb
    • actionview/test/template/tag_helper_test.rb
    • actionview/CHANGELOG.md
  • 変更規模:
    • 3ファイル変更
    • 追加 14 行 / 削除 1 行
  • 関連ヘルパ:
    • tag.div, tag.span, tag.p など tag オブジェクト経由のタグ生成系ヘルパ全般に適用されます。

#56294 Fix MemCacheStore for connection_pool >= 3

マージ日: 2025/12/5 | 作成者: @fatkodima

  1. 概要 (1-2文で)
    ActiveSupport::Cache::MemCacheStoreconnection_pool gem の v3 以降と正しく連携できるように、初期化ロジックを1行だけ修正したPRです。直前の PR #56292 での connection_pool 対応から mem_cache_store だけ漏れていた部分をフォローしています。

  1. 変更内容の詳細

※ 実際の差分は 1 行の置き換えのみです。PR タイトルと文脈からすると、以下のような意図の修正です(擬似コードで説明します):

ruby
# 変更前(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 に対応したものに揃える変更です。


  1. 影響範囲・注意点
  • 影響対象:
    • config.cache_store = :mem_cache_store などで MemCacheStore を使用しつつ、connection_pool gem v3 以上を利用しているアプリケーション。
  • 期待される改善:
    • connection_pool v3 系で MemCacheStore を使ったときに発生していた例外(例えば「uninitialized constant ConnectionPool::Wrapper」や、逆に Wrapper 必須なのに使っていないことによるエラーなど)が解消される。
    • RedisCacheStore など他のキャッシュストアと同様に、connection_pool v3+ と一貫した挙動をとる。
  • 互換性:
    • 変更は 1 行のみで、connection_pool の公式な v3 向け移行パスに沿った修正のため、通常の利用において後方互換性リスクは小さいと考えられます。
    • もしアプリ側で MemCacheStore の内部実装(@pool のクラスや API)に依存したメタプログラミングをしている場合は、@pool の型変更などにより影響する可能性があります(そのような利用は通常は非推奨です)。

  1. 参考情報 (あれば)

#56280 Use Digest::UUID.uuid_v5 to generate uuid instead of hand-rolled

マージ日: 2025/12/4 | 作成者: @zzak

  1. 概要 (1-2文で)
    Rails 内部で UUID を生成していた箇所を、自前実装ではなく Ruby 標準ライブラリ Digest::UUID.uuid_v5 を使うように置き換えた PR です。これにより UUID 生成ロジックが標準化され、保守性と一貫性が向上します。

  1. 変更内容の詳細
  • 対象ファイル:
    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 を作りたい用途に適しています。

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

    • Rails::DevtoolsController が利用している UUID 生成ロジックのみが影響を受けます。
    • 本番アプリケーションに直接影響するようなコア機能ではなく、開発支援用ツールに限定された変更です。
    • UUID の計算方法が「完全に同一」でない場合、同じ入力から得られる UUID が従来と変わる可能性があります。ただし devtools 用なので、互換性リスクは小さいと考えられます。
  • 注意点

    • Digest::UUID / Digest::UUID.uuid_v5 が利用できる Ruby バージョンが前提になります。
      Rails をこのコミット以降のバージョンで使う場合、対応する Ruby バージョン(Digest::UUID を含むもの)が必要になります。
    • もし外部ツールやスクリプトが「devtools が返す UUID の値そのもの」に依存していると、生成値の違いにより影響が出る可能性がありますが、一般的な Rails アプリではほぼ無視できるレベルです。

  1. 参考情報 (あれば)
  • 該当 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. 概要 (1-2文で)
    RedisCacheStoreconnection_pool gem のバージョン 3 以上で動かなくなる問題 (#56291) を修正した PR です。RedisCacheStoreconnection_pool のインターフェイスの差異を吸収するための、ごく小さな互換性修正が 1 行だけ入っています。

  1. 変更内容の詳細

※PR本文の実コード断片は提示されていないため、ここでは Rails と connection_pool 3 系の変更点から「どのような修正か」を技術的に推測・整理します。実際の 1 行差分は、この方向性のいずれかです。

背景: RedisCacheStoreconnection_pool の関係

ActiveSupport::Cache::RedisCacheStore は内部で connection_pool を使って Redis クライアントのプーリングを行います。典型的な利用イメージは以下のようなものです。

ruby
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 にアップデートしたタイミングで NoMethodErrorArgumentError が発生します。

この PR の実質的な修正内容

差分が「+1/-1 の 1 行のみ」であることから、以下のようなピンポイント修正の可能性が高いです。

一例:

ruby
# 変更前(connection_pool 2 系までは動くが、3 系で壊れる書き方)
@pool.with do |conn|
  # Redis 操作
end

# 変更後(3 系の挙動に合わせた呼び出し方)
@pool.with do |redis|
  # Redis 操作
end

または、キーワード引数・ブロック引数の扱いが変わったことに伴う修正:

ruby
# 例1: ブロックあり/なしのパターンに対応
@pool.with { |client| client.public_send(command, *args, **kwargs) }

# 例2: `then` のようなメソッドチェーンとの組み合わせを修正
@pool.with { |client| yield client } # と明示して新しいシグネチャに合わせる

いずれにせよ、

  • connection_pool 2 系でも動く
  • かつ 3 系でもエラーにならない

という互換的な呼び出し方に 1 行書き換えた修正と考えられます。


  1. 影響範囲・注意点

影響範囲

  • ActiveSupport::Cache::RedisCacheStore を利用しており、
  • かつアプリケーションが connection_pool 3 以上を採用している場合

に、キャッシュアクセス時の例外発生や挙動不良が解消されます。

Rails 自身が内部で利用する Redis ベースのキャッシュ(config.cache_store = :redis_cache_store など)にも関係するため、Redis キャッシュ利用アプリ全般に影響しうる変更です。

注意点

  • 既に connection_pool 2 系を使っていて問題なく動いているアプリに対しては、今回の修正による後方互換性ブレイクはほぼ考えにくく、安全なマイナー修正とみなせます。
  • もし connection_pool 3 系にアップグレードした際に
    • Redis のキャッシュ操作で NoMethodError, ArgumentError, または undefined method 'with' / wrong number of arguments などが出ていた場合は、この PR を含む Rails バージョンへの更新で解消される可能性が高いです。
  • Rails 本体よりも先に connection_pool だけを major upgrade していた場合、Rails をこの PR を含むバージョンまで引き上げることが推奨されます。

  1. 参考情報 (あれば)
  • 該当 Issue: #56291 — RedisCacheStoreconnection_pool 3 系で壊れる不具合報告
  • PR: #56292 “Fix RedisCacheStore for connection_pool >= 3”
  • 関連しうる外部リソース:
    • connection_pool GitHub リポジトリの 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. 概要 (1-2文で)
    Date.current の RDoc 内で、コード参照 Time.zone.today だけが <tt> でマークアップされていなかった問題を修正し、他のコード参照と統一したドキュメント整備のPRです。コードの挙動やAPI自体は一切変わっておらず、見た目と可読性の改善のみです。

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

対象ファイル:

  • activesupport/lib/active_support/core_ext/date/calculations.rb

Date.current のドキュメントコメント(RDoc)の中にある、以下のような説明文が元々ありました:

rdoc
# Returns Time.zone.today when config.time_zone is set, otherwise just returns
# Date.today.

この文中で、

  • Time.zone
  • config.time_zone
  • Date.today

<tt>...</tt> でコードとしてマークアップされていた一方で、Time.zone.today だけがプレーンテキストになっていました。

PRでは、この不整合を解消するために Time.zone.today<tt> で囲むように1行を修正しています:

diff
- # 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 が他と同様に等幅フォント・コードとして表示されるようになります。


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

    • Railsアプリケーションの挙動、API仕様、型や返り値などには一切変更なし。
    • 影響があるのは 生成されるドキュメント(RDoc) の表示のみ。
    • RDoc / edgeguides / 各種自動生成ドキュメントで、Date.current の説明におけるコード表記が統一されます。
  • 注意点

    • 既存コードの修正やテストの変更はなく、バージョンアップに伴う互換性問題はありません。
    • CIを回す必要のないドキュメントのみ変更のため、タイトルに [ci skip][docs] が付いています。

  1. 参考情報 (あれば)
  • 対象メソッド: 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. 概要 (1-2文で)
    このPRは、Rails本体のコメント中の英単語のスペルミスを修正しただけの変更です。コードの挙動や公開APIには一切影響しません。

  2. 変更内容の詳細

  • 修正内容

    • Action Controller のコメント:
      • ExplictlyExplicitly
    • Action Cable (PostgreSQL サブスクリプションアダプタ) のコメント:
      • purposedlypurposely
  • 対象ファイル

    • actionpack/lib/action_controller/metal/redirecting.rb
    • actioncable/lib/action_cable/subscription_adapter/postgresql.rb

どちらも「コメントのみ」の変更であり、実行パスに関わるコードは一切変更されていません。
codespell というスペルチェッカーツールで検出された誤字を修正した、ドキュメント改善系のPRです。

(疑似的なイメージ例)

ruby
# Before
# Explictly redirect to ...

# After
# Explicitly redirect to ...
ruby
# Before
# This is purposedly left ...

# After
# This is purposely left ...
  1. 影響範囲・注意点
  • 影響範囲

    • 実行時の挙動、パフォーマンス、セキュリティ、API 仕様に影響はありません。
    • 変更箇所はコメントのみのため、バイナリ・コンパイル成果物にも実質的な差分は出ません。
    • ドキュメントとしての読みやすさ・正確さがわずかに向上しています。
  • 注意点

    • スペル修正に伴うコメント内テキストの変更のみのため、既存コードやアプリケーション側での対応は不要です。
    • コメントを参照している外部ツール(ドキュメント生成等)を使っている場合も、意味は変わらず、破壊的な変更にはなりません。
  1. 参考情報 (あれば)
  • PR: https://github.com/rails/rails/pull/56289
  • 使用ツール: codespell(OSSプロジェクトでよく使われる英単語スペルチェックツール)
  • Rails の機能的な変更ではなく、品質改善(コメントの整備)カテゴリの変更として扱って問題ありません。

#56288 Add SecureRandom.base32

マージ日: 2025/12/4 | 作成者: @monorkin

  1. 概要 (1-2文で)
    Rails の SecureRandom に、人間が読みやすく誤読しづらい Crockford 方式の Base32 文字列を生成する SecureRandom.base32 が追加されました。これにより、2FA コードやマジックリンク用トークンなどを、安全かつユーザーフレンドリーな形式で簡単に生成できます。

  1. 変更内容の詳細

追加された API

ActiveSupport の SecureRandom 拡張に、新しく SecureRandom.base32 が追加されています。
既存の hex, base64, urlsafe_base64, base58 と同じパターンで実装されており、暗号学的に安全な乱数ソースを使いつつ、文字集合を Crockford Base32 にしたものです。

典型的な使い方は以下のようなイメージです(PR 本文から推測される使用感):

ruby
# 例: 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.rb
    • BASE32_ALPHABET を追加
    • 既存の Base 系メソッドと同じテンプレートで base32 を追加
  • activesupport/test/core_ext/secure_random_test.rb
    • 新しい base32 メソッド用のテストを追加(長さ・文字集合・ランダム性などを検証)
  • activesupport/CHANGELOG.md
    • ActiveSupport に SecureRandom.base32 を追加した旨を記載

これにより、アプリケーション側で毎回「安全な乱数 + 人間向け文字集合」の実装を自前で用意する必要がなくなります。


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

    • 新規メソッド追加のみで、既存 API の挙動変更や削除はありません。
    • ActiveSupport をロードしている Rails アプリ/ライブラリで SecureRandom.base32 が利用可能になります。
    • 2FA コード、招待コード、マジックリンク用トークンなど「人が読む・打つ前提のコード」を生成するユースケースで特に有用です。
  • 注意点

    • 「Base32」とだけ書かれているものには複数のバリアント(RFC 4648, Crockford など)があるため、この base32Crockford Base32 固定である点に注意してください。
      既存の RFC 4648 準拠の Base32 実装とは互換ではありません。
    • Rails 側では base58 が Bitcoin 形式を特に名前に含めずに採用している慣例にならって、base32 という汎用的な名前で Crockford バリアントを提供しています。
    • 既に独自の Base32 実装(特に Crockford Base32)を持っている場合は、徐々に SecureRandom.base32 に置き換えることで、暗号学的安全性と実装のシンプルさを統一できる可能性があります。
    • 「長さの指定方法」(引数の意味)は SecureRandom.base58 等と合わせているはずなので、置き換え時には base58 との対応関係を確認しておくと安全です。

  1. 参考情報 (あれば)

#56287 Fix ActiveRecord::SoleRecordExceeded#record to return the relation

マージ日: 2025/12/4 | 作成者: @byroot

  1. 概要 (1-2文で)
    ActiveRecord::SoleRecordExceeded 例外オブジェクトの #record メソッドが、本来返すべき「元の Relation」ではなく誤った値を返していたリグレッションを修正し、再び Relation を返すようにした PR です。sole / sole! 利用時に、例外から安全にクエリ条件を再利用できる挙動が復元されています。

  1. 変更内容の詳細

背景

  • ActiveRecord::Relation#sole / #sole! は「レコードがちょうど1件だけ存在する」ことを期待する finder です。
    • 0件: ActiveRecord::RecordNotFoundsole!) / nilsole
    • 1件: そのレコードを返す
    • 2件以上: ActiveRecord::SoleRecordExceeded を発生させる
  • 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 内部実装ですが、挙動の意味として):

ruby
# 修正後のイメージ
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.soleActiveRecord::SoleRecordExceeded を発生させる
  • rescue ブロック内で error = e; end
  • assert_equal relation, error.record

という形で、SoleRecordExceeded#record が元の Relation をそのまま返すことを明示的に保証するテストです。

これにより、今後の変更で同じリグレッションが再発しないように保護されています。


  1. 影響範囲・注意点

影響範囲

  • 対象: ActiveRecord::Relation#sole / #sole! を使っていて、「複数レコードヒット時に発生する ActiveRecord::SoleRecordExceeded 例外の #record を利用しているコード」。
    • 例外の record からクエリ条件を再利用するような実装:
      ruby
      begin
        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 等が顕在化する可能性があります(もともと仕様違反な使い方です)。

  1. 参考情報 (あれば)
  • 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」を参照すると、SoleRecordExceededrecord の意味付けが確認できます。

#56159 Restore missing Postgres type decoding

マージ日: 2025/12/4 | 作成者: @matthewd

  1. 概要 (1-2文で)
    Rails の PostgreSQL アダプタで、moneybytea 型の値が「公開されているクエリ実行 API (select_value / exec_query など)」経由でも正しくデコードされるように復元した PRです。既存アプリへの影響を最小化するための設定フラグと、PG::Connection.unescape_bytea の安全策(ダブルデコード防止)も併せて導入されています。

  1. 変更内容の詳細

背景

  • もともと内部用メソッド 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 らしい値」が返ってくるようになる:
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 のモンキーパッチ

この変更により問題となり得るパターン:

ruby
# これまで: 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 で追加されたものに依存します):

ruby
# 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.rb
    • money / bytea について、「デコード機能を有効にした際に期待通りの Ruby 値が返るか」を検証。
  • postgresql_adapter_test.rb
    • 公開 API(select_value, select_all, exec_query 等)での型デコードの有無、設定フラグによる挙動の違いを網羅的にテスト。
  • railties/test/application/configuration_test.rb
    • 設定フラグのデフォルト値、new_framework_defaults による切り替えを検証。

  1. 影響範囲・注意点

影響があるコードパターン

  1. AR モデルを経由しない生クエリ実行money / bytea を扱っている場合:

    • select_value / select_all / exec_query などで money / bytea カラム(またはそれらを返す式)を直接取得しているコードは、戻り値の型・内容が「より正しくデコードされた値」に変わる可能性があります。
    • 特に bytea は、文字列エスケープを前提にした独自処理を書いていると、二重デコードなどの不整合が起きかねないので注意が必要です。
  2. PG::Connection.unescape_bytea を明示的に呼んでいるコード:

    • 典型的なパターン: PG::Connection.unescape_bytea(ActiveRecord::Base.connection.select_value(...))
    • 新挙動有効化後も壊れないよう PR 側で防御策を入れていますが、
      将来的にはこのような「手動 unescape」は不要になるため、
      デコード済み値を前提にコードを整理していく方が安全です。
  3. 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 でフラグをオフにしておけば、
      これまでと同じ「ほぼデコードなし」の挙動を維持できる。
  • 新挙動を有効にする際の推奨手順:
    1. テスト環境でフラグをオンにして CI を回す。
    2. ログ検索・grep などで PG::Connection.unescape_bytea の利用箇所を洗い出す。
    3. select_value / exec_query を使って money / bytea を取得している箇所の戻り値を確認し、
      文字列前提のパース処理などがないかチェックする。
    4. 問題がなければ本番でもフラグをオンにする。

  1. 参考情報 (あれば)

#56267 Move async execution into QueryIntent

マージ日: 2025/12/4 | 作成者: @matthewd

  1. 概要 (1–2文で)
  • Active Record の非同期実行まわりの内部構造が変更され、実際のクエリ実行ロジックを FutureResult から QueryIntent に移し、コネクションプールの非同期キューには QueryIntent を積むようになりました。
  • これにより、FutureResult はあくまでユーザー向けの結果ハンドラに近い役割になり、非同期実行の責務がより明確に分離されています。

  1. 変更内容の詳細

全体像の変化

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 側に寄せられたと考えられます(擬似的なイメージ):

    ruby
    class 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 など) の表面上の振る舞いは基本的に従来と互換のはずですが、裏側の管理単位が FutureResultQueryIntent にシフトしています。

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 の振る舞い)は変わっていない」ことの裏付けとも言えます。

  1. 影響範囲・注意点

アプリケーション開発者への影響

  • 公開 API レベル(Relation#load_async, FutureResult#result など)は原則として互換であり、通常のアプリケーションコードは変更不要と思って問題ありません。
  • ただし、以下に該当する場合は注意が必要です:
    • ActiveRecord の非同期実行内部 (FutureResult の内部状態やコールバック) に依存したメタプログラミングをしている。
    • FutureResult を継承 / モンキーパッチして非標準な挙動を追加している。
    • コネクションプールの async キュー内部(キューに載るオブジェクトのクラスなど)を前提にした独自コードを書いている。
  • そのような場合、今後は QueryIntent が実行単位であることを前提に実装・パッチを書き換える必要があります。

ライブラリ・フレームワーク作者への影響

  • ActiveRecord の非同期クエリをラップしている gem / フレームワーク (GraphQL の load_async 連携など) で、FutureResult の内部のメソッドやインスタンス変数を直接触っている場合は互換性確認が必要です。
  • 将来的に非同期実行の拡張(キャンセル、タイムアウト、優先度制御など)を行う場合は、QueryIntent が拡張ポイントになる可能性が高く、FutureResult ではなく QueryIntent の API に注目すべきです。

パフォーマンス・安定性の観点

  • 実行の責務が QueryIntent に移ったことで、以下のようなメリットが狙われていると考えられます:
    • 責務分離によりコードが読みやすくなり、バグが減る。
    • 将来的な最適化(実行戦略の変更、バッチ化、より賢いスケジューリングなど)を QueryIntent に閉じた形で行いやすくなる。
  • この PR 自体は大規模な挙動変更を意図していないようですが、実装が大きく書き換わっているため、非同期クエリを多用するアプリではアップグレード後に実働環境での負荷テストや例外パターンの確認を推奨します。

  1. 参考情報 (あれば)
  • 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. 概要 (1-2文で)
    ActiveRecord::Relation#scoping の挙動、とくに all_queries: true を使ったブロックレベルスコープの動きやネスト時の挙動について、Rails Guides(Active Record クエリインターフェイスガイド)に明確な説明とサンプルを追加したドキュメント専用のPRです。コード本体の挙動変更はなく、ガイドの追記のみです。

  1. 変更内容の詳細

対象ファイル:

  • guides/source/active_record_querying.md に約 49 行程度のドキュメント追記

主な追加内容:

2-1. Relation#scoping の基本的な説明強化

  • Model.where(...).scoping { ... } 形式で「一時的に」デフォルトスコープのような条件を適用できる、という点をガイドで明示。
  • ブロック内で発行されるクエリに対して、その Relation が持つ条件や includes, joins, order などが反映されることを説明。

例(典型的なイメージ・ガイドに沿った内容のサンプル):

ruby
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 を指定すると、ブロック内で発行される「すべてのモデルのクエリ」に対してスコープが影響する、という挙動を説明。

イメージとなるサンプル:

ruby
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 は後勝ちなど)ため、その点を含めて例で示しています。

例(趣旨イメージ):

ruby
Post.where(published: true).scoping do
  Post.where(archived: false).scoping do
    Post.first
  end
end
  • 発行される SQL は WHERE published = TRUE AND archived = FALSE のように、ネストされたスコープが組み合わさる形になることを説明。
  • また、orderlimit などについても、内側のスコープで指定したものがどのように作用するかに簡単に触れていると考えられます。

2-4. 使いどころ・注意に関する簡単なガイダンス

ガイド内で、以下のような「使い方のヒント」も含めています:

  • ブロックレベルで一時的にスコープを切り替えたい場合に scoping を使うと便利であること。
  • default_scope の代わりに「明示的な」スコープを適用する用途としても使えること。
  • ただし all_queries: true は影響範囲が広く、意図しないクエリに条件が掛かる可能性があるため注意すべき、という旨の注意喚起。

  1. 影響範囲・注意点
  • これは ドキュメントのみ の変更であり、Relation#scoping の実装には一切変更がありません。
  • 既存コードの挙動・パフォーマンスには影響しません。
  • 影響があるのは「開発者がこのガイドを読んだときの理解・使い方」であり、特に:
    • これまで scoping / all_queries: true の挙動が曖昧だった部分が明文化される。
    • all_queries: true の利用を検討する際に、影響範囲やネスト時の挙動を事前に把握しやすくなる。
  • 強力なため、テストコードや一部のバッチ処理などで all_queries: true を多用する場合、ガイドのサンプルを参考にしながら「どこまで影響が及ぶか」を確認すると安全です。

  1. 参考情報 (あれば)

#56275 Use Bundler.with_unbundled_env for GeneratorsTestHelper run_app_update

マージ日: 2025/12/3 | 作成者: @zzak

  1. 概要 (1-2文で)
    Generators のテストヘルパーで run_app_update を実行する際に、Bundler の影響を受けないように Bundler.with_unbundled_env を使うように変更した PR です。これにより、bundle exec 配下でテストを実行しても、アプリ更新用コマンドが意図しない Gem 環境に縛られずに動くようになります。

  1. 変更内容の詳細

対象ファイル:

  • railties/test/generators/generators_test_helper.rb

GeneratorsTestHelper 内の run_app_update メソッドで、Bundler.with_unbundled_env を使用するようになりました。概ね以下のような変更です(イメージ・擬似コード):

ruby
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_GEMFILEBUNDLE_BIN_PATH など Bundler が設定する環境変数がクリアされ、いわゆる「素の」Ruby/Gem 環境でコマンドが実行されます。
  • Generators のテストは、テンプレートアプリケーションを生成・更新するようなコマンド(bin/rails app:update 相当)を内部的に実行しますが、それらが「テストフレームワーク側の Bundler 環境」に引きずられないようにしています。

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

    • Rails 本体のランタイム挙動ではなく、「Generators 関連のテストコード」に限定された変更です。
    • 主に Rails 開発者・コントリビュータ、および Rails を fork して独自にテストを回している人が対象になります。
    • CI などで bundle exec ruby -Itest ... のように Bundler 経由でテストを走らせている場合に、Generators のテストがより安定・再現性の高い形で動作するようになります。
  • 解決している問題の方向性

    • Issue #56271 で報告された、run_app_update 実行時に Bundler による環境変数や Gem ロードパスが干渉して失敗・不整合が起きる問題を緩和/解消する意図があります。
    • 典型的には、テスト実行側の Gemfile に縛られて、本来想定していない Gem バージョンで app:update が走ってしまう、といった症状を防ぎます。
  • 注意点

    • Bundler.with_unbundled_env を使うと、ブロック内では「現在の Bundler コンテキスト外」の世界になるため、その中で別の bundle exec を前提とするような処理を追加すると挙動が変わる可能性があります。
    • ただし、このメソッドはテスト専用ヘルパー内で完結しているため、一般的な Rails アプリケーション開発者がこの PR を意識する必要はほぼありません。

  1. 参考情報 (あれば)

#56272 Skip all system test files on app generation

マージ日: 2025/12/2 | 作成者: @eileencodes

  1. 概要 (1-2文で)
    Rails アプリ生成時に常に作られていた application_system_test_case.rb を、システムテストを生成したときに限って作成するように変更した PR です。scaffold でのシステムテストスキップ対応 (#55743) の取りこぼしを補完し、不要なシステムテスト関連ファイルが生成されないようにしています。

  1. 変更内容の詳細

背景

  • 以前の 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.rb

  • railties/lib/rails/generators/test_unit/scaffold/templates/application_system_test_case.rb.tt

  • scaffold 用の 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 にも検証が追加されています。
  • 既存の app generator テストから、もはや生成されない application_system_test_case.rb を前提としたアサーションが削除されています (-9 行)。


  1. 影響範囲・注意点
  • 新規プロジェクト (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? など)
        • なければ生成
          を行うようにする必要があります。

  1. 参考情報 (あれば)

#56269 Add a top level bin/test

マージ日: 2025/12/2 | 作成者: @byroot

  1. 概要 (1-2文で)
    Rails リポジトリのルートに bin/test が追加され、サブディレクトリへ cd しなくても各サブプロジェクト(activemodel, activerecord など)のテストを直接実行できるようになりました。あわせて、失敗テストの「再実行コマンド」もリポジトリルートからそのままコピペで使える形に改善されています。

  1. 変更内容の詳細

2-1. bin/test の新規追加

リポジトリ直下に bin/test が追加され、Rails コアの各コンポーネントのテストを簡単に実行できるようになりました。

使い方の例(PR本文のまま):

bash
# コンポーネント単位のテスト
$ 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 activemodelcd activerecord せずにテストを叩ける。
  • 単一サブプロジェクト内でのテスト実行を想定
    一回のコマンドで「activemodel と activerecord を同時に」など、複数サブプロジェクトを混在させた実行はサポートしていないと明記されています。
  • おそらく内部的には既存の tools/test.rb / tools/test_common.rb を呼び出す薄いラッパーになっており、引数に応じて対象コンポーネントを解決している形です。

bin/test の追加によって、これまで

bash
cd activemodel && bin/test test/cases/dirty_test.rb

のようにしていた作業が

bash
bin/test activemodel/test/cases/dirty_test.rb

だけで済むようになります。

2-2. テストレポーター (reporter.rb) の修正

railties/lib/rails/test_unit/reporter.rb が変更され、Rails 自身のテストを実行したときに表示される「失敗テストの再実行コマンド」が、ルートディレクトリからそのまま使える形に変わりました。

従来は、CI やローカルで失敗したときに、次のようなコマンドが表示されていました:

bash
bin/rails test /rails/activerecord/test/cases/adapters/postgresql/connection_test.rb:187
  • 絶対パス (/rails/...) になっていて、そのままコピペしても意図通りに動きにくい。
  • 実行パスも bin/rails で、コンポーネント単体テストを回す文脈とやや合わない。

これが PR によって、以下のようになります:

bash
bin/test activerecord/test/cases/adapters/postgresql/connection_test.rb:187
  • リポジトリルートからの相対パスになり、そのままコピペで実行可能。
  • コマンドが bin/test に統一され、今回追加されたワークフローと整合します。

2-3. tools/test.rb / tools/test_common.rb の微修正

tools/test.rbtools/test_common.rb にはそれぞれ 1〜2 行程度の軽微な修正が入っています。

推測される内容:

  • bin/test からの呼び出しをサポートするために、エントリーポイントの扱い引数のパース方法を少しだけ調整。
  • コンポーネント名(activemodel, activerecord など)とパスの解決ロジックを bin/test と共有するための小さな共通化。

このあたりは内部実装の整合性調整であり、外部 API/インターフェースとしては主に bin/test の追加とレポーターの出力変更が本質的な変更です。


  1. 影響範囲・注意点
  • 対象は Rails リポジトリの開発者・コントリビューター
    Rails を利用するアプリ開発者向けではなく、「Rails 本体のテストを書く・直す人」の開発体験改善が主目的です。
  • 既存のテスト実行方法は引き続き使用可能
    cd activerecord && bundle exec ruby -Itest test/cases/... のような既存手法は変わらず動作し、bin/test は追加のエイリアス・ラッパーの位置づけです。
  • 複数サブプロジェクトへの同時テスト実行はサポート外
    一回の bin/test 呼び出しで activemodelactiverecord のテストを同時に指定するような使い方は想定されていません。
    → そのようなケースでは、これまで通り各ディレクトリで個別に実行するか、独自スクリプトを使う必要があります。
  • CI ログやローカル実行時の「再実行コマンド」が変わる
    今後は bin/test ... 形式のコマンドが出力されるため、ドキュメント・社内 Wiki 等で「失敗テストの再実行方法」を案内している場合は、必要に応じて記述を更新するとよいです。
  • パス前提の変化
    再実行コマンドは「リポジトリルートからの相対パス」を前提にしています。そのため、コマンドをコピペするときは「Rails リポジトリのルートで実行しているか」を意識する必要があります。

  1. 参考情報 (あれば)
  • 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. 概要 (1-2文で)
    Api アプリケーションジェネレータのテスト (ApiAppGeneratorTest) で、bundle install(正確には kamal 実行時の Bundler 解決)に依存していた部分をスタブ化し、テストが外部の gem 解決に依存しないようにした PR です。これによりテスト時間が約 3 秒 → 約 1 秒に短縮され、ログノイズも解消されています。

  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 の実行部分をテストダブルに差し替え

    • 例(イメージ):

      ruby
      def stub_kamal
        File.write("bin/kamal", <<~RUBY)
          #!/usr/bin/env ruby
          # no-op for tests
        RUBY
        FileUtils.chmod("+x", "bin/kamal")
      end

      もしくは、システムコール層のスタブ:

      ruby
      Kamal::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 件(スタブが正しく呼ばれたこと/生成物の検証が増えた)

  1. 影響範囲・注意点
  • 影響範囲
    • Rails 本体のプロダクションコードには影響せず、「railties のテストコードのみ」が対象です。
    • 特に ApiAppGeneratorkamal 用のファイル/設定をどう生成するかをテストする部分のみが変更されています。
  • 良くなった点
    • テストが外部の gem レジストリ状態(rails 8.2.0.alpha がインストールされているかどうか)に依存しなくなる
    • CI / ローカル環境共にテストが安定し、ノイズログがなくなる
    • テスト実行時間の短縮
  • 注意点
    • スタブにより「実際に kamal コマンドを走らせたときの挙動」はこのユニットテストではカバーしない形になっています。
      • これは他のジェネレータテストと同じ方針で、「ジェネレータの責務(ファイル生成やコマンド呼び出しのセットアップ)だけ」をテストし、外部ツールの実際の挙動はそのツール自身のテストに任せる、という設計です。
    • kamal 側のインターフェースが変わった場合は、このテストだけでは気付きにくくなるので、
      • 統合テストや手動検証など、別レイヤーのテストで補完する必要があります。

  1. 参考情報 (あれば)
  • 類似パターンがすでに存在するテスト:
    • PluginGeneratorTest
    • ActionTest::Generators::InstallGeneratorTest
  • テストがスタブしている対象は、
    • bundle install や外部 CLI(kamal)の実行部分
    • Rails のジェネレータテストでは「外部コマンド呼び出しを no-op 化して、生成されたファイルのみ検証する」というパターンが広く使われています。

#56256 Include HTTP_FORWARDED header in IpSpoofAttackError message if available

マージ日: 2025/12/1 | 作成者: @zzak

  1. 概要 (1-2文で)
    Rails の IpSpoofAttackError が発生した際のエラーメッセージに、従来の X-Forwarded-For に加えて Forwarded (HTTP_FORWARDED) ヘッダの値も含められるようになりました。これにより、IP なりすまし検出時のデバッグ情報がより充実します。

  1. 変更内容の詳細

どの部分が変わったか

変更ファイル:

  • actionpack/lib/action_dispatch/middleware/remote_ip.rb
  • actionpack/test/dispatch/request_test.rb

ActionDispatch::RemoteIp ミドルウェア内で IpSpoofAttackError を生成するときのメッセージに、利用可能であれば HTTP_FORWARDED ヘッダを含めるように修正されています。

従来はおおむね次のようなメッセージでした(例):

txt
IP spoofing attack?! HTTP_CLIENT_IP="1.2.3.4" HTTP_X_FORWARDED_FOR="5.6.7.8"

この PR により、Forwarded ヘッダがある場合は:

txt
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 内容からの推定を含むサンプルです(実際の実装のニュアンスとして):

ruby
# 例: エラー生成部分のイメージ
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 の値が含まれていることを検証するテストが追加されています。


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

    • ActionDispatch::RemoteIp を使用していて、かつ IP なりすまし (spoofing) 判定が走った場合の「例外メッセージ」が変わります。
    • ランタイムの挙動(例外が発生するかどうか、どの条件で発生するか)自体は変わっていません。あくまでエラーメッセージの情報量追加です。
  • ログ解析・監視との互換性

    • IpSpoofAttackError のメッセージ文字列をパースして独自に解析している場合、HTTP_FORWARDED の追加でフォーマットが変わる可能性があります。
    • 「メッセージ全体の完全一致 (exact match)」でアラート設定やテストを書いている場合は、これを機に「部分一致」や「正規表現ベース」に変更した方が安全です。
  • Forwarded ヘッダを利用している環境

    • RFC 7239 準拠の Forwarded ヘッダを積極的に使っているリバースプロキシ構成(例: 一部の CDN / LB)では、IP spoofing 検出時のトラブルシューティングがしやすくなります。
    • どのヘッダでどの IP が渡されているかが 1 つの例外メッセージにまとまるため、プロキシの設定ミスや想定外のヘッダ上書きの発見に役立ちます。

  1. 参考情報 (あれば)
  • 関連 Issue: #56186
    • この PR は、IpSpoofAttackErrorForwarded ヘッダ情報が含まれないためデバッグが難しい、という報告を解消するものです。
  • 対象コンポーネント:
    • 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. 概要 (1-2文で)
    ActiveJob.perform_all_later が、ジョブクラスに設定された enqueue_after_transaction_commit を正しく尊重するように修正された PR です。これにより、トランザクション完了後にキュー投入すべきジョブが、perform_all_later 経由でも期待通りに動作します。

  1. 変更内容の詳細

背景

  • 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 と同じようなラッピングロジックを、配列形式の複数ジョブに対して適用する形になっています。

参考となる利用イメージ(疑似コード):

ruby
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 される
end

2) テストの追加

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 = false or 未設定のジョブは、perform_all_later でも即時 enqueue される

これにより perform_laterperform_all_later 間での挙動の一貫性がテストで担保されています。


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

    • 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 がこれに揃えられます。

  1. 参考情報 (あれば)
  • 修正元 PR(バグ報告 / 初期修正案): https://github.com/rails/rails/pull/56246
    • この PR はオリジナル作者がメンテナによる force push を許可していなかったため、本 PR (#56264) としてやり直されています。
  • 機能そのものの背景:
    • Rails ガイド: Active Job のドキュメント(enqueue_after_transaction_commit 周辺)
    • ActiveRecord のトランザクションコールバック (after_commit) と連携する仕組みで、DB 一貫性の担保に有用です。

#56258 Add schematized json for has_json

マージ日: 2025/11/30 | 作成者: @dhh

  1. 概要 (1–2文で)
    Rails の JSON 属性に対して「型付き・スキーマ付き」でアクセスできる schematized_json 機能が ActiveModel に追加されました。has_json / has_delegated_json を使うことで、UI から文字列で値を渡しても、DB には boolean / integer / string の正しい JSON 型で保存されるようになります。

  1. 変更内容の詳細

2-1. 機能の概要

  • 目的

    • JSON カラムを「なんでも入る Hash」ではなく、事前に決めたキーと型を持つ半構造化データとして安全に扱えるようにする。
    • フォームや API からはすべて文字列として送られてきても、モデル側で boolean / integer などに 自動キャスト したい。
  • サポートされる型

    • boolean
    • integer
    • string
    • ネストは非対応(フラットなキーのみ)

2-2. 代表的な API と使い方

PR 説明の例をベースに整理します。

ruby
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 型
end

has_json

  • 対象: モデルの JSON カラム(例: settings

  • 使い方:

    • キー名 => デフォルト値 で渡すと、デフォルト値の型から JSON 型が決まる
      • true / false → boolean
      • 10 → integer
      • "Hello" → string
    • デフォルト値を持たないキーは symbol で型指定できる(例: staff: :boolean
  • 実行時の挙動:

    • Account.new 時に、定義されたキーに デフォルト値がセット される。
    • before_save でも再度デフォルトセットが行われる(nil のままの場合などの補完)。
ruby
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 が返す「アクセサオブジェクト」を介さず、モデル直下にメソッドを生やすパターン。
ruby
a = Account.new
a.beta    # => false (flags["beta"] の boolean 値)
a.staff   # => nil   (デフォルトなし、型は boolean)
a.staff = true
a.staff?  # => true

2-3. 文字列入力からの自動型変換

この PR のキモは、「UI からは全部文字列でも、JSON としては型付きで扱える」点です。

ruby
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, 42to_i ベース)
  • string
    • そのまま保存(トリム・バリデーション等はこの層では行わない想定)

2-4. 実装位置

  • 追加ファイル:
    • activemodel/lib/active_model/schematized_json.rb
      • スキーマと JSON を結び付ける中核クラス/モジュールが定義されている。
    • activemodel/lib/active_model.rb, activemodel/lib/active_model/api.rb
      • schematized_json 機能を Active Model API から利用できるように require / include。
  • テスト:
    • activemodel/test/cases/schematized_json_test.rb
      • 型付け、デフォルト適用、キャスト、has_delegated_json の挙動などがカバーされている。

  1. 影響範囲・注意点
  • 対象バージョン

    • PR #56258 がマージされる Rails の次リリース以降で利用可能(少なくとも edge / main ブランチ)。
  • JSON カラムの前提

    • DB 側は json / jsonb(PostgreSQL)など、ネイティブ JSON 型または text + serialize でも動くが、Rails 的には JSON 属性として定義されている前提。
  • 型の制約

    • サポートは boolean / integer / string のみ。
    • 配列・オブジェクト・ネストした JSON 構造はサポート外。
      • これらを使いたい場合は、従来通り store, store_accessor, serialize, JSON カスタム型などを使う必要あり。
  • 既存データとの整合性

    • すでに JSON カラムに異なる型が入っている場合、
      • 読み出し時に想定と違う型が来る可能性がある。
      • その場合の挙動(強制キャストかエラーか)は実装依存なので、マイグレーションやデータクレンジングが望ましい。
  • before_save でのデフォルト適用

    • before_save でもデフォルト値が適用されるため、
      • 「ユーザーが nil を明示的に入れたが、保存時にデフォルトで上書きされる」ケースをどう扱うかに注意。
      • 「nil を許容したい」場合は、この schematized_json に乗せるべきか、仕様を検討した方がよい。
  • フォーム/フロントエンド側への利点

    • すべて文字列で送ってよい、という前提が作れるので、JS 側の型管理がシンプルになる。
    • ただし、boolean の "true" / "false" などは UI からのバリエーションを考慮したうえで、変換ロジックに合わせた値を送る必要がある。

  1. 参考情報 (あれば)
  • 追加クラス: 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 / テストコードを参照するとより正確に把握できます)