2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Turbo Stream × Action Cable でリアルタイム更新を実装する!Turbo Rails Tutorial をやってみた(5章)

Last updated at Posted at 2023-04-29

本記事は、🔽「Turbo Rails Tutorial」の学習を進めていく際の個人メモです!

🔽 前章の記事はこちら

5章 Real-time updates with Turbo Streams

何やら Turbo Stream と Action Cable を使うと、リアルタイム更新の実装はめちゃくちゃ簡単で、コードもほとんど必要ないとの事。試してみましょう。

リアルタイム更新(create)を実装する

redis入っていない場合はbrew install redisしておきます。

$ bin/dev

で、redisが動いてなさそうなので、Procfile.devredisーserverを追加してやり直し 🔽

Procfile.dev
web: unset PORT && bin/rails server
js: yarn build --watch
css: yarn build:css --watch
redis: redis-server /usr/local/etc/redis.conf

まずはQuoteモデルに以下のコードを追加します。

app/models/quote.rb
class Quote < ApplicationRecord
  validates :name, presence: true

  scope :ordered, -> { order(id: :desc) }

  # 以下を追加する
  after_create_commit -> { broadcast_prepend_to "quotes", partial: "quotes/quote", locals: { quote: self }, target: "quotes" }
end

次にビュー側にquotesの stream を受け取るコードを書きます。

app/views/quotes/index.html.erb
<%= turbo_stream_from "quotes" %>
.
.

たったこれだけです、リアルタイム更新を確認してみます。

確認方法1. コンソールを使う

rails consoleから新しい見積もりを作成してみます。

> Quote.create!(name: "コンソールから作成")

ブラウザを見てみると、即座に追加されています。
スクリーンショット 2023-04-29 11.11.11.png

確認方法2. 複数のウインドウを並べる

ブラウザを2つ並べてリアルタイム更新を確認できます。右で作成したものが左にも即座に反映されていますね!
output.gif

リファクタリング

先ほどモデルに書いたコードは、短縮が可能です。まずは、現在のフルバージョンがこちら。

app/models/quote.rb
after_create_commit -> { broadcast_prepend_to "quotes", partial: "quotes/quote", locals: { quote: self }, target: "quotes" }

最後にターゲット名を"quotes"と指定していますが、デフォルトではモデル名の複数形がターゲットに入るのでtarget: "quotes"を省略できます。

app/models/quote.rb
after_create_commit -> { broadcast_prepend_to "quotes", partial: "quotes/quote", locals: { quote: self } }

また、partiallocalsにもデフォルト値が設定されています。

  • パーシャルのデフォルト値は、Quoteモデルでは"quotes/quote"
  • localsのデフォルト値は{ モデル名をシンボル化したもの => self }で、Quoteモデルでは{ quote: self }

デフォルト値と現在記載しているものは同等なので、これらも省略可能となり、最終的には以下のコードになりました。

app/models/quote.rb
after_create_commit -> { broadcast_prepend_to "quotes" }

リアルタイム更新(update)を実装する

Quoteモデルにコードを追加します。

app/models/quote.rb
class Quote < ApplicationRecord
  validates :name, presence: true

  scope :ordered, -> { order(id: :desc) }

  after_create_commit -> { broadcast_prepend_to "quotes" }
  # 以下を追加する
  after_update_commit -> { broadcast_replace_to "quotes" }
end

コードを追加したら一旦rails consoleを再起動し、createの時と同じくコンソールから更新すると、

> Quote.last.update!(name: "コンソールから更新")

ブラウザで更新されています。
スクリーンショット 2023-04-29 13.36.02.png

リアルタイム更新(destroy)を実装する

この雰囲気で進めてきたら、destroyQuoteモデルに1行足すだけで実装できそうだなと想像できますね。

app/models/quote.rb
class Quote < ApplicationRecord
  validates :name, presence: true

  scope :ordered, -> { order(id: :desc) }

  after_create_commit -> { broadcast_prepend_to "quotes" }
  after_update_commit -> { broadcast_replace_to "quotes" }
  # 以下を追加する
  after_destroy_commit -> { broadcast_remove_to "quotes" }
end

rails consoleを再起動しコンソールから削除すると、

> Quote.last.destroy!

最後の見積もりが削除されています。
スクリーンショット 2023-04-29 13.45.03.png

ActiveJob で非同期にする

これまで実装してきたリアルタイム更新は、非同期処理することでさらにパフォーマンスを向上させることが可能です。Turbo Streamsの画面の更新は、非同期処理で行われることが推奨されています。

app/models/quote.rb
class Quote < ApplicationRecord
  validates :name, presence: true

  scope :ordered, -> { order(id: :desc) }

  # 以下に変更
  after_create_commit -> { broadcast_prepend_later_to "quotes" }
  after_update_commit -> { broadcast_replace_later_to "quotes" }
  after_destroy_commit -> { broadcast_remove_to "quotes" }
end

broadcast_remove_later_toは存在しないようです。(データベースから削除されてしまうとJob実行時にレコードを取得できないから)

コンソールから見積もりを作成してみると、ログがfrom Asyncとなっているのを確認できました。

> Quote.create!(name: "Asynchronous quote")
.
.
Performing Turbo::Streams::ActionBroadcastJob (Job ID: 96977e78-e5a5-4308-9ca7-3a3838f57907) from Async(default) enqueued at 2023-04-29T11:54:42Z with arguments: "quotes", {:action=>:prepend, :target=>"quotes", :targets=>nil, :locals=>{:quote=>#<GlobalID:0x000000010cf3db28 @uri=#<URI::GID gid://quote-editor/Quote/908005744>>}, :partial=>"quotes/quote"}
  Rendered quotes/_quote.html.erb (Duration: 0.4ms | Allocations: 297)
[ActionCable] Broadcasting to quotes: "<turbo-stream action=\"prepend\" target=\"quotes\"><template><turbo-frame id=\"quote_908005744\">\n  <div class=\"quote\">\n    <a data-turbo-frame=\"_top\" href=\"/quotes/908005744\">Asynchronous quote</a>\n    <div class=\"quote__actions\">\n      <form class=\"button_to\" method=\"post\" acti...
Performed Turbo::Streams::ActionBroadcastJob (Job ID: 96977e78-e5a5-4308-9ca7-3a3838f57907) from Async(default) in 2.78ms

リファクタリング

ここまでで、リアルタイム更新を非同期で行うことができました。そして今から、RailsのDRY魂を見ることになります。

機能はそのままでこうなります。

app/models/quote.rb
class Quote < ApplicationRecord
  validates :name, presence: true

  scope :ordered, -> { order(id: :desc) }

  # after_create_commit -> { broadcast_prepend_later_to "quotes" }
  # after_update_commit -> { broadcast_replace_later_to "quotes" }
  # after_destroy_commit -> { broadcast_remove_to "quotes" }
  # 上記の3行は、次の1行と同等
  broadcasts_to ->(quote) { "quotes" }, inserts_by: :prepend
end

まとめ

(ビューとモデルに1行ずつ、計2行追加するだけで、リアルタイム更新が完成した。Railsすっご!)

特にモデルに追加した1行は、徐々にリファクタリングしていったので理解が深まった気がします。

次の章では、セキュリティについて学びます。Turbo Streams を安全に使用し、非同期処理の画面更新時に間違ったユーザーに送信しないようにする方法について学びます。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?