Turbo Framesを使ってRailsでTo-Doアプリを作ってみよう

English follows Japanese. 英語の文章は後半にあります。

こんにちはエンジニアのフンです。

私はフロントエンドの開発がメインですが、最近は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の更新

コントローラーで各アクションが必要な部分ビューをレンダリングし、スムーズに更新されるようにします。特にcreateupdateメソッド内では、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_toformat.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

Create a simple model to store our to-dos:
rails g model Todo title:string completed:boolean
rails db:migrate
This will create a Todo model with title and completed attributes.

Set up the Controller and Routes

We need a controller to manage the CRUD operations for our to-dos:
rails g controller Todos index new create edit update destroy
Next, configure the routes. Open config/routes.rb and add the following:
Rails.application.routes.draw do
root 'todos#index'
resources :todos, except: :show
end
This will map the todos resource routes to our controller, making it easier to handle actions like creating, editing, and deleting to-dos.

Building the Turbo-Powered Views

Now, let’s create views that use Turbo Frames for dynamic updates. Start with the index view (app/views/todos/index.html.erb):
<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 %>
In this code, we use turbo_frame_tag to wrap the to-do list. When we perform actions like creating or updating to-dos, we’ll only refresh this part of the page instead of reloading everything.

Rendering Partial for Todos

Next, let’s render the partial to list each to-do (app/views/todos/_todo.html.erb):
<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>
Here, we use Turbo Frames for individual items (todo_<%= todo.id %>) and enable partial updates on the to-do list when editing or deleting items.

Add Forms and Turbo Frames for New/Edit

Create a form for both creating and editing todos. You can reuse the form partial (app/views/todos/_form.html.erb):
<%= 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

Make sure that in your controller, each action renders the necessary partials to allow for smooth updates. For example, in create and update methods, render the updated to-do list inside the Turbo Frame.
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)

採用情報

株式会社シェアウィズでは、新しいメンバーの募集を積極的に行っています。フルタイムの勤務から学生インターンまで、ご関心をお持ちの方はお気軽にコンタクトしてください!

採用ページへ

開発
ShareWis Blog(シェアウィズ ブログ)