Closed8

「Progressive Application Development with Hotwire」をやっていく

KotaKota

Turbo Drive

リンクをクリックしたり、フォームをSubmitするとTurbo Driveは次のことをおこなう。

  • ブラウザにリンクを辿らせない
  • History APIを使ってURLを変える
  • リクエストはFetchで行う(裏でJSが動く)
  • レスポンスを受け取り、bodyをリプレイスする
  • headに変更があればリプレイスし、変更がなければリプレイスしない

Form submissionsについても同じ

Turbo Drive converts Form submissions into fetch requests. Then it follows the redirect and renders the HTML response.
As a result, your browser doesn’t have to reload, and the app feels much faster.

KotaKota

counterをupdateするためのroutesを定義する。

Rails.application.routes.draw do
  resources :habits, only: [:show] do
    member do
      post :plus
      post :minus
    end
  end
end

これでrails routesすると以下のようなルーティングになる。

 plus_habit POST /habits/:id/plus(.:format)  habits#plus
minus_habit POST /habits/:id/minus(.:format) habits#minus

memberを使うことで、そのリソースのIDが含まれるパスを作ることができる。この場合、habitsリソースの特定のhabitに対してplus,minusというアクションを定義している。

POST /habits/:id/plus -> HabitsController#plus
POST /habits/:id/minus -> HabitsController#plus

次に、コントローラーにplusminusメソッドを追加している。

個人的にコントローラーに書くメソッド名はindexcreateupdate、、、などの基本7メソッド名しか書きたくない派だが、チュートリアルなのでそこはスルーしよう。

class HabitsController < ApplicationController
  before_action :set_habit

  def show; end

  def plus
    @habit.update(count: @habit.count + 1)
    redirect_to @habit
  end

  def minus
    @habit.update(count: @habit.count - 1)
    redirect_to @habit
  end

  private

    def set_habit
      @habit = Habit.find_by(params[:id])
    end
end

After updating the habit, we're redirecting the user to the habit, which renders the show action. It will fetch and render the updated habit. Makes sense?

redirect_toしているのは、showを再びrenderするため。そしてデータがフェッチされてbodyがリプレイスされる。SPAっぽくcountが変わる。

Upon receiving the request on the above endpoint, the minus controller action will decrement the habit count by one and redirect to the show_habit_path(@habit), which returns habits/1.

エンドポイント宛のリクエストを受けて、minusアクションはhabit.countをdecrementする。そしてshow_habit_path(@habit)へリダイレクトする。
このとき、Turbo Driveがリダイレクトのリクエストに従って、habits/1fetchリクエストをする。このリクエストを受診して、showアクションが呼ばれ、DBから更新されたhabit.countを取得する。そしてそれをレンダリングする。

Turbo Driveが勝手にSPA風の挙動にしてくれている。改めてこれはすごいよなぁ。

KotaKota

あえてブラウザにリロードさせると違いが顕著にわかる。

app/javascript/application.js

// Turbo Driveをオフる
Turbo.session.drive = false

を書いて実行すると、ブラウザがリロードしているのがわかる。

KotaKota

Turbo Frames

<%= turbo_frame_tag @habit do %>
  <div class="text-center font-bold text-gray-700" id="habit-name">
    <%= @habit.name %>
  </div>

  <div class="mt-3 flex justify-center items-center space-x-5">
    <%= button_to '-', minus_habit_path(@habit), class: "btn bg-red-300 inline-block shadow-lg" %>
    <div class="text-4xl font-bold"><%= @habit.count %></div>
    <%= button_to '+', plus_habit_path(@habit), class: "btn bg-green-300 inline-block shadow-lg" %>
  </div>

  <div class="mt-3 p-2 flex justify-center space-x-1">
    <% @habit.count.times do %>
      <div class="inline-block border p-1 bg-green-400"></div>
    <% end %>
  </div>
<% end %>

turbo_frame_tagで囲むと、そこの中で発生するリンククリックや、Form SubmissionsはTurboにインターセプトされる。そしていつも通り、Fetchリクエストにしてレスポンスを受け取る。dom_idにマッチしたturbo-frame要素を抽出して、そこだけを新しいコンテンツと入れ替える。body全体ではなく、マッチしたところだけというのがTurbo Driveとの違い。

KotaKota

Turbo Streams

  def plus
    @habit.update(count: @habit.count + 1)
    render :result
  end

  def minus
    @habit.update(count: @habit.count - 1)
    render :result
  end

resultテンプレートを呼ぶ👇

app/views/habits/result.turbo_stream.erb

<%= turbo_stream.replace 'habit-count' do %>
  <div id="habit-count"><%= @habit.count %></div>
<% end %>

<%= turbo_stream.replace 'habit-markers' do %>
  <%= render 'habit_markers', habit: @habit %>
<% end %>

habit count, streak makersをリプレイスする。

app/views/habits/_habit_markers.html.erb

<div id="habit-markers">
  <% @habit.count.times do %>
    <div class="inline-block border p-1 bg-green-400"></div>
  <% end %>
</div>

ボタンを押すと

turbo-strreamタグで囲まれたものだけがレンダーされている。

便利

KotaKota

まとめ

  • Turbo Drive
    • bodyのみをリプレイスしてくれる
    • 開発者は何もすることない。デフォルトでこの状態
  • Turbo Frames
    • 1箇所をもっとインタラクティブにしたいならこれ
  • Turbo Streams
    • 1つのレスポンスから複数の要素をリプレイスしたいならこれ

思想的には、あくまでも最初は何もしなくていい。アプリケーションにインタラクティビティが必要になったらその都度Turbo FramsなりStreamsなりを使えばいい。
ここではStimulusには触れていないのでチュートリアルはここまで。

KotaKota

感想

シンプルだけど、Hotwireのメリットを端的に身につけられるチュートリアルだった。Rails自体の基本的な説明が多く、Railsプログラマはそこはスルーできるので3時間くらいで終えられるんじゃないかな。

実践で使うならもうちょっと濃いチュートリアルをしたほうがいいと思うが、初めの一歩として最適なチュートリアルだと思う。

RailsのフロントエンドにはReactやVueなどのライブラリを採用するケースが多いと思うけど、「実際ここほんとにReactな必要ある?」みたいなプロダクトは多々あると思う。実際、自分が所属している会社では、Reactで実装していた箇所をTurbo系にリプレイスして数百行のコードを削除できたことがあった。

さすが、「一人のためのフレームワーク」という謳い文句に違わない機能だ。

このスクラップは4ヶ月前にクローズされました