NoticedとCableReadyでRailsアプリに堅牢なシステム通知を実装する方法
本記事は、著者の著書『Advanced CableReady』の一章に着想を得た内容を、AppSignalのゲスト投稿向けに調整したものです。
通知機能は、多くのWebアプリケーションに共通する横断的な関心事の一つです。
Noticed gemは、データベースバックエンドのモデルとプラグイン可能な配信手段を提供することで、Ruby on Railsアプリケーションにおける通知開発を驚くほど簡単にしてくれます。メーラーやWebSocketなど、主要な配信手段が標準で組み込まれています。
本記事では、CableReady gemを活用してRailsアプリケーションでシステム通知を発火させるメリットについても詳しく見ていきます。
それでは始めましょう!
前提条件と必要な環境
アプリケーションからシステム通知を発火させたい場面は、ときどき訪れます。これはNotifications APIを使えば実現できます。
たとえば、ユーザーが大容量ファイルをアップロードでき、そのトランスコードに長時間かかるアプリを考えてみましょう。動画のアップロードが完了した時点でユーザーへ通知を送れれば、ユーザーは待ち時間に別の作業へ移れますし、何分もアプリを開きっぱなしにしておく必要もありません。
幸い、次の2つの前提条件が揃っていれば、システム通知の実装と拡張は簡単に行えます。
- Noticedはカスタム配信手段をサポートしています(任意のトランスポート機構を実装できるシンプルなAPIを公開しており、たとえばDiscordサーバーへの投稿なども可能です)。
- CableReadyには、通知操作(notification operation)があらかじめ用意されています。
CableReadyとNoticedに必要な要件はごくわずかです。
- 稼働中のActionCableサーバー
- ActiveJobバックエンド(Noticedが通常利用します)
※ 本プロジェクトのサンプルコード一式はGitHubで公開しています。以下では手順を一つずつ丁寧に解説するので、そのまま一緒に進めていただいても構いません。
RailsアプリのためのCableReady入門
まずはCableReadyに慣れるところから始めましょう。2017年にリリースされたCableReadyは、「欠けていたActionCable標準ライブラリ」と表現できます。
Turboが登場する以前、Ruby on Railsでリアルタイムアプリケーションを構築する唯一の方法はActionCableでした。ActionCableはWebSocketのネイティブなRailsラッパーであり、永続的な接続を介して双方向にいつでもメッセージを送受信できる、サーバーサイド・クライアントサイド双方のAPIを提供します。
しかしこのアプローチの弱点は、実現までに大量のボイラープレートコードを書かなければならない点でした(現在も同様です)。
そこで活躍するのがCableReadyです。サーバー側から発火できる多種多様なDOM操作を抽象化するレイヤーを提供してくれます。主な例を挙げると:
- DOMの変更(
inner_html、insert_adjacent_html、morphなど) - DOM要素プロパティの変更(
add_css_class、remove_css_class、set_dataset_propertyなど) - 任意のDOMイベントのディスパッチ
- ブラウザ履歴の操作
- 通知(本記事で実際に使用します)
CableReadyのサーバーサイドとクライアントサイド
CableReadyはどのようにして魔法のような動作を実現しているのでしょうか? 簡潔に言えば、サーバーサイドとクライアントサイドの2つのパーツで構成されています。
サーバーサイドでは、CableReady::Broadcasterモジュールを、必要なアプリケーションの任意の場所にincludeできます。ジョブ、モデルのコールバック、あるいは普通のコントローラなどが該当します。ただし、その前にActionCableのチャネルを用意しておく必要があります。CableReady公式ドキュメントからの引用です:
ここでvisitorsはストリーム識別子(stream identifier)と呼ばれます。これを使うことで、幅広い対象にも、チャネルを購読している特定のクライアントだけにも絞って配信できます。例を締めくくるために、モデルにBroadcasterモジュールをincludeし、サインアップ後にクライアントへconsole.logを送信してみましょう:
クライアントサイドのロジックはシンプルです。まず前述のチャネルへのサブスクリプションを作成し、そのreceivedフック内で、通信経由で渡されたすべての操作に対してCableReady.performを呼び出します:
RailsにおけるCableReady vs Turbo
整理すると、どんなときにCableReadyを使い、どんなときに避けるべきでしょうか?
Turboの登場により、Web開発コミュニティはサーバーレンダリング型のリアクティブアプリケーションを構築するための強力なツールボックスを手に入れました。Turboは本質的にフロントエンド技術ですが、Rails向けの強力なサーバーサイドバインディングを備えており、標準的なMVC(Model-View-Controller)スタックに自然に溶け込みます。そのため、典型的なアプリの要件のほとんどをカバーできます。
一方、CableReadyはリアルタイムRails開発における「スイスアーミーナイフ」であり、慎重に使うべきツールです。あまりに強力な抽象化であるため、あらゆる場面で使い倒したくなる誘惑があります。しかし、DOMのあらゆる部分がアプリのどこからでも変更できてしまう状態を想像すれば、それが競合状態(race condition)や追跡困難なバグにつながりかねないことが分かるでしょう。
とはいえ、今回のようなケースでは、DOMをより細かく制御できるため、CableReadyを使うのは理にかなっています。
ひとことでTLDRを求められるなら、こう答えます。Turboはアプリケーション開発者のためのもの、CableReadyはライブラリ作者のためのものだと。ただしこれから見ていくように、両者の間にはグレーゾーンも存在します。
Noticed ― Railsアプリのためのシンプルな通知基盤
システム通知の配信に使う2つ目のライブラリは、Chris Oliver氏によるNoticed gemです。その核心は、「受信者に対する1件の通知」をモデル化したActiveRecordモデルの上に成り立っています。このモデルは次のような共通メタデータを保持します。
- 通知が誰に送られたか(受信者)
- 通知がいつ既読になったか
- 通知に関連付けられたパラメータ(通常は別モデルへの参照)
ActiveStorageやActionTextのメタテーブルの仕組みをご存知なら、非常に似た設計だと感じられるでしょう。
これに加えて、NoticedはPORO(Plain Old Ruby Objects、つまりRailsなどのフレームワークに依存しない素朴なRubyオブジェクト)を採用しており、これらが実際の通知の設計図として機能します。やや紛らわしいことに、これらも「Notification」と呼ばれており、通知のレンダリング方法や配信方法に関するロジックを担います。READMEからの例がこちらです:
この動作はすぐ後で確認します。特に注目したいのはdeliver_byの呼び出しです。ここで、その通知がどの配信手段を使うかが決まります。
deliver_by :database― Notificationレコード(前述のモデル)を保存し、後から参照できるようにします。deliver_by :action_cable― 定義済みのActionCableチャネルとストリーム(デフォルトはNoticed::NotificationChannel)経由で送信します。deliver_by :email― 通知送信に使うメーラーを指定します。例では、受信者が設定した通知設定(preferences)を考慮に入れる方法も示されています。
本記事の残りのゴールは、システム通知を送信するカスタム配信手段を実装することです。
カスタム配信手段の実装:Notifications APIによるシステム通知
実装に取り掛かる前に、まず新しいRailsアプリケーションを作成して土台を整えましょう。CableReadyとの統合が容易になるため、importmapsではなくesbuildオプションを選択しました:
※ 執筆時点でのRailsの最新バージョンは7.0.4です。
既存のRailsアプリケーションをお持ちの方は、次のステップをスキップしても構いません。ただし、通知の受信者となるUserモデル(または同等の仕組み)が存在することを確認してください。
1. 受信者の準備
Noticedは受信者として機能するUserモデルを必要とします。ここでは簡潔に進めるため、Deviseを導入してUserモデルを生成します。
その後、Railsコンソールを開いてサンプルユーザーを作成しましょう:
2. Noticedの導入
次に、NoticedをGemfileに追加し、データベースモデルを生成します。
指示に従ってdb:migrateを実行し、Userモデルにポリモーフィックなhas_many関連を追加します:
動作確認までの最後のピースは、設計図となるPOROの作成です:
ここでは少し手を加えて、当面はデータベース配信のみを使い、メッセージはプレースホルダーとします。本来であれば、メッセージ本文やリンク先URLを組み立てるために、モデルIDなどの必須パラメータをここに定義します。詳細はNoticedのREADMEを参照してください。
Railsコンソールだけで、このPOROに基づいて通知を配信する流れを確認できます:
ご覧のとおり、NoticedはデータベースへのINSERTを実行するため、特定ユーザーの全Notificationsを取得できるようになりました:
これで、現在のユーザー宛てに配信されたすべての通知を一覧表示するシンプルなindexビューを構築できます:
bin/devでアプリを起動し、ログインして https://localhost:3000/notifications にアクセスしてみましょう:

3. CableReadyのインストール
CableReadyを利用するには、まずインストールが必要です。幸い、あっという間に完了します:
続いて、メッセージ配信用のNotificationChannelを生成しましょう:
これにより、不足していたActionCable(JavaScript)関連の依存関係がすべて追加され、対応するチャネルファイル――具体的にはapp/channels/notification_channel.rbとapp/javascript/channels/notification_channel.js――がスキャフォールドされます。
サーバーサイドのチャネルは、Noticed::NotificationChannelを継承します:
先へ進む前に、Deviseのユーザーに対してActionCableの認証が機能するよう設定しておく必要があります。詳細は割愛しますが、必要なボイラープレートは次のとおりです:
その他の選択肢については、StimulusReflexのドキュメントを参照してください。
クライアントサイドでは、前述の小さなセットアップコードを追加します:
※ ActionCableの動作にはRedisが必要です。以降の説明では、ローカルでRedisサーバーが稼働していることを前提とします。
4. 配信手段の実装
システム通知をブロードキャストするために、新しい配信手段を生成しましょう:
生成されたクラスは次のようになっています:
続いて、deliverメソッドの設計を進めていきます。
channelメソッドは、組み込みのActionCable配信手段から一部借用したものです。クラスメソッドのオプション経由でチャネルを渡せるようになっており、指定がない場合はNoticed::NotificationsChannelへフォールバックします。
そして、CableReadyのnotificationメソッドを使って、該当する通知インスタンスを受信者へブロードキャストします。
これを実際に動かすには、通知のPOROに接続する必要があります:
5. 実際に動かしてみる
あとは試すだけです。再びRailsコンソールから同じコマンドを実行してみましょう:
ログインしたままの状態であれば、まずブラウザがアプリからの通知受信の許可を求めてきます:

許可を確認すると、ブラウザからこの美しいポップアップ通知が届きます:

まとめ
CableReadyとNoticedを駆け足で巡りながら、ネイティブブラウザAPIをアプリに統合する方法を実演しました。その結果として得られるのは、ユーザーへシステム通知を届けるためのシンプルで一貫性のある手法です。
このユースケースは、CableReadyを自分のユースケースに組み込むのがいかに簡単かを示す例でもあります。一歩先を考えれば、今回草案した配信手段をライブラリとして切り出すことも難しくありません。
本記事が、皆さんのアプリの中にある「縦方向のリアクティブな問題領域」を見つけ出し、CableReadyを試してみるきっかけとなれば幸いです。
さらに深く学びたい方は、拙著『Advanced CableReady』をぜひお手に取ってください。
クーポンコード APPSIGNAL-PROMO を使えば10ドルお得になります!
それでは、Happy coding!
P.S. Ruby Magicの記事を公開と同時に読みたい方は、Ruby Magicニュースレターを購読して、記事を見逃さないようにしましょう!
-
【Rails】Flashメッセージの使い方を徹底解説!alertとnoticeの違いからflash.nowまで
Flashメッセージとは? Flashメッセージとは、Railsアプリケーションのユーザーに対して、自分の操作によって何が起こったのかを伝えるための仕組みです。 メッセージの例: 「パスワードが正しく変更されました」(確認メッセージ) 「ユーザーが見つかりません」(エラーメッセージ) Flashメッセージはコントローラーで設定し、ビューで表示します。ユーザーはそのメッセージを受け取って、次のアクションにつなげられます。 それでは、具体的な使い方を見ていきましょう! Flashメッセージの基本的な使い方 通知メッセージは、flashヘルパーメソッドを使って扱います。 flashオブジェクトは
-
RubyのStructとOpenStructの使い方を徹底解説!値オブジェクトを簡単に作る方法
Rubyには、関連する属性をまとめて保持する「値オブジェクト」を手軽に作れるStructという組み込みクラスが用意されています。本記事では、Structの基本的な使い方から注意点、そしてOpenStructとの違いまで詳しく解説します。 そもそもStructとは何か? StructはRubyに組み込まれたクラスで、新しいクラスを動的に生成し、値オブジェクト(Value Object)を作成するために使われます。値オブジェクトとは、関連性のある複数の属性をひとまとめにして扱うためのオブジェクトです。 具体例を挙げてみましょう。 2つの座標(xとy)を持つPointというデータを表したい場合、この