こんにちは、かつコーチです。
これまでルーティング・ActiveRecord・バリデーション・アソシエーション・Service Objectと、Railsの要素を1つずつ解説してきました。
今回はその総まとめとして、シンプルなTodoアプリを実際に手を動かしながら作っていきます。
一覧表示・作成・完了・削除という基本のCRUD操作を、これまで学んだベストプラクティスを織り込みながら実装します。
実装手順
プロジェクトとモデルの作成
まずは新規プロジェクトを作成し、Todoモデルを生成します。
rails new todo_app
cd todo_app
rails generate model Todo title:string done:boolean
生成されたマイグレーションに、done列のデフォルト値とnull制約を追加しておきます。
# db/migrate/xxxxxx_create_todos.rb
class CreateTodos < ActiveRecord::Migration[8.0]
def change
create_table :todos do |t|
t.string :title, null: false
t.boolean :done, null: false, default: false
t.timestamps
end
end
end
rails db:migrate
バリデーションとスコープの追加
モデルには、タイトルの必須チェックと、未完了/完了を絞り込むスコープを追加します。
# app/models/todo.rb
class Todo < ApplicationRecord
validates :title, presence: true, length: { maximum: 100 }
scope :pending, -> { where(done: false) }
scope :completed, -> { where(done: true) }
scope :recent_first, -> { order(created_at: :desc) }
end
length: { maximum: 100 }を入れておくことで、想定外に長いタイトルが登録されるのを防げます。
ルーティングとコントローラの実装
一覧・作成・完了切り替え・削除に必要なルーティングを定義します。
# config/routes.rb
Rails.application.routes.draw do
root "todos#index"
resources :todos, only: %i[index create destroy] do
member do
patch :toggle
end
end
end
resourcesにtoggleのような独自アクションを足す場合は、member do ... endブロックの中に書くのが定石です。
続いてコントローラを実装します。
# app/controllers/todos_controller.rb
class TodosController < ApplicationController
def index
@todo = Todo.new
@todos = Todo.recent_first
end
def create
@todo = Todo.new(todo_params)
if @todo.save
redirect_to root_path, notice: "Todoを追加しました"
else
@todos = Todo.recent_first
render :index, status: :unprocessable_entity
end
end
def toggle
todo = Todo.find(params[:id])
todo.update!(done: !todo.done)
redirect_to root_path
end
def destroy
Todo.find(params[:id]).destroy
redirect_to root_path, notice: "Todoを削除しました"
end
private
def todo_params
params.require(:todo).permit(:title)
end
end
createが失敗したときに@todosを再セットしてからrenderしているのがポイントです。
これを忘れると、バリデーションエラー時に一覧が空のまま表示されてしまいます。
ビューの実装
最後に、一覧表示と入力フォームを1画面にまとめたビューを作成します。
<%# app/views/todos/index.html.erb %>
<h1>Todoリスト</h1>
<%= form_with model: @todo, url: todos_path do |f| %>
<% if @todo.errors.any? %>
<ul class="errors">
<% @todo.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
<% end %>
<%= f.text_field :title, placeholder: "やることを入力" %>
<%= f.submit "追加" %>
<% end %>
<ul>
<% @todos.each do |todo| %>
<li class="<%= 'done' if todo.done? %>">
<%= todo.title %>
<%= button_to (todo.done? ? "未完了に戻す" : "完了にする"),
toggle_todo_path(todo), method: :patch %>
<%= button_to "削除", todo_path(todo), method: :delete,
data: { turbo_confirm: "本当に削除しますか?" } %>
</li>
<% end %>
</ul>
button_toにdata: { turbo_confirm: "..." }を指定すると、Rails 8標準のTurboが確認ダイアログを出してから削除リクエストを送ってくれます。
よくあるつまずきポイント・エラー対処
turbo_confirmが効かずに即削除されてしまう
私が実際にハマったのが、確認ダイアログなしで削除ボタンを押した瞬間に即削除されてしまった問題です。
❌ Before:古いRailsの書き方をそのまま使ってしまう
<%= button_to "削除", todo_path(todo), method: :delete,
data: { confirm: "本当に削除しますか?" } %>
Rails 6以前はdata: { confirm: "..." }で確認ダイアログが出ていましたが、Rails 8ではデフォルトのJavaScriptライブラリがUJS(jquery-ujs/rails-ujs)からTurboに変わっています。
古いconfirm属性はTurboに認識されず、確認なしでそのまま削除リクエストが送信されてしまいました。
✅ After:Turbo対応のturbo_confirmを使う
<%= button_to "削除", todo_path(todo), method: :delete,
data: { turbo_confirm: "本当に削除しますか?" } %>
data-turbo-confirm属性に変更するだけで、Turboが標準の確認ダイアログを表示してくれるようになりました。
Rails 8系のプロジェクトでボタンの確認ダイアログが効かない場合は、まずこの属性名の違いを疑うとよいです。
resources内のmemberルーティングでNoMethodError
もう1つ、独自アクションのルーティングでつまずいた経験があります。
❌ Before:memberブロックの外にpatchを書いてしまう
resources :todos, only: %i[index create destroy]
patch "toggle", to: "todos#toggle"
このルーティングでビューからtoggle_todo_path(todo)を呼び出したところ、次のエラーが出ました。
NoMethodError: undefined method `toggle_todo_path' for an instance of #<Class:...>
patch "toggle", to: "todos#toggle"のようにトップレベルで定義すると、toggle_pathというルーティングヘルパーにはなっても、個々のTodoに紐づくtoggle_todo_pathにはなりません。
✅ After:resourcesのmemberブロック内に定義する
resources :todos, only: %i[index create destroy] do
member do
patch :toggle
end
end
memberブロックの中に書くことで、/todos/:id/toggleというURLと、IDを引数に取るtoggle_todo_path(todo)ヘルパーが自動生成されます。
個々のレコードに対する操作を追加する場合は、必ずmemberブロックを使うのが正解です。
まとめ
この記事のポイント
- モデル・バリデーション・スコープ・ルーティング・コントローラ・ビューを組み合わせてシンプルなTodoアプリを実装した
create失敗時は@todosを再セットしてからrenderしないと一覧が空になる- Rails 8ではTurboが標準のため、確認ダイアログは
data-confirmではなくdata-turbo-confirmを使う - 個々のレコードに対する独自アクションは、
resourcesのmemberブロック内に定義する
次に読むべき記事
→ 次の記事:Ruby on RailsとNext.js、どちらを選ぶべきか比較検証
タグ: Ruby on Rails, 上級者向け, 実践プロジェクト