Turbo 8はライブ更新するRailsアプリケーションの開発を簡素化します。Turbo FramesとTurbo Streamsのレスポンスを手作業でコーディングする必要性を最小限に抑えることで、以前のバージョンから飛躍的に進歩しています。この進歩によってRailsアプリケーションの開発と保守の両方が簡素化され、生産性がさらに向上します。
Turboをご存じない方もいらっしゃるかもしれませんが、これはRuby on Railsアプリケーションで広く使用されているライブラリで、ページを部分的に更新してシングルページのJavaScriptアプリケーションと同じように応答性を感じさせるものです。HTMX、StimulusReflex、Phoenix LiveView、Laravel LiveWireなどのフレームワークに似ています。
Turbo 8は本当にスマートなページリローダーだと考えてください
これは単純化しすぎですが、この例えはTurbo 8の仕組みをよりよく理解するのに役立ちます。考え方はこうです:
- Railsはデータが変更されるとパブリッシュします。
broadcasts_refreshes
を持つRailsモデルはActionCableを介してモデルが作成、更新、破棄されるとパブリッシュします。 - ページが気になるデータ変更を購読します。 ページが読み込まれると、Turbo JavaScriptがスキャンして
<turbo-cable-stream-source/>
タグを探します。各タグにはActionCable経由のデータ変更通知を購読するために使用されるモデルクラスとIDが記述されています。 - モデルが更新されるとサブスクライブされたページは何かが変更されたという通知を受け取ります。 TurboはバックグラウンドでHTMLページ全体をHTTP経由でリクエストし、新しいHTMLと現在ロードされている古いHTMLを比較します。HTMLファイル間に差異がある場合、ページ全体をリロードすることなく、差異のみをページに適用します。
それだけです。これがフレームワークです。印象的なのは、その印象の薄さです。私がこれまで見てきたチュートリアルのほとんどはTurbo 7とTurbo 8の比較にとらわれていますが、それは理解を難しくしていると思うのでTurboの古いバージョンについて知っていることはすべて忘れて、ベータ版を試すことで今後どのように動作するかを見てみましょう。
Turbo 8ベータ版のインストール
以下をGemfileに追加するか既存のturbo-rails
エントリーを更新して、Turbo 8 gemをインストールしてください。
# Hotwire's SPA-like page accelerator [https://turbo.hotwired.dev] gem "turbo-rails", "~> 2.0.0.pre.beta"
Railsサーバーを再起動して準備を整えましょう!
ページの先頭にTurboタグを追加する
最初に行う必要があるのはアプリケーションレイアウトの<head/>
タグにタグを追加することです。
<%= turbo_refreshes_with method: :morph, scroll: :preserve %> <%= content_for :head %>
これによりTurbo 8の動作がページを「モーフィング」してスクロール位置が保持されるように設定されます。Turboの「古い」動作はページ全体を「置き換え」スクロール位置を「リセット」します。
コンテンツページを読み込むには依然としてTurboの「従来の」動作が必要です。コンテンツページのようにすべてにmethod: :morph, scroll: preserve
を適用すると、ユーザーがコンテンツをクリックしたときにページがトップではなく真ん中から始まるという奇妙な動作が発生します。
ページがモデルに対してサブスクライブすることで変更を常に通知し、それに応じてリロードします。
ビューを更新したいとき、アプリケーションのビューファイルからサブスクライブします。例えば作者が変更を公開したときに更新したいブログ記事がある場合、./app/views/post/show.html.erb
の先頭に次のように追加します。
<%= turbo_stream_from @post %> <h1><%= @post.title %></h1>
このヘルパーは、HTMLにこのようなタグを出力します:
<turbo-cable-stream-source channel="Turbo::StreamsChannel" signed-stream-name="IloybGtPaTh2YzJWeWRtVnlMMVZ6WlhJdk1RIg==--b4bcfff51ae4074540fdefbada55a237d68206bf960bd30a6684b310a255656c" connected=""></turbo-cable-stream-source>
不可解に見えるかもしれませんがsigned-stream-name
属性はモデルクラス内にあり、Turboが変更を購読するIDです。投稿が更新されるとTurboはサーバーから「このブログの投稿が変更されました」というシグナルを受け取ります。そしてJavaScript経由で現在のHTMLページにHTTPリクエストを行い、新しいHTMLをすでにロードされているDOMに差分し、ページをリロードする必要がないように2つの変更をマージします。
素晴らしい!これで変更をサーバーにサブスクライブするクライアントができました。Post
モデルに変更を公開するようRailsに指示する必要があるので、Post
モデルに以下を追加します。
class Post < ApplicationRecord # When the model instance is changed, a message will sent over # ActionCable that notifies the page to reload. broadcasts_refreshes end
これでPost
モデルを作成、更新、破棄するとRailsはそれをActionCableでパブリッシュし、変更があった場合はすべての関連ページにリロードするよう通知します。
コレクションを更新する方法
通常、コレクションはアプリケーション内のいずれかに属しています。例えばブログにはたくさんの投稿があります。ブログの全投稿を一覧表示するビューが./app/views/blog/posts/index.html.erb
のどこかにあるでしょう。
<%= turbo_stream_from @blog %> <%= render @blog.posts %>
そしてBlog
に属するという関連付けをPost
モデルに追加します。重要なのはtouch: true
を追加することです。
class Post # Touch will update the timestamp on the blog when # a post is created, updated, or destroyed. belongs_to :blog, touch: true # When the model is changed, a message will sent over ActionCable. broadcasts_refreshes end
それからBlog
モデルは更新をブロードキャストする必要があります:
class Blog has_many :posts broadcasts_refreshes end
投稿が作成、更新、削除されるとBlogモデルはタイムスタンプを更新して、Blogインスタンスへの変更をリッスンしているページの更新をトリガーします。
何にも属さないコレクションについてはどうでしょうか?
実際にはこれはアプリケーションではまれなことです。例えばブログはおそらくユーザーやアカウントに属しており、上記と同様に "タッチ "することができます。以下はその例です。
class Blog < ApplicationRecord # Code from above removed for clarity belongs_to :user, touch: true end
そしてUser
モデルにbroadcast_refreshes
を追加します。
class User < ApplicationRecord has_many :blogs # Blog will touch the account when something is changed. broadcasts_refreshes end
そして全ユーザーのブログが一覧表示されるダッシュボード・ページで、アカウントの変更を確認します:
<%= turbo_stream_from current_user %> <%= render @current_user.blogs %>
アプリケーションに親オブジェクトでモデル化できないコレクションがある場合でも、Turbo Streamsを使用してリストに追加することができます。
PostgresまたはSQLiteを使用して変更をブロードキャストする
Turbo 8ではHTMLペイロードをWebSocket経由でプッシュする必要がないため、Postgres ActionCableアダプタの8000バイト制限はもはや問題ではありません。小規模から中規模のアプリケーションでRedisまたはActionCable pub-subのみを使用している場合、依存関係としてRedisを排除することでアプリケーションのインフラストラクチャを簡素化できます。
SQLiteアプリケーションを本番環境にデプロイする場合、RailsアプリケーションにLitestackをインストールし、Litecableを使ってTurbo 8上で変更通知を発行することができます。
Rails開発の未来にもう一度ワクワクしよう
Railsアプリケーションを自動更新するために必要な労力は驚くほどわずかです。Turbo 8はまだベータ版であり、Railsアプリケーションを構築するためのこのアプローチには考慮すべきエッジケースがたくさんありますが、この技術はRailsアプリケーション開発をさらに簡素化する有望な方法になりつつあります。
Turbo 8より前のバージョンでTurbo Framesに大きく投資している場合、おそらく移行で一番大変なのはコントローラコード内のformat.turbo_stream
ブロックとビューのturbo_frame
タグをすべて削除することでしょう。何千行ものコードを削除してgitコミットすることが生産的だと考えているのであれば、非常に生産的な開発の日々が待っていることを覚悟してください。