おはようございます。今日は金曜日で一週間も終わりですが、変わらずコツコツやっていきます。
今回は、Railsのバリデーション機能についてよく分からなりググっているので、それについてまとめていければと思います。
なぜバリデーションをつけるのか
バリデーションをつけないと意図しないデータがデータベース上に入ってしまい、アプリケーション上でエラーを引き起こしてしまったり、脆弱性を入れ込んでしまう原因にもありえます。DBの制約を利用する方法もありますが、入力に近いアプリケーション側でバリデーションを行い、不正なデータを弾くのがベターです。
さまざまなバリデーション
最初にパーフェクトRuby on Railsでの内容をまとめていきます。気になった方はぜひ手にとって読んでみてください。少し難しいですが、すごく勉強になります。
下のようなbooksテーブルが定義されているとします。
booksテーブル |
name |
price |
published_on
|
sales_status
|
publisher_id
|
それでは、Bookモデルにバリデーションを書いていきます。アプリケーション上の要件として以下のものが挙げられたとしましょう。
- 本の名前は必ず入力されていること
- 本の名前は25文字以下であること
- 本の価格は0円以上であること
このような単純な要件であれば、ActiveRecord / ActiveModelの機能を用いれば簡潔に書くことができます。
class Book < ApplicationRecord
validates :name, presence: true
validates :name, length: { maximum: 25 }
# 連ねて書くこともできる
# validates :name, presence: true, length: { maximum: 25 }
validates :price, numericality: { greater_than_or_equal_to: 0 }
end
また、他にもActiveModelには様々なバリデーション機能が備わっています。
class Book < ApplicationRecord
# 3字以上で入力
validates :name, length: { minimum: 3 }
# 1字以上10字以下を許可
validates :nickname, presence: true, length: { in: 1..10 }
# 301円から99,999円を許可
validates :price, numericality: { grater_than: 300, less_than_or_equal: 99,999 }
# 数字のみ許可
validates :price, numericality: { only_integer: true }
# 関連先のモデルのバリデーションチェックも行う
validates_associated :publisher
end
class User < ApplicationRecord
# ユニーク制約
validates :email, uniqueness: true
# フォーマットチェック
validates :email, format: { with: URI::MailTo::EMAIL_REGEXP }
# チェックボックスにチェックがついているか確認 (※)
validates :terms_of_service, acceptance: true
end
class Like < ApplicationRecord
# 複合ユニーク制約
validates :user_id, uniqueness: { scope: :post_id }
end
※ acceptanceメソッドは、フォームが送信されたときにチェックボックスがオンになっているかどうかを検証します。ユーザーによるサービス利用条項への同意が必要な場合や、ユーザーが何らかの文書に目を通したことを確認させる場合によく使われます。これはデータベースに保存する必要はありません。これに対応するフィールドがなくても、単にヘルパーが仮想の属性を作成してくれます。(Railsガイドより)
カスタムバリデーション
この他にも自分でバリデーションをカスタムしたい場合もあるかと思います。その場合はvalidateブロックを利用すると簡単に実装ができます。
errorsオブジェクトに何かしらの値が入っていれば、バリデーションエラーとみなされるため、自分でバリデーションを定義する場合には不正な条件であればerrorsにエラーメッセージを付与するという形がいいでしょう。
validate do |book|
if book.name.include?("exercise")
book.errors[:name] << "I don't like exercise."
end
end
コンソールで挙動を確認します。
publisher = Publisher.first
book = Book.new(name: "exercise", price: 1000, publisher: publisher)
book.valid?
=> false
book.errors.messages
=> {:name=>["I don't like exercise."]}
また、以下のようにプライベートメソッドを渡す書き方もできます。
validate :email_is_not_taken_another
private
def email_is_not_taken_another
errors.add(:email, :taken, value: email) if User.exists?(email: email)
end
上で使っているaddメソッドは特定の属性に関連するエラーメッセージを手動で追加できます。このメソッドは属性とエラーメッセージを引数として受け取ります。ここでは、エラーメッセージとしてtakenが引数として渡されていますが、これは"has already been taken"
というメッセージになります。
https://railsguides.jp/active_record_validations.html#errors-add
エラーメッセージが重複して表示されるのを防ぐ方法
また、モデルで下記のようにバリデーションをはっていた場合、エラーメッセージが重複して表示されることがあるかと思います。
class User < ApplicationRecord
validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
end
emailが空で送られてくると、次のようにエラーメッセージが複数格納されており、これを表示させるとエラー文が重複して表示されてしまいます。
self.valid?
=> false
self.errors.messages
=> {:email=>["can't be blank", "is invalid"]}
これを防ぐには、allow_blank: true
オプションをつけ、emailが空欄の場合はバリデーションをスキップするようにします。
class User < ApplicationRecord
validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP, allow_blank: true }
end
すると、もう一度emailを空で送信をすると、エラーメッセージが重複しないのが確認できると思います。
self.valid?
=> false
self.errors.messages
=> {:email=>["can't be blank"]}
2つのテキストフィールドで受け取る内容が完全に一致するか確認する方法
少し長くなりましたが、タイトルのようなことをしたい場合、confirmation: true
とし、テキストフィールドとして***_confirmation
フィールドを用意します。emailならemail_confirmation
となります。
class User < ApplicationRecord
validates :email, confirmation: true
end
# 略
<%= form_with model: @user_registration_form, url: user_registrations_path do |f| %>
<%= f.label :email %>
<%= f.text_field :email %>
<%= f.label :email_confirmation, "Confirmation" %>
<%= f.text_field :email_confirmation %>
# 略
すると、userモデルに仮想的なemail_confirmation属性がつくられ、バリデーションがはられます。試しに下画像のようにemailとemail_confirmationを異なる値にして送信してみます。
すると、errorsオブジェクトに値が入っているのが確認できます。
self.valid?
=> false
self.errors.messages
=> {:email_confirmation=>["doesn't match Email"]}
さいごに
いかがだったでしょうか。バリデーションの種類はまだまだたくさんあって全部覚えるのは不可能なので、必要になったらRailsガイドを見るのがいいかと思います。また、よく使うバリデーションがあればコメント欄などで教えていただけると嬉しいです。ここまで読んでいただきありがとうございました。