今回達成すること
前回作成したユーザーモデルの「emailカラム」にカスタムバリデーションを設定します。
最終的なuser.rbはこのようになります。
app/models/user.rb
class User < ApplicationRecord
before_validation :downcase_email
# validates
validates :email, presence: true, email: { allow_blank: true }
# methods
# 自分以外のactiveなユーザーがいればtrueを返す
def activated?(email)
users = new_record? ? User.all : User.where.not(id: self.id)
users.find_by(email: email, activated: true).present?
end
private
# validatesにemail小文字化
def downcase_email
self.email = email.downcase if self.email
end
end
EachValidatorクラスを使ったカスタムバリデーション
emailカラムにはEachValidatorクラスを使ったカスタムバリデーションを設定します。
そのためにまず、
- カスタムバリデーションファイルの作成
- Railsのオートロードシステムに、lib/validator以下のファイルを読み込む設定
を行います。
カスタムバリデーションファイルを作成する
まずは、libの直下にvalidatorディレクトリを作成して、email_validator.rbファイルを作成しましょう。
ディレクトリも同時に作成するので、Railsのルートディレクトリで下記コマンドを実行してください。
myapp $ mkdir lib/validator && touch lib/validator/email_validator.rb
libはこのようなディレクトリ構造となります。
lib
├── tasks
└── validator
└── email_validator.rb
Validatorクラスを宣言する
作成した
今回は、EmailValidator
と命名しました。
lib/validator/email_validator.rb
class EmailValidator < ActiveModel::EachValidator
end
-
ActiveModel::EachValidator
検証クラスの基本機能を提供するクラスです。
-
EmailValidator < ActiveModel::EachValidator
Railsでよく見る
<
←これ。これはActiveModel::EachValidatorクラスを継承していることを表します。
クラスを継承すると、EmailValidator内でActiveModel::EachValidatorの機能が使えるようになります。
Validatorクラス命名のルール
カスタムバリデーションクラスは、「検証名Validator」の形式で命名する必要があります。
モデルから呼び出すときは、この「検証名」だけを指定してアンダースコア形式で呼び出します。
検証クラス名 | モデルからの呼び出し |
---|---|
EmailValidatorの場合 | |
UserEmailValidatorの場合 | user_email |
validate_eachメソッドを定義する
次にvalidate_each
メソッドを定義します。
lib/validator/email_validator.rb
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
end
end
validate_eachメソッドは3つの引数を持ちます。
-
record
対象のモデルオブジェクトを受け取ります。
-
attribute
対象のフィールド名を受け取ります。カラム名のことですね。
-
value
対象の値を受け取ります。ユーザーが入力した値のことです。
この3つの引数を使ってバリデーションを実装します。
lib以下のファイルを読み込むよう設定する
Railsはオートロードシステムを使って、起動時にファイルを読み込んでいます。
オートロードシステムとは、「命名規則に則ったファイルを自動でrequireしてくれる機能」のことです。
要は、ファイルの自動読み込み機能のことですね。
オートロードシステムが読み込むディレクトリパス
オートロードシステムは、指定されたディレクトリパスの配下にあるファイルを自動で読み込みます。
どのディレクトリが指定されているかは、Railsコンソールで確認をとることができます。
myapp $ rails c
> puts ActiveSupport::Dependencies.autoload_paths
ずらずらとディレクトリパスが出てきますね。
/Users/andou/rails/myapp8/app/channels
/Users/andou/rails/myapp8/app/controllers
/Users/andou/rails/myapp8/app/controllers/concerns
/Users/andou/rails/myapp8/app/jobs
/Users/andou/rails/myapp8/app/
...
これはデフォルトで設定されているパスですが、ここにはlib/validatorのパスが指定されていません。
今のままでは、作成したemail_validator.rbが読み込まれない状況です。
lib/validatorのパスをオートロードに追加する
そこで、lib/validatorのディレクトリパスをオートロードに追加します。
config/application.rb
module Myapp8
class Application < Rails::Application
...
config.autoload_paths += %W(#{config.root}/lib/validator) # 追記
オートロードに追加できたか確認する
もう一度、Railsコンソールに入り直して確認コマンドを実行します。
myapp $ rails c
> puts ActiveSupport::Dependencies.autoload_paths
追加されていますね。
/Users/andou/rails/myapp8/lib/validator
これで、email_validator.rbが読み込まれるようになりました。
ユーザーモデルから呼び出してみよう
では実際に機能しているかユーザーモデルから呼び出してみましょう。
app/models/user.rb
validates :email, email: true
gem 'pry-byebug' を使用しています。
lib/validator/email_validator.rb
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
binding.pry
end
end
コンソールからユーザーを作成してみましょう。
myapp $ rails c
> User.create(email:"", password_digest:"")
デバッグに引っかかりました。email_validator.rbが呼び出されている証拠です。
2: def validate_each(record, attribute, value)
3: binding.pry
=> 4: end
デバッグから抜けましょう。
> exit
確認が取れたらbinding.pry
は削除しておいてください。
emailバリデーションの設定
emailには下記バリデーションを設定します。
- 入力必須
- 文字数は255文字まで
- 基本書式に沿っていること
- アクティブでかつ、同じemailのユーザーを保存しない
255文字制限は必要?
ほうほう。Rails4.2以降255文字制限がなくなったようですね。
string型もtext型もそう変わり無いようです。↓
今回は開発環境に「sqlite」、本番環境に「postgresql」を使うので255文字制限を外しても問題ないのですが、「mysql」は対象外。
もしもの移行のために255文字制限をつけることにします。
email_validator.rbを編集する
それでは、
lib/validator/email_validator.rb
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
# max
max = 255
record.errors.add(attribute, :too_long, count: max) if value.length > max
# format
format = /\A\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*\z/
record.errors.add(attribute, :invalid) unless value =~ format
# uniqueness
record.errors.add(attribute, :taken) if record.activated?(value)
end
end
max
-
record.errors.add(attribute, :too_long, count: max)
errors.addの引数は(対象オブジェクト, エラーメッセージ, エラーメッセージに渡す引数)となります。
この「エラーメッセージに渡す引数」により、"255文字までにしてください"と表示することができます。
Validation failed: Email is too long (maximum is 255 characters), Email is invalid
format
-
value =~ format
Rubyの
=~
演算子を使っています。 =>「str =~ regexp」=~
は、文字列strに対して正規表現regexpとのパターンマッチを行います。パターンにマッチしない時はnilを返します。
# OKパターン
> email = "email@example.com"
> email =~ format
=> 0
# NGパターン
> email = "email"
> email =~ format
=> nil
uniqueness
-
record.activated?(value)
ユーザーモデルのactivated?メソッドにemail(value)を渡しています。
activated?メソッドは、自分以外のアクティブなユーザーがいる時にtrueを返します。
EmailValidatorクラスをユーザーモデルから呼び出す
EmailValidatorクラスを呼び出すために、
app/models/user.rb
class User < ApplicationRecord
before_validation :downcase_email
# validates
validates :email, presence: true, email: { allow_blank: true }
# methods
# 自分以外のactiveなユーザーがいればtrueを返す
def activated?(email)
users = new_record? ? User.all : User.where.not(id: self.id)
users.find_by(email: email, activated: true).present?
end
private
# validatesにemail小文字化
def downcase_email
self.email = email.downcase if self.email
end
end
before_validation
-
before_validation :downcase_email
before_validationは、バリデーション前にdowncase_emailのメソッドを実行します。
downcase_emailは、ユーザーが入力したemailを全て小文字にしています。
> email = "EMAIL@EXAMPLE.COM"
> email.downcase
=> "email`example.com"
validates
-
presence: true
入力必須としています。
-
email: { allow_blank: true }
EmailValidatorクラスを呼び出しています。
nilもしくは、空白の場合は検証をスキップします。
activated?(email)
「同じemailかつ、activatedプラグがtrue」のユーザーが既に存在しているときにtrueを返します。
-
new_record?
新しいレコードの時にtrueを返します。
-
users.find_by(email: email, activated: true).present?
ユーザーテーブルからfind_byを使って、引数に渡されたemailと同じ、かつactivatedプラグがtrueのユーザーを取得しています。
emailのみでユーザーの重複を判定すると?
emailのみでユーザーの重複を判定すると、メール認証の有効期限が切れた時に、二度と新規会員登録ができなくなります。
そこでactivatedプラグを利用し、アクティブなユーザーは1人だけという制約にしています。
まとめ
今回は、emailカラムにカスタムバリデーションを設定していきました。
お疲れ様でした。
簡潔にまとめると以下の通りです。
- カスタムバリデーションは
ActiveModel::EachValidator
クラスを継承して宣言する - バリデーションのクラス名は「検証名Validator」と命名する
- モデルからの呼び出し時は、検証名をアンダースコア形式で呼び出す
- lib以下にファイルを作成する場合、Railsのオートロードシステムにディレクトリパスを登録しなければならない
- カスタムバリデーションの実装は
validate_each
メソッドで行う
さて、次回は?
さてさて、次回は「password」カラムのバリデーション設定と、エラーメッセージの日本語化を行います。
まだまだサーバーサイドの実装が続きますが頑張りましょう。
それでは!