`before_subscribe` コールバックで `#reject` を呼んだ際に `#subscribed` をスキップするよう修正

rails/rails

Action Cable の before_subscribe コールバック内で #reject を呼び出しても #subscribed が実行されてしまうバグが修正されました。これにより、before_action でレンダリングした場合にアクションがスキップされるコントローラの挙動と同様の一貫した動作が実現されます。

背景

before_subscribe コールバックで #reject を呼び出しても、後続の #subscribed メソッドが実行されるという不整合な挙動が報告されていました(#52229)。

以下のように before_subscribe でサブスクリプションを拒否するチャンネルを定義した場合、#subscribed は呼び出されないことが期待されます:

class ChatChannel < ApplicationCable::Channel
  before_subscribe do
    reject
  end

  def subscribed
    Rails.logger.info("subscribed!") # 本来は呼ばれてはいけない
  end
end

しかし実際には subscribed! がログに出力されていました。通常のコントローラで before_action がレンダリングやリダイレクトを行った際にアクションメソッドがスキップされるのとは対照的な挙動であり、設計上の一貫性を欠いていました。

技術的な変更

変更の核心は ActionCable::Channel::Base#subscribe_to_channel の1行の修正です。#subscribed の呼び出しに unless subscription_rejected? の条件を追加することで、before_subscribe コールバック実行後にサブスクリプションが拒否済みであれば #subscribed をスキップします。

変更前:

def subscribe_to_channel
  run_callbacks :subscribe do
    subscribed
  end

  reject_subscription if subscription_rejected?
  ensure_confirmation_sent
end

変更後:

def subscribe_to_channel
  run_callbacks :subscribe do
    subscribed unless subscription_rejected?
  end

  reject_subscription if subscription_rejected?

  ensure_confirmation_sent
end

run_callbacks :subscribe ブロック内で before コールバック(before_subscribe)が先に実行されるため、その時点で subscription_rejected? を評価することで、#reject が呼ばれていれば #subscribed の実行を防止できます。reject_subscription および ensure_confirmation_sent の呼び出し順序は変わらないため、拒否通知のクライアントへの送信フローには影響しません。

合わせて actioncable/lib/action_cable/channel/callbacks.rbbefore_subscribe メソッドのドキュメントも更新されました。#reject を呼び出した場合に #subscribed がスキップされる旨が明記されています。

# This callback will be triggered before the Base#subscribed method is called.
#
# However, if the subscription is rejected with the Base#reject method in any
# such callback, the Base#subscribed method will not be called.
#
def before_subscribe(*methods, &block)
  set_callback(:subscribe, :before, *methods, &block)
end

設計判断

subscribed の条件付きスキップ という最小限の変更が採用されています。

run_callbacks:subscribe フックは before → 本体 → after の順に実行されます。before_subscribe コールバックが #reject を呼び出すと subscription_rejected?true になるため、本体(subscribed)の実行をその場でガードするだけで目的が達成できます。コールバックチェーン自体を中断するのではなく、本体をスキップする判断が取られており、after_subscribe など他のコールバックへの影響を最小化しています。

テストでは RejectBeforeSubscribeChannel を定義し、subscribe_to_channel 呼び出し後に channel.roomnil であること、および subscribed?false であることを検証しています。また、テストスタブの test_socket.rbActionCable::Connection::Subscriptions の初期化が追加され、テストインフラも整備されています。

まとめ

今回の修正は subscribed unless subscription_rejected? という1行の追加に留まりながら、Action Cable のコールバックと通常のコントローラの before_action における振る舞いの一貫性を回復させています。before_subscribe を認証・認可のゲートとして利用するパターンが、設計上の期待通りに機能するようになりました。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
e3aa0083

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

「総論→各論→結論」の構成が明確で、リード文、背景、技術的な変更、設計判断、まとめの各要素がすべて含まれており、模範的な構成です。

カスタムMarkdown構文 ✓ PASS

シンタックスハイライト・GitHubリンク記法の正確性

ファイル名付きシンタックスハイライトやGitHubのPR/Issueへのリンク記法が正しく使用されています。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

Action Cableの内部実装に関する内容であり、専門知識を持つエンジニアという対象読者に適した技術レベルと表現です。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクションが総論・各論・結論で構成され、各段落はトピックセンテンスで始まり、1段落1トピックの原則が守られており、可読性が非常に高いです。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

記事内で引用されているコードブロックは、提供されたDiff情報と完全に一致しており、ファイル名も正確です。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

Action Cableのコールバックに関する技術用語(`before_subscribe`, `subscribed`, `#reject`など)が正確かつ適切に使用されています。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

変更内容の説明は技術的に正確であり、`run_callbacks`の挙動や変更が他に影響を与えない理由についても論理的に解説されています。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

記事内のすべての主張(バグの存在、テストコードの追加など)は、PRのDescriptionやDiff内のコードによって裏付けられており、ハルシネーションは検出されませんでした。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR番号(#53101)やIssue番号(#52229)などの数値や固有名詞はすべて正確です。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事のタイトルはPRの主題を正確に要約しており、内容との一貫性が保たれています。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

PR情報に記載のないバージョン情報やリリース予定などの外部知識の追加はなく、すべての情報がPRに基づいています。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

「〜が修正されました」といった過去形の表現が使われており、PRの変更内容を正確な時間軸で記述しています。