6
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

hotwireの学習②

6
Posted at

はじめに

この記事は、hotwireの学習についての備忘録です。

前回の記事の続きです。
今回は、主に「Turbo Streams」に絞った内容です。

前回からの修正

情報が少ないので、showを使う場面あるっけ?と思いまして、リンクを削除します

app/app/views/todos/_todo.html.erb
<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>

確認

image.png

変更画面

app/app/views/todos/_todo.html.erb
<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化します

確認

image.png
ボタンを押すとコンソールログに、Turbo Streams化されてることが表示されてるのが分かります。

app/app/views/todos/_edit_form.html.erb
<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>
app/app/views/todos/edit.turbo_stream.erb
<%= turbo_stream.replace dom_id(@todo) do %>
  <%= render partial: 'todos/edit_form', locals: { todo: @todo } %>
<% end %>

確認

1709020036.gif
Turbo Streamsによって置換がされてるのが分かります

image.png

コンソールを確認するとedit.turbo_stream.erbが動いたあと、_edit_form.html.erbを描写してるのが分かります。

元の画面に戻す(showを利用)

変更画面を最初の表の状態に戻す処理を作成します

app/app/views/todos/_edit_form.html.erb
<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>
app/app/views/todos/show.turbo_stream.erb
<%= turbo_stream.replace dom_id(@todo) do %>
  <%= render partial: 'todos/todo', locals: { todo: @todo } %>
<% end %>

確認

1709021020.gif
戻るボタンを押すことで、元の状態に戻せることが確認できます

image.png
コンソールログでも、showのturbo_streamが動いてるのが分かります

更新処理

app/app/controllers/todos_controller.rb
  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
app/app/views/todos/update.turbo_stream.erb
<%= turbo_stream.replace dom_id(@todo) do %>
  <%= render partial: 'todos/todo', locals: { todo: @todo } %>
<% end %>

確認

1709021535.gif

image.png
コンソールログでも、updateのturbo_streamが動いてるのが分かります

削除処理

app/app/controllers/todos_controller.rb
  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
app/app/views/todos/destroy.turbo_stream.erb
<%= turbo_stream.remove dom_id(@todo) do %>
<% end %>

確認

l_6814799_112_8b4e2db53eddd518912d6d7fdf5fa20a.png

image.png
コンソールログで、destroyのturbo_streamが動いてるのが分かります

新規登録画面

app/app/views/todos/_form.html.erb
<%= 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 %>
app/app/views/todos/index.html.erb
<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 %>
app/app/controllers/todos_controller.rb
  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
app/app/views/todos/create.turbo_stream.erb
<%= turbo_stream.append  "todos_tbody" do %>
  <%= render partial: 'todos/todo', locals: { todo: @todo } %>
<% end %>

確認

1709022873.gif

image.png
コンソールログで、createのturbo_streamが動いてるのが分かります

新規登録フォームの修正

登録後、新規登録フォームの値がそのままだったため、修正

app/app/views/todos/index.html.erb
<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 %>
app/app/views/todos/create.turbo_stream.erb
<%= 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が動いたあと、入力フォームを初期化する処理を追加します

確認

1709023381.gif

image.png
コンソールログで、createのturbo_streamが動き、_form.html.erbを再描写してるのが分かります

完了処理

app/config/routes.rb
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
app/app/controllers/todos_controller.rb
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
fix.turbo_stream.erb
<%= turbo_stream.replace dom_id(@todo) do %>
  <%= render partial: 'todos/todo', locals: { todo: @todo } %>
<% end %>
app/app/views/todos/_todo.html.erb
- <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>

確認

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

image.png
コンソールログで、fixのturbo_streamが動いてるのが分かります
レコード更新が走ってるせいか、少し処理に時間がかかっています

さいごに

hotwireのみでtodoリストを作成してみましたが、予想以上に描写速度が速く、意外と使えるのでは?と思っています。(今回はvalidateを設けてないです)
コチラにhotwireについての知見が色々載っていましたので、とても勉強になりました(ありがとうございます)

今回bootstrapを使ってたので、tailwindを使ったバージョンだったり、他のパターン(esbuildなど)でも色々試してみたいと思います

6
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?