【Ruby on Rails】バリデーションの基本

Ruby

こんにちは、かつコーチです。

フォームからデータを保存する機能を作っていると、「空欄のまま保存されてしまう」「同じメールアドレスで何人も登録できてしまう」といった困りごとに出会います。

こうした不正なデータの保存を防ぐ仕組みが、Railsのバリデーション(データを保存する前に内容が正しいかチェックする仕組み)です。

今回は、ユーザー登録フォームを例に、よく使うバリデーションの書き方と、valid?メソッドやエラー情報の確認方法を解説します。

バリデーションの基本の書き方

presence:空欄を許さない

もっともよく使うのが、値が空でないことを保証するpresenceバリデーションです。

# app/models/user.rb
class User < ApplicationRecord
  validates :name, presence: true
  validates :email, presence: true
end

これで、nameまたはemailが空のままレコードを保存しようとすると、保存が失敗するようになります。

user = User.new(name: "", email: "test@example.com")
user.save
# => false(保存されない)

length:文字数の範囲を指定する

パスワードの最低文字数や、名前の文字数上限などを指定するにはlengthを使います。

class User < ApplicationRecord
  validates :name, presence: true, length: { maximum: 30 }
  validates :password, length: { minimum: 8 }, allow_nil: true
end

minimumは最小文字数、maximumは最大文字数、in: 8..20のように範囲で指定することも可能です。

uniqueness:重複を防ぐ

メールアドレスの重複登録を防ぐにはuniquenessを使います。

class User < ApplicationRecord
  validates :email, presence: true, uniqueness: true
end

ただし、uniquenessだけではアプリケーション側のチェックしか行われません。

同時に複数のリクエストが飛んできた場合、わずかなタイミングのずれで重複登録が起きる可能性があるため、データベース側にも一意インデックス(重複した値を許さない設定)を張っておくのが安全です。

# db/migrate/xxxxxx_add_unique_index_to_users_email.rb
class AddUniqueIndexToUsersEmail < ActiveRecord::Migration[8.0]
  def change
    add_index :users, :email, unique: true
  end
end

正規表現でメール形式をチェックする

メールアドレスの形式を簡易的にチェックするにはformatを使います。

class User < ApplicationRecord
  validates :email,
    presence: true,
    uniqueness: true,
    format: { with: URI::MailTo::EMAIL_REGEXP }
end

URI::MailTo::EMAIL_REGEXPはRuby標準ライブラリに用意されているメール形式チェック用の正規表現で、自分で正規表現を組む必要がないため安心して使えます。

よくあるつまずきポイント・エラー対処

saveの戻り値を確認せずに次の処理へ進んでしまう

私が初心者の頃によくやってしまった失敗が、saveの戻り値をチェックせずに処理を進めてしまうことです。

❌ Before:save!を使わずエラーに気づけない

def create
  @user = User.new(user_params)
  @user.save
  redirect_to @user
end

バリデーションに引っかかって保存が失敗しても、savefalseを返すだけで例外を投げません。

そのため上のコードでは、保存されていないのにredirect_to @userが実行され、@user.idnilのままURLが組み立てられて、次のような画面に遷移してしまいました。

ActiveRecord::RecordNotFound in UsersController#show
Couldn't find User without an ID

保存が失敗したことに気づかないまま、まったく別のエラー画面が表示されるという、原因を追いにくいバグでした。

✅ After:valid?とerrorsで保存前にチェックする

def create
  @user = User.new(user_params)

  if @user.save
    redirect_to @user
  else
    render :new, status: :unprocessable_entity
  end
end

savefalseを返した場合は、render :newで入力フォームに戻し、@user.errorsにエラー内容が入っている状態でビューに渡します。

ビュー側では、errorsオブジェクトから具体的なエラーメッセージを取り出せます。

<% if @user.errors.any? %>
  <ul>
    <% @user.errors.full_messages.each do |message| %>
      <li><%= message %></li>
    <% end %>
  </ul>
<% end %>

valid?とinvalid?の使い分け

保存はせずに、事前にバリデーションだけ実行したい場合はvalid?を使います。

user = User.new(name: "", email: "test@example.com")

user.valid?
# => false(バリデーション実行後、falseが返る)

user.errors.full_messages
# => ["Nameを入力してください"]

valid?を呼び出すとerrorsオブジェクトにエラー内容が格納されるため、saveを呼ばなくてもエラー内容を確認できます。

逆に、バリデーションが通っているかを確認したいだけならinvalid?valid?の逆で、エラーがある場合にtrueを返す)を使うと、条件分岐が読みやすくなる場合もあります。

まとめ

この記事のポイント

  • presenceは空欄を許さない、lengthは文字数、uniquenessは重複防止のバリデーション
  • uniquenessだけに頼らず、データベース側にも一意インデックスを設定する
  • saveは失敗時にfalseを返すだけなので、戻り値を必ずチェックする
  • valid?を呼ぶとerrorsオブジェクトにエラー内容が格納される
  • エラーメッセージはerrors.full_messagesで取り出してビューに表示する

次に読むべき記事

→ 次の記事:アソシエーション(has_many・belongs_to)の書き方

タグ: Ruby on Rails, 初心者向け, データベース

タイトルとURLをコピーしました