はじめに
この記事は、hotwireの学習についての備忘録です。
前回の記事の続き(importmap + bootstrap)です。
また、記事が長くなると思われるので、数回に分割する予定です
今回は、主に「Turbo Frames」に絞った内容です。
hotwireとは
他の記事に詳細が書かれてるので、この場ではさらっとした説明です。
HotwireはRails7からRailsのフロントエンドのデフォルトとなった技術です。
また、複数の機能を統合した相称の名前となります。
- Hotwire
- Turbo
- Turbo Drive ・・・画面描写を早くする
- Turbo Frames ・・・部分差し替えを行う(主にgetで利用)
- Turbo Streams ・・・部分差し替えを行う(主にget以外で利用)
- Stimulus ・・・rails専用のJS
- Turbo
todo作成
基本画面を作成
コマンド
docker compose run --rm app bundle exec rails generate scaffold todo content:string complete:boolean
テストデータを登録するseedファイルを準備します
app/db/seeds.rb
Todo.create(
[
{content: "柳田 政年" , complete: 0},
{content: "佐野 護" , complete: 0},
{content: "小松 純典" , complete: 0},
{content: "堀川 陽子" , complete: 0},
{content: "堀川 武士" , complete: 0},
{content: "松原 竹義" , complete: 0},
{content: "山崎 詔勝" , complete: 0},
{content: "大内 武俊" , complete: 0},
{content: "藤村 広重" , complete: 0},
{content: "白川 芳尚" , complete: 0},
{content: "大塚 美砂" , complete: 0},
{content: "茂木 幸郎" , complete: 0},
{content: "辻 和広" , complete: 0},
{content: "東 茂俊" , complete: 0},
{content: "豊田 世弥" , complete: 0},
{content: "笠原 永寿" , complete: 0},
{content: "浅井 陽一郎" , complete: 0},
{content: "吉沢 祐子" , complete: 0},
{content: "中野 貢太郎" , complete: 0},
{content: "奥野 貢太郎" , complete: 0},
{content: "竹内 芳伸" , complete: 0},
{content: "花田 十四夫" , complete: 0},
{content: "西川 重文" , complete: 0},
{content: "後藤 一樹" , complete: 0},
{content: "古田 早葉子" , complete: 0},
{content: "坂井 裕仁" , complete: 0},
{content: "中野 昌光" , complete: 0},
{content: "堀江 茂也" , complete: 0},
{content: "小野 理江" , complete: 0},
{content: "徳田 真里" , complete: 0},
{content: "古谷 督彦" , complete: 0},
{content: "田代 健生" , complete: 0},
{content: "古谷 常一" , complete: 0},
{content: "藤村 克三" , complete: 0},
{content: "横山 琢司" , complete: 0},
{content: "浅野 元信" , complete: 0},
{content: "古谷 政年" , complete: 0},
{content: "小田 三佐男" , complete: 0},
{content: "村瀬 尭道" , complete: 0},
{content: "武井 恵治" , complete: 0},
{content: "土屋 孝次" , complete: 0},
{content: "大場 令" , complete: 0},
{content: "久保 友良" , complete: 0},
{content: "大平 美津枝" , complete: 0},
{content: "山岸 時司" , complete: 0},
{content: "下田 美里" , complete: 0},
{content: "岡 敬志" , complete: 0},
{content: "森田 道和" , complete: 0},
{content: "白川 恵" , complete: 0},
{content: "松本 俊憲" , complete: 0},
{content: "秋田 秋代" , complete: 0},
{content: "安井 勝英" , complete: 0},
{content: "藤川 敬生" , complete: 0},
{content: "谷川 元三" , complete: 0},
{content: "山本 興亜" , complete: 0},
{content: "小松 良司" , complete: 0},
{content: "竹下 勤" , complete: 0},
{content: "新田 酉蔵" , complete: 0},
{content: "竹本 幸四郎" , complete: 0},
{content: "野崎 慶太郎" , complete: 0},
{content: "古谷 朋子" , complete: 0},
{content: "大森 裕子" , complete: 0},
{content: "荻野 生三" , complete: 0},
{content: "丹羽 重三郎" , complete: 0},
{content: "坂田 安民" , complete: 0},
{content: "堀 十四夫" , complete: 0},
{content: "奥山 義将" , complete: 0},
{content: "小出 成男" , complete: 0},
{content: "馬場 育雄" , complete: 0},
{content: "高瀬 夕子" , complete: 0},
{content: "福原 和久" , complete: 0},
{content: "大沢 允彦" , complete: 0},
{content: "本間 多栄子" , complete: 0},
{content: "細川 敬" , complete: 0},
{content: "植田 麻紀" , complete: 0},
{content: "千田 三重子" , complete: 0},
{content: "緒方 裕恵" , complete: 0},
{content: "福岡 美緒" , complete: 0},
{content: "野田 常次" , complete: 0},
{content: "藤川 真樹" , complete: 0},
{content: "横井 紀子" , complete: 0},
{content: "堀内 美里" , complete: 0},
{content: "本間 十四夫" , complete: 0},
{content: "松山 勝行" , complete: 0},
{content: "福永 優紀" , complete: 0},
{content: "関根 洋昌" , complete: 0},
{content: "富田 忠之" , complete: 0},
{content: "平山 勝司" , complete: 0},
{content: "田上 恵治" , complete: 0},
{content: "増田 雅則" , complete: 0},
{content: "古谷 俊郎" , complete: 0},
{content: "安部 善二" , complete: 0},
{content: "土田 十四夫" , complete: 0},
{content: "池上 玲子" , complete: 0},
{content: "福井 純隆" , complete: 0},
{content: "大石 梨華" , complete: 0},
{content: "尾崎 聡" , complete: 0},
{content: "島田 利克" , complete: 0},
{content: "堀内 唯" , complete: 0},
{content: "福本 美津枝" , complete: 0},
]
)
DB構築及びテストデータを登録
docker compose run --rm app bundle exec rails db:drop db:create db:migrate db:seed
全体的に淵ギリギリに表示されるのは微妙なので、少しマージンを設定しときます
app/app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title>App</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
<%= javascript_importmap_tags %>
</head>
- <body>
+ <body class="px-4">
<%= yield %>
</body>
</html>
トップページに、作成したtodoへのリンクを配置しておきます
app/app/views/top/index.html.erb
<h1>Top#index</h1>
<p>Find me in app/views/top/index.html.erb</p>
<button type="button" class="btn btn-primary">Primary</button>
<button type="button" class="btn btn-secondary">Secondary</button>
<button type="button" class="btn btn-success">Success</button>
<button type="button" class="btn btn-danger">Danger</button>
<button type="button" class="btn btn-warning">Warning</button>
<button type="button" class="btn btn-info">Info</button>
<button type="button" class="btn btn-dark">Dark</button>
<button type="button" class="btn btn-link">Link</button>
<div class="btn-group">
<button type="button" class="btn btn-danger dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
Action
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#">Action</a></li>
<li><a class="dropdown-item" href="#">Another action</a></li>
<li><a class="dropdown-item" href="#">Something else here</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#">Separated link</a></li>
</ul>
</div>
+ <hr>
+ <%= link_to 'todo', todos_path, class: 'btn btn-primary' %>
indexの修正
画面は、昔のテーブル形式にしておきます
app/app/views/todos/index.html.erb
<h1>Todos</h1>
<%= link_to "New todo", new_todo_path %>
<table class="table table-bordered mt-2">
<thead>
<td>ID</td>
<td>Content</td>
<td></td>
</thead>
<tbody>
<% @todos.each do |todo| %>
<%= render todo %>
<% end %>
</tbody>
</table>
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>
画面確認
ページネートを追加
app/Gemfile
gem "kaminari"
gem 'bootstrap4-kaminari-views'
コマンド
docker compose build --no-cache
docker compose run --rm app bundle install
app/app/controllers/todos_controller.rb
# GET /todos or /todos.json
def index
- @todos = Todo.all
+ @todos = Todo.all.page(params[:page]).per(10)
end
app/app/views/todos/index.html.erb
<h1>Todos</h1>
<%= link_to "New todo", new_todo_path %>
<table class="table table-bordered mt-2">
<thead>
<td>ID</td>
<td>Content</td>
<td></td>
</thead>
<tbody>
<% @todos.each do |todo| %>
<%= render todo %>
<% end %>
</tbody>
</table>
+ <%= paginate @todos, theme: 'twitter-bootstrap-4'%>
画面確認
コンソールでは、こんな感じに表示されており、平均速度35~45msという感じでした
コレクションキャッシュを使ってみる
app/app/views/todos/index.html.erb
<h1>Todos</h1>
<%= link_to "New todo", new_todo_path %>
<table class="table table-bordered mt-2">
<thead>
<td>ID</td>
<td>Content</td>
<td></td>
</thead>
<tbody>
- <% @todos.each do |todo| %>
- <%= render todo %>
- <% end %>
+ <%= render partial: 'todo', collection: @todos, cached: true %>
</tbody>
</table>
<%= paginate @todos, theme: 'twitter-bootstrap-4'%>
画面確認
レンダリングが少し早くなって、平均速度25~35msという感じでした
Turbo Frames
app/app/views/todos/index.html.erb
<h1>Todos</h1>
<%= link_to "New todo", new_todo_path %>
+ <%= turbo_frame_tag "todos-table" do %>
<table class="table table-bordered mt-2">
<thead>
<td>ID</td>
<td>Content</td>
<td></td>
</thead>
<tbody>
<%= render partial: 'todo', collection: @todos, cached: true %>
</tbody>
</table>
<%= paginate @todos, theme: 'twitter-bootstrap-4'%>
+ <% end %>
画面確認
最後に
今回はここまでにします。
Turbo Framesを使うことで、簡単にSPA化できてることに驚いています。
次回は、引き続きTODOの更新関係を手掛け、Turbo Streamsについて学んでいきます