本記事は、🔽「Turbo Rails Tutorial」の学習を進めていく際の個人メモです!
🔽 前章の記事はこちら
5章 Real-time updates with Turbo Streams
何やら Turbo Stream と Action Cable を使うと、リアルタイム更新の実装はめちゃくちゃ簡単で、コードもほとんど必要ないとの事。試してみましょう。
リアルタイム更新(create
)を実装する
※redis
入っていない場合はbrew install redis
しておきます。
$ bin/dev
で、redis
が動いてなさそうなので、Procfile.dev
にredisーserver
を追加してやり直し 🔽
web: unset PORT && bin/rails server
js: yarn build --watch
css: yarn build:css --watch
redis: redis-server /usr/local/etc/redis.conf
まずはQuote
モデルに以下のコードを追加します。
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 を受け取るコードを書きます。
<%= turbo_stream_from "quotes" %>
.
.
たったこれだけです、リアルタイム更新を確認してみます。
確認方法1. コンソールを使う
rails console
から新しい見積もりを作成してみます。
> Quote.create!(name: "コンソールから作成")
確認方法2. 複数のウインドウを並べる
ブラウザを2つ並べてリアルタイム更新を確認できます。右で作成したものが左にも即座に反映されていますね!
リファクタリング
先ほどモデルに書いたコードは、短縮が可能です。まずは、現在のフルバージョンがこちら。
after_create_commit -> { broadcast_prepend_to "quotes", partial: "quotes/quote", locals: { quote: self }, target: "quotes" }
最後にターゲット名を"quotes"
と指定していますが、デフォルトではモデル名の複数形がターゲットに入るのでtarget: "quotes"
を省略できます。
after_create_commit -> { broadcast_prepend_to "quotes", partial: "quotes/quote", locals: { quote: self } }
また、partial
とlocals
にもデフォルト値が設定されています。
- パーシャルのデフォルト値は、
Quote
モデルでは"quotes/quote"
-
locals
のデフォルト値は{ モデル名をシンボル化したもの => self }
で、Quote
モデルでは{ quote: self }
デフォルト値と現在記載しているものは同等なので、これらも省略可能となり、最終的には以下のコードになりました。
after_create_commit -> { broadcast_prepend_to "quotes" }
リアルタイム更新(update
)を実装する
Quote
モデルにコードを追加します。
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: "コンソールから更新")
リアルタイム更新(destroy
)を実装する
この雰囲気で進めてきたら、destroy
もQuote
モデルに1行足すだけで実装できそうだなと想像できますね。
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!
ActiveJob で非同期にする
これまで実装してきたリアルタイム更新は、非同期処理することでさらにパフォーマンスを向上させることが可能です。Turbo Streamsの画面の更新は、非同期処理で行われることが推奨されています。
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魂を見ることになります。
機能はそのままでこうなります。
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 を安全に使用し、非同期処理の画面更新時に間違ったユーザーに送信しないようにする方法について学びます。