[Rails]turbo_streamでコメントをBroadcastする
はじめに
turbo_stream
を使って、投稿に新しいコメントを追加されるとき、リアルタイムなDOMの更新を行ってコメントをページに追加します。
turbo_stream.prepend
でコメントしたユーザーのブラウザに更新を行いますが、Broadcastも効かせて全てのユーザーのブラウザに表示されるようにしたいです。
Turbo Stream
Turbo Streamは、サーバーサイドで生成されたデータの変更をクライアントに伝え、それに基づいてクライアント上で動的な更新を実現するための仕組みです。
Broadcast
BroadcastはイベントベースでTurbo Streamsを生成および送信する方法を提供します。
アプリ内で生成されたイベントをリッスンし、それに応じてTurbo Streamsを送信します。
Broadcastの基本的なフローは次のようになります:
- サーバーサイドでデータが変更される(例:新しいコメントが投稿される)と、Turbo Streamsを使用してその変更がストリームに含まれるHTMLフラグメントとして生成されます。
- サーバーはWebSocketを介してクライアントにTurbo Streamsメッセージをプッシュします。
- クライアントはTurbo Streamsメッセージを受信し、Turbo Streams内のHTMLフラグメントをクライアント上の指定されたTurbo Frame要素内更新を行います。
環境
Rails 7.0.7
ruby 3.2.1
Redis
流れ
1. コメントの更新用streamを作成する
2. コメントの更新用broadcast
メソッドを作成する
turbo_stream_from
を作成する
コメント用コメントを表示させたいビュー(例:投稿の詳細ページ)にturbo_stream_from
を作成します。
turbo_stream_from
は、Turbo Streamsの更新を監視し、その変更に対して自動的にリアルタイムで再レンダリングを行うためのRailsヘルパーメソッドです。
名前が"comments"
のTurbo Streamsを生成し、クライアントがそのストリームからのメッセージを受信することになります。
<%= turbo_stream_from "comments" %>
Redisを立ち上げて、Turbo Streamを追加されたことを確認します。
コメント一覧パーシャル
<%= turbo_frame_tag 'comments' do %>
<% comments.each do |comment| %>
<%= render 'comments/comment', comment: comment %>
<% end %>
<% end %>
コメント詳細パーシャル
<%= turbo_frame_tag dom_id(comment) do %>
...
<%= comment.user %>
<%= comment.body %>
...
<% end %>
Broadcastメソッドを作成する
class Comment < ApplicationRecord
...
after_create_commit -> { broadcast_prepend_to "comments",
partial: "comments/comment",
locals: { comment: self }, target: "comments" }
end
-
after_create_commit
: コメントがデータベースにcommitされた後に呼び出されるコールバックです。 -
broadcast_prepend_to "comments"
: Turbo Streamsを指定のストリームにブロードキャストします。"comments"
ストリームに対して、新しいコメントを追加するように指示しています。 -
partial: "comments/comment"
: この部分は、新しいコメントが表示される際にどの部分テンプレートを使用するかを指定します。"comments/comment"
部分テンプレートが使用されます。 -
locals: { comment: self }
: 部分テンプレートに渡されるローカル変数です。 -
target: "comments"
: 送信されるターゲットを指定します。"comments"
ターゲットにTurbo Streamを内容を送信します。
WebSocketを介してTurbo Streamsはcomments
からストリーミングし、comment
をcomments
にprepend
されることを確認します。
19:34:01 web.1 | Started GET "/cable" for ::1 at 2023-09-02 19:34:01 +0900
19:34:01 web.1 | Started GET "/cable" [WebSocket] for ::1 at 2023-09-02 19:34:01 +0900
19:34:01 web.1 | Successfully upgraded to WebSocket (REQUEST_METHOD: GET, HTTP_CONNECTION: Upgrade, HTTP_UPGRADE: websocket)
19:34:01 web.1 | Turbo::StreamsChannel is transmitting the subscription confirmation
19:34:01 web.1 | Turbo::StreamsChannel is streaming from comments
19:34:02 web.1 | Started GET "/cable" for ::1 at 2023-09-02 19:34:02 +0900
19:34:02 web.1 | Started GET "/cable" [WebSocket] for ::1 at 2023-09-02 19:34:02 +0900
19:34:02 web.1 | Successfully upgraded to WebSocket (REQUEST_METHOD: GET, HTTP_CONNECTION: Upgrade, HTTP_UPGRADE: websocket)
19:34:02 web.1 | Turbo::StreamsChannel is transmitting the subscription confirmation
19:34:02 web.1 | Turbo::StreamsChannel is streaming from comments
19:34:25 web.1 | Turbo::StreamsChannel transmitting "<turbo-stream action=\"replace\" target=\"comment_3\"><template><turbo-frame id=\"comment_3\">\n <div class=\"my-5\" id=\"comment_3\">\n <div class=\"bg-white py-5 px-3 rounded shadow\">\n <div class=\"flex space-x-3 pb-2 border-b\">\n <div class=\"flex-shrink-0\">\n <di... (via streamed from comments)
コメントの編集と削除
コメントの編集と削除も同じようにBroadcastしたいのでコールバックメソッドを作成します。
先に作成したメソッドでもよかったですが、Railsのネーミング規則に従って命名しているため、partial
、locals
、target
を指定しなくても大丈夫です。
なので、一行で書くこともできます。
class Comment < ApplicationRecord
...
after_create_commit { create_broadcast }
after_update_commit { update_broadcast }
after_destroy_commit { destroy_broadcast }
private
def create_broadcast
broadcast_prepend_to "comments"
# 非同期 broadcast_prepend_later_to "comments"
end
def update_broadcast
broadcast_replace_to "comments"
# 非同期 broadcast_replace_later_to "comments"
end
def destroy_broadcast
broadcast_remove_to "comments"
end
end
irb(main):017:0> c = Comment.last
Comment Load (0.9ms) SELECT "comments".* FROM "comments" ORDER BY "comments"."id" DESC LIMIT ? [["LIMIT", 1]]
=>
#<Comment:0x000000010e308860
...
# パーシャル
irb(main):018:0> c.to_partial_path
=> "comments/comment"
# locals:model_name.element.to_sym => self
# モデルのelementをシンボルに変換し
irb(main):019:0> c.model_name.element
=> "comment"
irb(main):020:0> c.model_name.element.to_sym
=> :comment
# target:モデル名の複数形
irb(main):021:0> c.model_name.plural
=> "comments"
Broadcastの使えるメソッド:
終わりに
turbo_stream
に対しての理解がまだ浅いですが少しずつ練習して理解を深めていきたいです。
参考した記事:
Discussion