SPA開発 7. Userモデル開発 #03
2019年11月15日に更新

【Rails】EachValidatorクラスを使ったEmailカスタムバリデーション【lib以下読み込み】

今回達成すること

前回作成したユーザーモデルの「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クラスを使ったカスタムバリデーションを設定します。

そのためにまず、

  1. カスタムバリデーションファイルの作成
  2. 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クラスを宣言する

作成したemail_validator.rbにクラスを宣言します。

今回は、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の場合 email
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

    対象のモデルオブジェクトを受け取ります。

    2019-11-07 09-07-20

  • attribute

    対象のフィールド名を受け取ります。カラム名のことですね。

    2019-11-07 09-07-39

  • value

    対象の値を受け取ります。ユーザーが入力した値のことです。

    2019-11-07 09-08-08

この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のディレクトリパスをオートロードに追加します。

application.rbに1行追加することで実現します。

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

email_validator.rbにデバッグを差し込みます。

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には下記バリデーションを設定します。

  1. 入力必須
  2. 文字数は255文字まで
  3. 基本書式に沿っていること
  4. アクティブでかつ、同じemailのユーザーを保存しない

255文字制限は必要?

ほうほう。Rails4.2以降255文字制限がなくなったようですね。

string型もtext型もそう変わり無いようです。↓

https://blog.willnet.in/entry/2015/08/31/093741

今回は開発環境に「sqlite」、本番環境に「postgresql」を使うので255文字制限を外しても問題ないのですが、「mysql」は対象外。

もしもの移行のために255文字制限をつけることにします。

email_validator.rbを編集する

それでは、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クラスを呼び出すために、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

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」カラムのバリデーション設定と、エラーメッセージの日本語化を行います。

まだまだサーバーサイドの実装が続きますが頑張りましょう。

それでは!

現在、カテゴリー「Rails apiとNuxt.jsでSPA開発」のデモアプリを構築中です。記事になるまでもう少々のお時間が必要です。ブログの更新が止まって申し訳ありません。デモアプリの進捗状況は こちらの記事 で随時お伝えしてまいります。
スポンサー広告
次の記事はこちらです
SPA開発
今日のTweet
スポンサー広告