はじめに
JSON でなく HTML を送ることでモダンなウェブアプリケーションを開発できる Hotwire に少し前から興味がありました。
Hotwireとは何なのか? を始めとした日本語の情報を拾い読みしていたのですが、
ユーザにとっても、開発の進め方も Progressive Enhancement にできること
と書かれていたりして、わかったような、わからないような。
で、Hotwire を開発した DHH 氏みずから Hotwire の使い所を解説していた Podcast があって聞いてみたら、とてもわかりやすくて納得した。
» 151: DHH – Building HEY with Hotwire | Full Stack Radio
実際にデモアプリを作りながら、Hotwire がどういうものなのかを見ていきます。
クラシックなウェブアプリを作る
まずは現時点で最新の Ruby 3.0.0 & Rails 6.1.0 の環境を用意します。長らく gem install rails で最新の rails をインストールしてから rails new する方法をとってきたのですが、今回Ruby 歴 10 年の私が【絶対に】 gem install rails コマンドを実行しない理由を参考に、bundle init で作った Gemfile に rails のバージョンを指定するやり方で用意しました。
% npm install --global yarn (yarn が未インストールの場合)
% mkdir hotwire_sample
% cd hotwire_sample
% rbenv install 3.0.0 (definition not found と出た場合はその前に brew update && brew upgrade ruby-build)
% bundle init
Gemfile を開き
gem 'rails', '~> 6.1.0'
を追記したら、
% bundle install --path=vendor/bundle
% bundle exec rails new .
で rails をインストールします。
% bundle exec rails s
で rails サーバーを起動し、http://localhost:3000/ にアクセスして、「Yay! You’re on Rails!」のページが表示されればOK。
Rails でデモアプリと言えば、かつてDHHが15分でつくってみせたブログアプリです。
% bundle exec rails g scaffold Post title:string body:text
% bundle exec rails db:migrate
2005年には15分かかっていましたが、いまや一瞬でタイトルと本文が入力できるブログアプリが完成しました。

Hotwire をインストール
% bundle add hotwire-rails
% bundle exec rails hotwire:install
で hotwire をインストールします。
次に app/views/layouts/application.html.erb の head 内に
<%= turbo_include_tags %>
を追加します。
モダン化その1 - 画面遷移しないようにする
Scaffold だけでつくったブログ、僕はこれで十分便利と思うのですけれど、2021年では「いちいち画面遷移するなんてダサい、モダンじゃない」という声が聞こえてきそうです。
そこで記事を追加する New Post のページを、画面遷移せずリスト画面の下の方にそのまま表示するようにします。
iframe で New Post をはめこむようなイメージです。実際には Hotwire の Turbo Frames という機能の turbo_frame_tag を使います。
app/views/posts/index.html.erb の New Post のリンクを
<%= turbo_frame_tag 'new_post', src: new_post_path, target: :_top %>
に入れ替え、app/views/posts/new.html.erb の中身全体を turbo_frame_tag 'new_post' で囲んでしまいます。
<%= turbo_frame_tag 'new_post' do %>
<h1>New Post</h1>
<%= render 'form', post: @post %>
<%= link_to 'Back', posts_path %>
<% end %>
turbo_frame_tag に続く 'new_post' の部分にはユニークなIDを付け、同じIDの部分が対応しています。つまり、index.html.erb の turbo_frame_tag 'new_post' の部分に、app/views/posts/new.html.erb の中身全体がそのまま表示されます。
app/controllers/posts_controller.rb の def create 内で posts コントローラーの show にリダイレクトしている部分はコメントアウトし、index にリダイレクトするように変更します。
# format.html { redirect_to @post, notice: 'Post was successfully created.' }
format.html { redirect_to posts_url, notice: 'Post was successfully created.' }
これで、画面遷移せず、記事を登録できるモダンなアプリの出来上がりです。この変更には、リダイレクト先を変える以外、モデルやコントローラーの修正はほとんど必要ありません。

モダン化その2 - リアルタイムでページを更新する
ブログのアプリでその必然性はほとんどないと思うのですが、モダンなアプリというのは、データが変更されたときにリロードすることなくリアルタイムにページが更新されるものです。
2000年-2004年アメリカに住んでいたとき、サッカー日本代表チームの試合の模様をテキストで伝える実況サイトを見ていて、5秒間ごとにリロードボタンを押しながら一喜一憂していたものですが、2021年ではもうそんなことは許されません。
そこで Hotwire の Turbo Streams を使います。
これを使うには redis が必要なので
% brew install redis
で redis をインストールします。
記事が更新されたときに、更新されたことを通知するため、app/models/post.rb の Post モデルに broadcasts_to を追記します。通知先となる 'posts' は、後述する
に付ける id です。
class Post < ApplicationRecord
broadcasts_to ->(_post) { 'posts' }
end
Turbo Streams を使ってデータをリアルタイムに更新するには更新する部分を partial ファイルに切り出す必要があります。ブログ記事を列挙している部分を partial ファイルに置き換えます。
新たに app/views/posts/_post.html.erb というファイルを追加し、index.html.erb から tr 部分を移してきます。id には dom_id(post) を設定します。
<tr id="<%= dom_id(post) %>">
<td><%= post.title %></td>
<td><%= post.body %></td>
<td><%= link_to 'Show', post %></td>
<td><%= link_to 'Edit', edit_post_path(post) %></td>
<td><%= link_to 'Destroy', post, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
tr 部分を parial ファイルに移したので、app/views/posts/index.html.erb の tbody の部分は以下のようになります。
<%= turbo_stream_from 'posts' %>
<tbody id="posts">
<% @posts.each do |post| %>
<%= render post %>
<% end %>
</tbody>
turbo_stream_from 'posts' で post インスタンスに関する変更内容を WebSocket 経由で受け取れるようになります。
以上でモダン化その2が完了です。
一方のブラウザで記事の内容を更新したり、削除すると、もう一方のブラウザにすぐに反映されます。

まとめ
ここまでで、クラシックなアプリに 1) 遷移しない画面、2) リアルタイムに更新される画面、というモダンなウェブアプリケーションに特徴的な機能を追加しました。
意図的ですが、JavaScript は一行も書いていません。しかしさらに細かくモダンな機能を追加していくにはさすがに JavaScript を書く必要があり、そこは Hotwire の Stimulus を使うことになります。Stimulus の導入は若干大変なので、次の機会にでもブログで紹介しようと思います。
Hotwire を少し使ってみてわかったのは、サーバーサイドでHTMLをレンダリングするクラシックなウェブアプリケーションをベースにして、必要な箇所に Turbo Frames を使って遷移を省いたり、Turbo Streams を使ってリアルタイム性を追加したり、細かな機能追加には Stimulus を使っていくというのが Hotwire のアプローチだということでした。
冒頭の Progressive Enhancement (段階的な改善)というのは、そういうことなのだ納得しました。
Podcast での DHH 氏の解説の端々に、「Ruby 最高、全部 Ruby で書きたい。JavaScript はできるだけ書きたくない」という Ruby 愛が感じられます。2019年、東京で開催された Rails Developer Meetups の基調講演 で、DHH は、ほとんどのアプリケーションは JavaScript のフレームワークなんて必要としていない、もっとシンプルに作れるはずだと語っていました。
デモのために無理やり Turbo Streams を使ったブログ記事の更新のように、そのままで十分なところは通常のサーバーサイドレンダリングのままに、たとえばチャットの画面のように必要なところだけをモダン化していけば良いというのが DHH および Hotwire のアプローチなのだと思います。
参考
さらに詳細を知りたい場合は、Hotwire | Drifting Ruby のチュートリアル動画に沿って、チケット管理システムのデモアプリを作ってみることから始めるのが良いと思います。
ファーレ立川アート、街なかに100以上パブリックアートが展示されておりクエストの作りがいがありました。
https://t.co/WkdnKlCTSD
まだわずか3つだけですが、結構良問が作れたと思うので機会があったらやってみてください。
Scratch に関する成果物や執筆した書籍、Web記事などをまとめておくページを用意しようと思ったんだけど、作った拡張機能を列挙するだけで疲れたので今日はここまで。随時書き足していきます。
https://t.co/mYFzrfuFeW
面白かった。そして、未来は常に現在よりも進歩していて、明るいと信じている姿勢が素晴らしいと思った。
【第4回:村井純様】デジタル庁、今だからこそ僕らにはできる。「誰一人置いてきぼりにしない」デジタル政策を!
https://t.co/M7ICiKieoj
わざわざ家のテレビにつながっているApple TVでYouTube検索して大画面で観ました。成功はたくさんの失敗をし続けた末にしかやってこないという人生の教訓を身に沁みて感じられる感動作でした。 https://t.co/IgVDp3enFQ
SpaceXのこれまでの着陸シーン。最後の2基同時着陸は圧巻。
https://t.co/8wb0gv3qtl
ウォッチしているオープンソースプロジェクト、GitHubのPRやIssueのコメントが中国語のみで書かれていることが最近多くなってきており、近い将来ソフトウェアエンジニアは英語と中国語の両方を読める必要が出てきそう。
https://t.co/ja2OwLkCX5 ってのを教えてもらった。良さそう。
かつて、エンジニアがちょっとしたWebサービスを作りたいというとき、Twitter Bootstrap でちょこちょこっと見た目を整えていたのだが、2020年は何を使うのが定番なのだろう。