はじめに
この記事は、hotwireの学習についての備忘録です。
前回の記事の続きです。
今回は、主に「Turbo Streams」に絞った内容です。
前回からの修正
情報が少ないので、showを使う場面あるっけ?と思いまして、リンクを削除します
<tr>
<td>
<%= todo.id %>
</td>
<td>
<%= todo.content %>
</td>
<td>
<div class="d-flex justify-content-end">
- <%= link_to "Show", todo, class: "btn btn-sm btn-outline-primary me-2" %>
<%= link_to "edit", edit_todo_path(todo), class: "btn btn-sm btn-outline-primary me-2" %>
<%= button_to "Destroy", todo, method: :delete, data: { turbo_confirm: '本当に削除していいですか?' } , class: "btn btn-sm btn-outline-danger" %>
</div>
</td>
</tr>
確認
変更画面
<tr id=<%= dom_id(todo) %> >
<td>
<%= todo.id %>
</td>
<td>
<%= todo.content %>
</td>
<td>
<div class="d-flex justify-content-end">
- <%= link_to "edit", edit_todo_path(todo), class: "btn btn-sm btn-outline-primary me-2" %>
+ <%= link_to "edit", edit_todo_path(todo), class: "btn btn-sm btn-outline-primary me-2" , method: :get, data: { turbo_stream: true } %>
<%= button_to "Destroy", todo, method: :delete, data: { turbo_confirm: '本当に削除していいですか?' } , class: "btn btn-sm btn-outline-danger" %>
</div>
</td>
</tr>
本当はTurbo Framesで置き換えたい気持ちもあったのですが、editのリンクを無理やりTurbo Streams化します
確認

ボタンを押すとコンソールログに、Turbo Streams化されてることが表示されてるのが分かります。
<tr id="<%= dom_id(todo) %>" >
<td>
<%= todo.id %>
</td>
<td>
<%= form_with(model: todo) do |form| %>
<div class="input-group">
<%= form.text_field :content , class: "form-control" %>
<%= form.submit '更新' , class: "btn btn-outline-secondary" %>
</div>
<% end %>
</td>
<td>
</td>
</tr>
<%= turbo_stream.replace dom_id(@todo) do %>
<%= render partial: 'todos/edit_form', locals: { todo: @todo } %>
<% end %>
確認

Turbo Streamsによって置換がされてるのが分かります
コンソールを確認するとedit.turbo_stream.erbが動いたあと、_edit_form.html.erbを描写してるのが分かります。
元の画面に戻す(showを利用)
変更画面を最初の表の状態に戻す処理を作成します
<tr id=<%= dom_id(todo) %> >
<td>
<%= todo.id %>
</td>
<td>
<%= form_with(model: todo) do |form| %>
<div class="input-group">
<%= form.text_field :content , class:"form-control" %>
<%= form.submit '更新' , class:"btn btn-outline-secondary" %>
</div>
<% end %>
</td>
<td>
+ <div class="d-flex justify-content-end">
+ <%= link_to "戻る", todo_path(todo), class: "btn btn-sm btn-outline-primary me-2" , method: :get, data: { turbo_stream: true } %>
+ </div>
</td>
</tr>
<%= turbo_stream.replace dom_id(@todo) do %>
<%= render partial: 'todos/todo', locals: { todo: @todo } %>
<% end %>
確認

コンソールログでも、showのturbo_streamが動いてるのが分かります
更新処理
def update
- respond_to do |format|
- if @todo.update(todo_params)
- format.html { redirect_to todo_url(@todo), notice: "Todo was successfully updated." }
- format.json { render :show, status: :ok, location: @todo }
- else
- format.html { render :edit, status: :unprocessable_entity }
- format.json { render json: @todo.errors, status: :unprocessable_entity }
- end
- end
+ unless @todo.update(todo_params)
+ render :edit, status: :unprocessable_entity
+ end
end
<%= turbo_stream.replace dom_id(@todo) do %>
<%= render partial: 'todos/todo', locals: { todo: @todo } %>
<% end %>
確認

コンソールログでも、updateのturbo_streamが動いてるのが分かります
削除処理
def destroy
@todo.destroy!
- respond_to do |format|
- format.html { redirect_to todos_url, notice: "Todo was successfully destroyed." }
- format.json { head :no_content }
- end
end
<%= turbo_stream.remove dom_id(@todo) do %>
<% end %>
確認

コンソールログで、destroyのturbo_streamが動いてるのが分かります
新規登録画面
<%= form_with(model: todo) do |form| %>
<div class="input-group">
<%= form.text_field :content , class:"form-control" %>
<%= form.submit '登録' , class:"btn btn-outline-secondary" %>
</div>
<% end %>
<h1>Todos</h1>
- <%= link_to "New todo", new_todo_path %>
+ <%= render "form", todo: Todo.new %>
+ <hr>
<%= turbo_frame_tag "todos-table" do %>
<table class="table table-bordered mt-2">
<thead>
<td>ID</td>
<td>Content</td>
<td></td>
</thead>
<tbody id="todos_tbody">
<%= render partial: 'todo', collection: @todos, cached: true %>
</tbody>
</table>
<%= paginate @todos, theme: 'twitter-bootstrap-4'%>
<% end %>
def create
@todo = Todo.new(todo_params)
- respond_to do |format|
- if @todo.save
- format.html { redirect_to todo_url(@todo), notice: "Todo was successfully created." }
- format.json { render :show, status: :created, location: @todo }
- else
- format.html { render :new, status: :unprocessable_entity }
- format.json { render json: @todo.errors, status: :unprocessable_entity }
- end
- end
+ @todo.save
end
<%= turbo_stream.append "todos_tbody" do %>
<%= render partial: 'todos/todo', locals: { todo: @todo } %>
<% end %>
確認

コンソールログで、createのturbo_streamが動いてるのが分かります
新規登録フォームの修正
登録後、新規登録フォームの値がそのままだったため、修正
<h1>Todos</h1>
- <%= render "form", todo: Todo.new %>
+ <div id="todo-new">
+ <%= render "form", todo: Todo.new %>
+ </div>
<hr>
<%= turbo_frame_tag "todos-table" do %>
<table class="table table-bordered mt-2">
<thead>
<td>ID</td>
<td>Content</td>
<td></td>
</thead>
<tbody id="todos_tbody">
<%= render partial: 'todo', collection: @todos, cached: true %>
</tbody>
</table>
<%= paginate @todos, theme: 'twitter-bootstrap-4'%>
<% end %>
<%= turbo_stream.append "todos_tbody" do %>
<%= render partial: 'todos/todo', locals: { todo: @todo } %>
<% end %>
+ <%= turbo_stream.update "todo-new" do %>
+ <%= render partial: "todos/form", locals: { todo: Todo.new } %>
+ <% end %>
createが動いたあと、入力フォームを初期化する処理を追加します
確認

コンソールログで、createのturbo_streamが動き、_form.html.erbを再描写してるのが分かります
完了処理
Rails.application.routes.draw do
- resources :todos
+ resources :todos do
+ post :fix, on: :member
+ end
get 'top/index'
root 'top#index' # ←この行を修正
# Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html
# Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500.
# Can be used by load balancers and uptime monitors to verify that the app is live.
get "up" => "rails/health#show", as: :rails_health_check
# Defines the root path route ("/")
# root "posts#index"
end
class TodosController < ApplicationController
- before_action :set_todo, only: %i[show edit update destroy]
+ before_action :set_todo, only: %i[show edit update destroy fix]
...
+ def fix
+ if @todo.complete == true
+ @todo.update(complete: false)
+ else
+ @todo.update(complete: true)
+ end
+ end
...
end
<%= turbo_stream.replace dom_id(@todo) do %>
<%= render partial: 'todos/todo', locals: { todo: @todo } %>
<% end %>
- <tr id=<%= dom_id(todo) %> >
+ <tr id=<%= dom_id(todo) %> class='<%= todo.complete ? "table-secondary" : "" %>'>
<td>
<%= todo.id %>
</td>
<td>
<%= todo.content %>
</td>
<td>
<div class="d-flex justify-content-end">
+ <%= link_to "fix", fix_todo_path(todo), class: "btn btn-sm btn-outline-primary me-2" , data: { "turbo-method": :post } %>
<%= link_to "edit", edit_todo_path(todo), class: "btn btn-sm btn-outline-primary me-2" , method: :get, data: { turbo_stream: true } %>
<%= button_to "Destroy", todo, method: :delete, data: { turbo_confirm: '本当に削除していいですか?' } , class: "btn btn-sm btn-outline-danger" %>
</div>
</td>
</tr>
確認

fixボタンが押されたら完了とし、行が塗られるようにしました。
また、再度fixボタンが押されたらキャンセルされるようにしています

コンソールログで、fixのturbo_streamが動いてるのが分かります
レコード更新が走ってるせいか、少し処理に時間がかかっています
さいごに
hotwireのみでtodoリストを作成してみましたが、予想以上に描写速度が速く、意外と使えるのでは?と思っています。(今回はvalidateを設けてないです)
コチラにhotwireについての知見が色々載っていましたので、とても勉強になりました(ありがとうございます)
今回bootstrapを使ってたので、tailwindを使ったバージョンだったり、他のパターン(esbuildなど)でも色々試してみたいと思います






