こんにちはエンジニアのフンです。
私はフロントエンドの開発がメインですが、最近はRailsのバックエンドの開発を行うことも増えてきました。
今回はTurbo Framesについて紹介します。
Turbo Framesは、Hotwireの一部であり、JavaScriptに過度に依存せずにRailsアプリにインタラクティブな機能を追加する優れた方法です。
Rails Turbo Framesを使用して基本的なTo-Doリストを作成し、ページ全体のリロードなしでUIのスムーズかつ迅速な更新を実現する方法を紹介します。
必要なもの
- Ruby on Rails(バージョン6.1以上)がインストールされていること
- 基本的なRailsのCRUD操作の知識
新しいRailsプロジェクトのセットアップ
まず、以下のコマンドで新しいRailsアプリを作成します。
1rails new todo-turbo
2cd todo-turbo
To-Doモデルの生成
次に、To-Doを保存するための簡単なモデルを作成します。
1rails g model Todo title:string completed:boolean
2rails db:migrate
これにより、タイトルと完了ステータスを持つTodoモデルが作成されます。
コントローラーとルーティングの設定
To-DoのCRUD操作を管理するコントローラーが必要です。
1rails g controller Todos index new create edit update destroy
次にルーティングを設定します。config/routes.rb
を開いて、以下を追加してください。
1Rails.application.routes.draw do
2 root 'todos#index'
3 resources :todos, except: :show
4end
これで、To-Doリソースのルートがコントローラーにマップされ、To-Doの作成、編集、削除などのアクションの管理が容易になります。
Turboを活用したビューの作成
次に、Turbo Framesを使用して動的に更新されるビューを作成します。まずは、index
ビューを作成しましょう。(app/views/todos/index.html.erb
)
1<h1>To-Do List</h1>
2<!-- To-Doリスト用のTurbo Frame -->
3<div id="todos-list">
4 <%= turbo_frame_tag"todos" do %>
5 <%= render@todos %>
6 <% end %>
7</div>
8<!-- Turbo Frame内で表示される「新しいTo-Do」フォームをトリガーするリンク -->
9<%= link_to'New To-Do', new_todo_path,data: {turbo_frame: "new_todo_form" },class: "button"%>
10
11<!-- 新規/編集用To-Doフォーム用のTurbo Frame -->
12<%= turbo_frame_tag"new_todo_form" do %>
13<% end %>
このコードでは、turbo_frame_tag
を使用してTo-Doリストをラップしています。To-Doの作成や更新を行う際には、ページ全体をリロードせずにこの部分だけをリフレッシュします。
To-Doの部分ビューをレンダリング
続いて、各To-Doをリスト表示するための部分ビューをレンダリングします。(app/views/todos/_todo.html.erb
)
1<div id="todo_<%= todo.id%>">
2 <p><%= todo.title%><%= todo.completed ?'(Completed)' :'(Pending)' %></p>
3 <%= link_to'Edit', edit_todo_path(todo),data: {turbo_frame: "todo_#{todo.id}" },class: "button"%>
4 <%= link_to'Delete', todo_path(todo),data: {turbo_method: :delete,turbo_confirm: "Are you sure?",turbo_frame: "todos" },class: "button"%>
5</div>
ここでは、Turbo Framesを使用して個々の項目(todo_<%= todo.id %>
)を部分的に更新可能にしています。編集や削除時にTo-Doリストの部分的な更新を可能にします。
新規/編集用のフォームとTurbo Framesの追加
To-Doを作成および編集するためのフォームを作成します。このフォームは部分ビューで再利用できます。(app/views/todos/_form.html.erb
)
1<%= form_withmodel: todo,data: {turbo_frame: "todos" },local: false do |form| %>
2 <div>
3 <%= form.label:title %>
4 <%= form.text_field:title %>
5 </div>
6 <div>
7 <%= form.label:completed %>
8 <%= form.check_box:completed %>
9 </div>
10 <%= form.submit%>
11<% end %>
作成、編集、および削除時のTurbo Framesの更新
コントローラーで各アクションが必要な部分ビューをレンダリングし、スムーズに更新されるようにします。特にcreate
やupdate
メソッド内では、Turbo Frame内で更新されたTo-Doリストをレンダリングします。
1class TodosController < ApplicationController
2 before_action :set_todo, only: [:edit, :update, :destroy]
3
4 def index
5 @todos = Todo.all
6 end
7
8 def new
9 @todo = Todo.new
10 respond_to do |format|
11 format.html # Non-Turboリクエストのフォールバック
12 format.turbo_stream do
13 render turbo_stream: turbo_stream.replace('new_todo_form', partial: 'todos/form', locals: { todo: @todo })
14 end
15 end
16 end
17
18 def create
19 @todo = Todo.new(todo_params)
20 if @todo.save
21 respond_to do |format|
22 format.html { redirect_to todos_path, notice: 'To-Do was successfully created.' }
23 format.turbo_stream do
24 render turbo_stream: [
25 turbo_stream.append('todos', partial: 'todos/todo', locals: { todo: @todo }), # 新しいtodoをリストに追加
26 turbo_stream.replace('new_todo_form', partial: 'todos/form', locals: { todo: Todo.new }) # フォームを初期化
27 ]
28 end
29 end
30 else
31 respond_to do |format|
32 format.html { render :new }
33 format.turbo_stream do
34 render turbo_stream: turbo_stream.replace('new_todo_form', partial: 'todos/form', locals: { todo: @todo }) # エラーを対処
35 end
36 end
37 end
38 end
39
40 def edit; end
41
42 def update
43 if @todo.update(todo_params)
44 respond_to do |format|
45 format.turbo_stream { render partial: "todos/todo", locals: { todo: @todo } }
46 end
47 else
48 render :edit
49 end
50 end
51
52 def destroy
53 @todo.destroy
54 respond_to do |format|
55 format.html { redirect_to todos_path, notice: 'To-Do was successfully deleted.' }
56 format.turbo_stream do
57 render turbo_stream: turbo_stream.remove("todo_#{@todo.id}")
58 end
59 end
60 end
61
62 private
63
64 def set_todo
65 @todo = Todo.find(params[:id])
66 end
67
68 def todo_params
69 params.require(:todo).permit(:title, :completed)
70 end
71end
respond_to
とformat.turbo_stream
を使用することで、Turbo Framesはページ全体のリロードを引き起こすことなく、関連するページのセクションのみを更新します。
Turbo Framesを使用することで、RailsでJavaScriptを最小限に抑えた、シンプルでありながら動的なTo-Doリストを構築することができました。
Turbo Framesは、ページの必要な部分のみをリフレッシュすることにより、パフォーマンスを向上させ、ユーザー体験を向上させることができます。
みなさんもTurbo Rramesを使って簡単なアプリをぜひ作ってみてください!
(フン)
Simplifying SPAs- Build a Dynamic To-Do App Using Rails Turbo Frames
Turbo Frames, part of Hotwire, are a great way to add interactive features to a Rails app without relying heavily on JavaScript. In this post, we’ll walk through building a basic To-Do list using Rails Turbo Frames, ensuring smooth, fast updates to our UI without full page reloads.
What You’ll Need
- Ruby on Rails installed (6.1+)
- Basic knowledge of Rails CRUD
Set up a New Rails Project
First, start by creating a new Rails app:
rails new todo-turbo cd todo-turbo
Generate To-Do Model
rails g model Todo title:string completed:boolean rails db:migrate
Set up the Controller and Routes
rails g controller Todos index new create edit update destroy
Rails.application.routes.draw do root 'todos#index' resources :todos, except: :show end
Building the Turbo-Powered Views
<h1>To-Do List</h1> <!-- Turbo Frame for To-Do List --> <div id="todos-list"> <%=turbo_frame_tag"todos"do%> <%=render @todos%> <%end%> </div> <!-- Link to trigger the 'New To-Do' form inside a turbo frame --> <%= link_to 'New To-Do', new_todo_path, data: { turbo_frame: "new_todo_form" }, class: "button" %> <!-- Turbo Frame for New/Edit To-Do Form --> <%= turbo_frame_tag "new_todo_form" do %> <% end %>
Rendering Partial for Todos
<div id="todo_<%= todo.id %>"> <p><%=todo.title%><%=todo.completed ? '(Completed)' : '(Pending)'%></p> <%=link_to 'Edit', edit_todo_path(todo), data: { turbo_frame:"todo_#{todo.id}" }, class:"button"%> <%=link_to 'Delete', todo_path(todo), data: { turbo_method: :delete, turbo_confirm:"Are you sure?", turbo_frame:"todos" }, class:"button"%> </div>
Add Forms and Turbo Frames for New/Edit
<%= form_with model: todo, data: { turbo_frame: "todos" }, local: false do |form| %> <div> <%= form.label :title %> <%= form.text_field :title %> </div> <div> <%= form.label :completed %> <%= form.check_box :completed %> </div> <%= form.submit %> <% end %>
Updating Turbo Frames on Create, Edit, and Delete
class TodosController < ApplicationController before_action:set_todo, only: [:edit, :update, :destroy] def index @todos=Todo.all end def new @todo=Todo.new respond_todo |format| format.html# Fallback for non-Turbo requests format.turbo_streamdo render turbo_stream:turbo_stream.replace('new_todo_form', partial:'todos/form', locals: { todo:@todo }) end end end def create @todo=Todo.new(todo_params) if @todo.save respond_todo |format| format.html { redirect_totodos_path, notice:'To-Do was successfully created.' } format.turbo_stream do render turbo_stream: [ turbo_stream.append('todos', partial:'todos/todo', locals: { todo:@todo }), # Append new todo to the list turbo_stream.replace('new_todo_form', partial:'todos/form', locals: { todo:Todo.new }) # Pass hide variable to form] end end else respond_todo |format| format.html { render:new } format.turbo_stream do render turbo_stream:turbo_stream.replace('new_todo_form', partial:'todos/form', locals: { todo:@todo }) # Pass hide variable as false end end end end def edit; end def update if @todo.update(todo_params) respond_todo |format| format.turbo_stream { renderpartial:"todos/todo", locals: { todo:@todo } } end else render:edit end end def destroy @todo=Todo.find(params[:id]) @todo.destroy respond_todo |format| format.html { redirect_totodos_path, notice:'To-Do was successfully deleted.' } format.turbo_stream do render turbo_stream:turbo_stream.remove("todo_#{@todo.id}") end end end private def set_todo @todo=Todo.find(params[:id]) end def todo_params params.require(:todo).permit(:title, :completed) end end
By using respond_to and format.turbo_stream, we ensure that our Turbo Frames only update the relevant section of the page without causing a full reload.
Using Turbo Frames in Rails, we’ve built a simple yet dynamic To-Do list with minimal JavaScript.
Turbo Frames allow us to create a more interactive experience by refreshing only the necessary parts of the page—boosting performance and enhancing user experience.
(Hung)