「いいね」ボタン (Turbo Drive)

Turbo Drive版の特徴

  • コードは最もシンプルです。
  • ページ全体を再描画します。
    • 通常は再描画によりページのUIステートがリフレッシュされます。目立つところではスクロール位置がリセットされます
    • ただしMorphingを使うと、Reactの差分アルゴリズムと類似の処理が行われ、変更箇所だけを修正できます。そのためMorphingを使うとスクロール位置をはじめとした各種のUIステートを維持できます
  • 更新系はPOST/Redirect/GETパターンに従うため、更新されたデータを受けとるまでにサーバに2回リクエストを飛ばします。
  • Optimistic(楽観的)UIを使用していないことに加え、POST/Redirect/GETをしないといけないので、「いいね」ボタンを押してから実際に画面に反映されるまでに時間がかかります。

コード

Todoの各行

app/views/todos/_todo.html.erb
<% highlight = local_assigns.fetch(:highlight, false) %>

<tr class="group p-2" id="<%= dom_id(todo) %>">
  <td class="<%= 'highlight-on-appear' if highlight %> p-2 border-gray-400 border-t group-[:first-child]:border-none">
    <div class="flex">
      <div class="flex grow items-center">
        <%= render 'like_button', todo: %>
        <%= todo.title %>
        <!-- ... -->
      </div>
        <!-- ... -->
    </div>
  </td>
</tr>
  • Todo一覧の各行を表示する app/views/todos/_todo.html.erb partialです
  • render 'like_button', todo:で「いいね」ボタンを表示しています。

「いいね」ボタン MPAバージョン

app/views/todos/_like_button.html+mpa.erb
<% todo = local_assigns.fetch(:todo) %>

<%= tag.div class: "flex items-center w-16 aria-busy:opacity-30" do %>
  <%= label_tag nil, class: "group flex cursor-pointer select-none" do %>
    <% if todo.liked_by?(current_user) %>
      <%= button_to todo_likes_path(todo), method: :post do %>
        <%= liked_icon %>
      <% end %>
    <% else %>
      <%= button_to todo_likes_path(todo), method: :post, params: { like: "1" } do %>
        <%= unliked_icon %>
      <% end %>
    <% end %>
    <div>
      : <span><%= todo.likes_count %></span>
    </div>
  <% end %>
<% end %>
  • todo.liked_by?(current_user)のところで「いいね」済みかどうかを確認し、それに応じて「いいね」ボタンの表示を切り替えています。
  • 「いいね」ボタンはtodo_likes_pathにPOSTリクエストを送信します。

Todos::LikesController MPAバージョン

app/controllers/todos/likes_controller.rb
class Todos::LikesController < ApplicationController
   # ...

   def create
      sleep 1

      if params[:like]
         @todo.like_by! current_user
      else
         @todo.unlike_by! current_user
      end

      if request.variant.drive?
         return redirect_to todos_path
      end
   end

   private

      def set_todo
         @todo = Todo.find(params[:todo_id])
      end
end
  • createメソッドで「いいね」ボタンのアクションを実行します。
  • Turbo Driveを使用している場合は、DBを更新後、return redirect_to todos_pathをします。いわゆるPOST/redirect/GETのパターンです
  • 通常のTurbo Driveであれば、redirect後にTodo一覧ページを再描画するとき、スクロール位置がリセットされます(画面の最上部にスクロールします)
    • しかし今回はapp/views/todos/index.html.erbturbo_refreshes_with method: :morph, scroll: :preserveを設定しているため、Morphingを使った再レンダリングをしています。そのためスクロール位置は維持されます
    • 最もシンプルなPOST/redirect/GETパターンを使いつつ、スクロール位置を含めたブラウザステートを維持したい場合、Morphingは非常に有効です