はじめに
前回は
TaskのViewにBootstrap5を適用として
- bootstrap5
について学びました。
今回は
statusをEnumへ として
- バリデーション
- enum
について学びます。
では、はじめていきましょう。
1. バリデーション
最初に仕込みとなりますが、最低限のバリデーション設定をしておきます。
バリデーション
バリデーションとはデーターの入力に対して制限を掛ける事で、空入力や多すぎる文字を防いだり、不正な入力を弾く事ができる機能です。
データーの登録前にチェックしようという事ですね。
SQLのエラー
現在、データーを空投稿しようとすると、
このようなエラーが出ます。
これは、マイグレーションファイルを作った時にDBに「必須入力」の制約を入れている為です。
このエラーを防ぐために、モデルにバリデーションを書き入れておきます。
Model
バリデーションの記述は Model
に書いていきます。
現状 app/models/task.rb
は
app/models/task.rb【確認】
class Task < ApplicationRecord
end
現状 class
以外は何も記述がありません。
実は、モデルのCRUD操作やクエリ操作を行う為の小難しい記述は、継承元の ApplicationRecord
や、さらにその継承元に書かれていますので、モデル単体には該当のテーブルの操作に関するロジックのみを書いていく事ができます。
では、実際に書いてみましょう。
app/models/task.rb【追記】
class Task < ApplicationRecord
validates :title, presence: true, length: { minimum: 3, maximum: 20 }
validates :description, allow_blank: true, length: { minimum: 3, maximum: 500 }
validates :start_time, presence: true
end
このように validates
で宣言を行って、各カラムに対して制約を書いていきます。
presence: true
は必須入力となり、length
入力出来る文字数を制限しています。
allow_blank
は 空入力
の場合、他のバリデーションをスキップするという記述になります。
ここまで書いて、再び空投稿すると、
このようにエラーが表示できるようになります。
ただし、フォームも消えてしまいました。
これは View
側に表示されるフォームに条件式を入れていた為です。
バリデーションエラーが起こった場合、次の url
にリクエストしていないので、アクションは create
アクションにとどまっているためです。
ですので、View側の方にも対策をしておきましょう。
app/views/_form.html.erb【確認】
<div class="row">
<div class="col-md-6">
<%= form_with(model: task) do |form| %>
<% if task.errors.any? %>
<div style="color: red">
<h2><%= pluralize(task.errors.count, "error") %> prohibited this task from being saved:</h2>
<ul>
<% task.errors.each do |error| %>
<li><%= error.full_message %></li>
<% end %>
</ul>
</div>
<% end %>
<% if params[:action] == "new" || params[:action] == "create" %>
<div class="mb-3">
<%= form.label :title, style: "display: block" %>
<%= form.text_field :title, class: "form-control" %>
</div>
<div class="mb-3">
<%= form.label :description, style: "display: block" %>
<%= form.text_area :description, class: "form-control" %>
</div>
<div class="mb-3">
<%= form.label :start_time, style: "display: block" %>
<%= form.datetime_field :start_time, class: "form-control" %>
</div>
<div class="mb-3">
<%= form.label :end_time, style: "display: block" %>
<%= form.datetime_field :end_time, class: "form-control" %>
</div>
<% end %>
<% if params[:action] == "edit" %>
<div class="mb-3">
<%= form.label :status, style: "display: block" %>
<%= form.number_field :status, class: "form-control" %>
</div>
<% end %>
<div class="mb-3">
<%= form.submit class: "btn btn-primary" %>
</div>
<% end %>
</div>
</div>
<% if params[:action] == "new" %>
この部分を以下のように書き換えます。
<% if params[:action] == "new" || params[:action] == "create" %>
app/views/_form.html.erb【編集】
<div class="row">
<div class="col-md-6">
<%= form_with(model: task) do |form| %>
<% if task.errors.any? %>
<div style="color: red">
<h2><%= pluralize(task.errors.count, "error") %> prohibited this task from being saved:</h2>
<ul>
<% task.errors.each do |error| %>
<li><%= error.full_message %></li>
<% end %>
</ul>
</div>
<% end %>
<!-- この部分を書き換え -->
<% if params[:action] == "new" || params[:action] == "create" %>
<!-- この部分を書き換え -->
<div class="mb-3">
<%= form.label :title, style: "display: block" %>
<%= form.text_field :title, class: "form-control" %>
</div>
(以下略)
こうする事によって、フォームが消える現象を防ぐ事ができます。
2. Enumでステータスを管理する
次にステータスの表示です。
Enum
Enum
とは、列挙型
と言われ、Railsでフラグ管理等に用いる integer
型の数値を管理しやすい Symbol
型のkeyと Integer
型のvalue を持った Hash
にする事でメンテナンスをしやすくする仕組みです。
まずは、管理しにくい現状を確認してみましょう。
データーの初期化
新たにデーターが入っていない状態から始めたいので、一旦DBを初期化します。
todo_app % rails db:migrate:reset
と実行してDBを一旦作り直しましょう。
todo_app % rails db:migrate:reset
Dropped database 'storage/development.sqlite3'
Database 'storage/test.sqlite3' does not exist
Created database 'storage/development.sqlite3'
Created database 'storage/test.sqlite3'
== 20231027073408 CreateTasks: migrating ======================================
-- create_table(:tasks)
-> 0.0012s
== 20231027073408 CreateTasks: migrated (0.0013s) =============================
リセットできたようです。続いて、
todo_app % bin/dev
でサーバーを起動し、 localhost:3000
にアクセスします。
New task
からデーターを入力します。
表示の不便さ
登録出来たら、 show
アクションを読み込みますので、
このような画面となります。 url
も localhost:3000/tasks/1
となっています。
この Status
が 0
とされている所もDBの制約で、「デフォルト値を0」とするようになっている為です。
本当は、 0
を 未着手
としたい目論見があるのですが、現状では 0
って何?という感じになっています。全く意味がわかりませんね。
明らかに変換が必要なので、その手法として今回は Enum
を使っていきます。
フォームの不便さ
次に、このTaskの進捗を変更する場合、
Edit this task
を押下して、編集画面に遷移します。
こちらのフォームも実際の入力に耐えないものとなっていますね。
マイナスの値も自由に入れられていますので、これは改善の必要があります。
希望としては、セレクトボックス等で、 未着手
着手中
完了
などのいくつかのステータスを 選択
出来るようになれば良いですね。
実装の流れは、下記表のように変換されますが、
DB上のフラグの数値 | 数字を置き換える為のキー | キーを日本語化したもの |
---|---|---|
0 | :not_started | 未着手 |
1 | :in_progress | 進行中 |
2 | :closed | 完了 |
段階を追って変換していこうと思います。
Model
では、早速置き換えるために Enumの記述を Task
モデルに実装していきます。
Railsでは基本的に、こういったロジック関連は Model
に書きます。
現状 app/models/task.rb
は
app/models/task.rb【確認】
class Task < ApplicationRecord
validates :title, presence: true, length: { minimum: 3, maximum: 20 }
validates :description, allow_blank: true, length: { minimum: 3, maximum: 500 }
validates :start_time, presence: true
end
このようになっています。
では、書いてみましょう。
app/models/task.rb【追記】
class Task < ApplicationRecord
enum status: { not_started: 0, in_progress: 1, closed: 2 } #追記部分
validates :title, presence: true, length: { minimum: 3, maximum: 20 }
validates :description, allow_blank: true, length: { minimum: 3, maximum: 500 }
validates :start_time, presence: true
end
まず、 enum
の宣言をし、 引数のキーにカラム名である :status
そしてその値に { not_started: 0, in_progress: 1, closed: 2 }
というHashが与えられています。
{ not_started: 0, in_progress: 1, closed: 2 }
このHashが置き換える数値と値の関係を定義しています。
これで第一段階が完了です。
View
todo_app % bin/dev
でサーバーを起動しましょう。
先程 0
だった status
のフラグが、 not_started
に変わっていますね。
無事、対応するキーが割り当てられた形となります。
FormHelper
では、次に編集画面も見ていきましょう。
app/views/_form.html.erb【確認】
<div class="row">
<div class="col-md-6">
<%= form_with(model: task) do |form| %>
<% if task.errors.any? %>
<div style="color: red">
<h2><%= pluralize(task.errors.count, "error") %> prohibited this task from being saved:</h2>
<ul>
<% task.errors.each do |error| %>
<li><%= error.full_message %></li>
<% end %>
</ul>
</div>
<% end %>
<% if params[:action] == "new" %>
<div class="mb-3">
<%= form.label :title, style: "display: block" %>
<%= form.text_field :title, class: "form-control" %>
</div>
<div class="mb-3">
<%= form.label :description, style: "display: block" %>
<%= form.text_area :description, class: "form-control" %>
</div>
<div class="mb-3">
<%= form.label :start_time, style: "display: block" %>
<%= form.datetime_field :start_time, class: "form-control" %>
</div>
<div class="mb-3">
<%= form.label :end_time, style: "display: block" %>
<%= form.datetime_field :end_time, class: "form-control" %>
</div>
<% end %>
<% if params[:action] == "edit" %>
<div class="mb-3">
<%= form.label :status, style: "display: block" %>
<%= form.number_field :status, class: "form-control" %>
</div>
<% end %>
<div class="mb-3">
<%= form.submit class: "btn btn-primary" %>
</div>
<% end %>
</div>
</div>
ここで少し独特な記法について見ていきます。
フォームを担っている部分は、それぞれ scaffold
によって自動生成されており、
<%= form_with(model: task) do |form| %>
<% end %>
この form_with
のブロック内の 引数
form
をレシーバーとして使える formヘルパー
の記法である
<%= form.text_field :title, class: "form-control" %>
<%= form.text_area :description, class: "form-control" %>
<%= form.datetime_field :start_time, class: "form-control" %>
<%= form.number_field :status, class: "form-control" %>
これらの部分によって書き込みが出来るようになっています。ざっくり
ヘルパーメソッド | 用途 |
---|---|
text_field | 文字列の入力 |
text_area | 改行を許容する文字列の入力 |
datetime_field | 日付と時間の入力 |
number_field | 数値の入力 |
このような目的のフォームとして指定されています。
ただし、今回使いたいフラグの選択肢に number_field
はふさわしくありません。
セレクトボックスを生成する select
を使ってみたいと思います。
書き換える部分は、
app/views/tasks/_form.html.erb【確認】(*コードが長いため一部のみを取り出しています。)
(略)
<% if params[:action] == "edit" %>
<div class="mb-3">
<%= form.label :status, style: "display: block" %>
<%= form.number_field :status, class: "form-control" %>
</div>
<% end %>
(略)
この edit
時に表示させる部分の
<%= form.number_field :status, class: "form-control" %>
この行です。
これを select
を使って書き換えます。
記法としては、
<%= form.select :カラム名, データーとして入力される要素 %>
<%= form.select :status, [0, 1, 2] %>
このように書くと数値を直接入力できます。
ただし、このまま更新しようとすると、
status
の値がエラーとなってしまいます。
データーとして入力される要素
には enum
の記法に従う必要があります。
<%= form.select :status, [:not_started, :in_progress, :closed] %>
このように Enum
のキーだけを渡すと、
表示上都合よく整えつつ、データーを送る事ができます。
また、この記述は動的に書くべきですので、
<%= form.select :status, Task.statuses.keys %>
となります。
さらに、Bootstrapを適用するには少し癖があって、
<%= form.select :status, Task.statuses.keys, {}, { class: "form-control" } %>
と書く必要があります。
select
のオプションは、書く場所と数を指定されているので、機能別に{}
で括ってあげないと認識できず、
<%= form.select :カラム名, Enumに設定したキーの配列, { フォームの機能オプション }, { HTMLのオプション } %>
このような記法となります。
最終的に、
app/views/tasks/_form.html.erb【編集】
(略)
<% if params[:action] == "edit" %>
<div class="mb-3">
<%= form.label :status, style: "display: block" %>
<%= form.select :status, Task.statuses.keys, {}, { class: "form-control" } %>
</div>
<% end %>
(略)
このように書き換えが出来ていると良いでしょう。
すると、
Bootstrapのフォームが適用できました。
データーの編集も出来たようです。
3. git
git
ここまでを保存しておきましょう。
todo_app % git add .
todo_app % git commit -m "statusをenumへ"
GitHub
Githubの設定ができているのであれば、同名のリポジトリを作成し、pushしておきましょう。
おわりに
本チャプターはここまでとなります。
次回は
- i18n
- enum_help
を学びます。
ここまでお疲れ様でした。