【Rails】EachValidatorクラスを使ったEmailカスタムバリデーション【lib以下読み込み】
  • 2019.11.10に公開
  • 2019.11.15に更新
  • SPA開発
  • 7. Userモデル開発
  • No.3 / 6
「SPA開発」では、Nuxt.js v2.14未満が使用されています。 Nuxt.jsはv2.13~14で大きなバージョンアップがありました。それに伴い、書き方も大きく変化しています。 v2.14以上で書かれた「SPA開発」の続編は、カテゴリー「 Udemy 」 で公開しています。

今回達成すること

前回作成したユーザーモデルの「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」カラムのバリデーション設定と、エラーメッセージの日本語化を行います。

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

それでは!

あなたの力になれること
私自身が独学でプログラミングを勉強してきたので、一人で学び続ける苦しみは痛いほど分かります。そこで、当時の私がこんなのあったら良いのにな、と思っていたサービスを立ち上げました。周りに質問できる人がいない、答えの調べ方が分からない、ここを聞きたいだけなのにスクールは高額すぎる。そんな方に向けた単発・短期間メンターサービスを行っています。
独学プログラマのサービス
SPA開発の投稿
1
  • 更新情報
  • /
  • #01
「Rails apiとNuxt.jsでSPA開発」のデモアプリを開発中...。【2020/05/19追記: このカテゴリーの更新を一旦終了といたします】
1
  • 今回作るアプリケーション
  • /
  • #01
Railsアプリの完成イメージ画像と作ろうと思った経緯
2
  • 今回作るアプリケーション
  • /
  • #02
今回作るRailsアプリの全体像と機能の整理
1
  • 開発環境を整える
  • /
  • #01
macにRailsをゼロからインストールする
2
  • 開発環境を整える
  • /
  • #02
Rails apiモードのプロジェクトを作成し、Gitにコミットする
3
  • 開発環境を整える
  • /
  • #03
Bitbucketに公開鍵を追加し、Railsプロジェクトをpushする
4
  • 開発環境を整える
  • /
  • #04
HerokuCLIのインストールとherokuアプリケーションの作成
1
  • RailsをHerokuにデプロイする
  • /
  • #01
Herokuのデータベース設定と開発に便利なgemを導入する
2
  • RailsをHerokuにデプロイする
  • /
  • #02
HerokuにPumaを導入するためのRailsセットアップ
3
  • RailsをHerokuにデプロイする
  • /
  • #03
Railsに"Hello"を表示してHerokuへデプロイする
1
  • RailsとNuxt.jsを共存させる
  • /
  • #01
【RailsとNuxt.jsの共存】Rails上にNuxt.jsのプロジェクトを構築しよう
2
  • RailsとNuxt.jsを共存させる
  • /
  • #02
Nuxt.jsからRailsへ、初めてのapi通信でHelloを表示しよう
3
  • RailsとNuxt.jsを共存させる
  • /
  • #03
Nuxt.jsにVuetify2.0を導入してFont Awesomeもインストールするぜ
4
  • RailsとNuxt.jsを共存させる
  • /
  • #04
初めてのRailsApiアプリの公開。Herokuにデプロイする準備と実際のデプロイまで
1
  • データベース設計
  • /
  • #01
データベースを正規化する前に、会計システムの勘定科目データを整理する
2
  • データベース設計
  • /
  • #02
会計システムのデータベース設計に挑む
1
  • バージョンアップ情報
  • /
  • #01
【ご報告】Nuxt.jsを2.10.2にバージョンアップしました
2
  • バージョンアップ情報
  • /
  • #02
【ご報告】Railsを6.0.0にバージョンアップしました
1
  • Userモデル開発
  • /
  • #01
本番環境と開発環境でRailsのSeedデータを切り替える
2
  • Userモデル開発
  • /
  • #02
Railsにユーザーテーブルを作成する【テーブル確認コマンド】
3
  • Userモデル開発
  • /
  • #03
【Rails】EachValidatorクラスを使ったEmailカスタムバリデーション【lib以下読み込み】
4
  • Userモデル開発
  • /
  • #04
【Rails】エラーメッセージの日本語化【i18nとja.ymlのセッティング】
5
  • Userモデル開発
  • /
  • #05
【Rails】開発・テスト・本番環境の全てにユーザーSeedデータ投入する
6
  • Userモデル開発
  • /
  • #06
【Rails】ユーザーモデルのバリデーションをテストする
1
  • ログイン周りのレイアウト設計
  • /
  • #01
【Nuxt.js】ログインフラグでレイアウトを切り替えるテクニック【2019/12/07追記あり】
2
  • ログイン周りのレイアウト設計
  • /
  • #02
【Nuxt.js】ウェルカムページのレイアウト構築【sassの導入】
3
  • ログイン周りのレイアウト設計
  • /
  • #03
【Nuxt.js】ログイン周りの入力フォームコンポーネント設計【2019/12/07追記あり】
4
  • ログイン周りのレイアウト設計
  • /
  • #04
【Nuxt.js】会員登録フォームを構築してサインアップページを完成させる
独学プログラマ
独学でも、ここまでできるってよ。
CONTACT
Nuxt.js制作のご依頼は下記メールアドレスまでお送りください。