Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

Railsでメールアドレスをバリデーションする方法(翻訳)

概要

原著者の許諾を得て翻訳・公開いたします。

日本語タイトルは内容に即したものにしました。
記事末尾のコメントもぜひお読みください。

Railsでメールアドレスをバリデーションする方法(翻訳)

RailsのActive Recordは、意味のあるデータを確実に得られるようにするためのバリデーション機能をActive Modelライブラリ経由で提供しています。

ユーザーにメールを送信可能であることを確認する処理は、アプリケーションを正しく設定するうえで非常に重要なので、既に皆さんもUser#email属性でバリデーションを行っていることでしょう。

🔗 以下の方法ではなく

手作り正規表現や、Railsの古いAPIドキュメントにあるものを使う。

class User < ApplicationRecord
  validates :email,
    format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i },
    presence: true,
    uniqueness: { case_insensitive: true }
end

🔗 以下の方法を使うこと

以下のより良いオプションの中から選ぶ。

🔗 Deviseの機能を利用する

既にDeviseでユーザー認証を行っているのであれば、Deviseのメールバリデーション機能を使うのが合理的です。

class User < ApplicationRecord
  validates :email,
    format: { with: Devise.email_regexp },
    presence: true,
    uniqueness: { case_insensitive: true }
end

heartcombo/devise - GitHub

🔗 URIライブラリ

Ruby 2.2以降では、Ruby標準のURLライブラリにメールアドレスバリデーション用の正規表現が組み込まれています(追加されたときのコミット: e63ab5d)。

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

この書き方はRailsガイドのサンプルコードでも使われています。

🔗 email_address gem

afair/email_address - GitHub

email_address gemのバリデーション機能には、メールアドレスを扱ううえで便利な拡張が他にもあります。

組み込みのバリデータを使う場合は以下のように書きます。

class User < ApplicationRecord
  validates :email,
    presence: true,
    uniqueness: { case_insensitive: true }

  # 属性名は`:email`にする必要がある
  validates_with EmailAddress::ActiveRecordValidator
end

validateブロック内でemail_address gemの機能を直接利用することもできます。

class User < ApplicationRecord
  validates :email,
    presence: true,
    uniqueness: { case_insensitive: true }

  validate do |user|
    EmailAddress::Address.new(user.email).valid?
  end
end

Railsのカスタムバリデータを利用する方法もあります。

class User < ApplicationRecord
  validates :email,
    email: true,
    presence: true,
    uniqueness: { case_insensitive: true }

  private

  # 他の場所でもメールの有効性を確認したい場合は別ファイルに置くことも可能
  class EmailValidator < ActiveModel::EachValidator
    def validate_each(record, attribute, value)
      unless EmailAddress::Address.new(value).valid?
        record.errors.add attribute, (options[:message] || "is not an email")
      end
    end
  end
end

🔗 そうする理由

メールアドレスのフォーマットの問題はとっくに解決済みです。既に解決した問題を「また」解決しようとするのは、見ていて頭がおかしくなりそうです。これと同じぐらい一般的な問題を解決するときは、インターネット全体で積み重ねられてきた経験に頼らない手はないでしょう。

上述したさまざまな良いオプションの背後では正規表現が利用されており、その厳密性のレベルはオプションごとに異なるものの、ほとんどのオプションは、公式RFCの厳密すぎる形式(email_address gemのREADMEをお読みください)に沿ったメールアドレスと比べれば「健全な慣習」レベルにあります。なお、公式RFCとして有効な、機械的に生成された(現実にはまずなさそうな)メールアドレスがバリデーションをパスする可能性もあります。

私が上のより良いオプションのどれを選ぶかと尋ねられたら、シンプルなURIライブラリを選ぶでしょう。このライブラリの背後で使われている正規表現は、通常の利用であれば十分柔軟です。

自分のアプリケーションでメールアドレスに「検出」「正規化」「部分の取り出し」操作を追加する必要が生じたらemail_address gemを採用し、ついでにバリデーションにも使いたくなるでしょう。

メールアドレスのバリデーションをアプリケーション内の複数箇所で行うのであれば、再利用可能なカスタムバリデータを検討するのは確実でしょう。

🔗 そうしない理由があるとすれば

上述のよりよいオプションで提供されるバリデーションの厳密さのレベル(および背後の正規表現)はそれぞれ異なるので、既存アプリでこれまで手作り正規表現などでバリデーションしていたものから乗り換えてバリデーションの仕様が変わると、もしかしたら問題が生じることがあるかもしれません。

しかし手作り正規表現を使い続けるよりも、新しいオプションに乗り換えて、アプリが扱うメールアドレスがその正規表現のバリデーションですべてパスするように作業する方が「おそらく」価値があるでしょう。

新規アプリであれば、従来のユースケースから大きく外れる非常に特殊なメールアドレスを扱うことが要求される場合でもない限り、手作りよりも上述の適切なソリューションの中から選ぶようにしましょう。

🔗 baba(BPS CTO)よりコメント

# https://github.com/ruby/ruby/commit/e63ab5d3ad289767eab49787e4e33390b0ce74e1#diff-61fb6899fba73f4d989b5f0d0c3be6a6a993c7a994c2c1c79f67b1cb46ed4a67R54

    # http://www.whatwg.org/specs/web-apps/current-work/multipage/states-of-the-type-attribute.html#valid-e-mail-address
    EMAIL_REGEXP = /\A[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*\z/

これにより、URIライブラリのメールアドレスバリデーションがブラウザのデフォルトのinput type="email"バリデーションと一致するという大きなメリットを得られます

参考: <input type="email"> - HTML: ハイパーテキストマークアップ言語 | MDN

  • Deviseのメールバリデーションでは、最新のDevise v4.9.3でも以下のような非常にユルい正規表現が使われていることは知っておいてよいでしょう。それこそほげ@ぴよでもバリデーションがパスするレベルです。
# https://github.com/heartcombo/devise/blob/v4.9.3/lib/devise.rb#L118
@@email_regexp = /\A[^@\s]+@[^@\s]+\z/

関連記事

Rails: Deviseを徹底理解する(1)基礎編(翻訳)

はじめての正規表現とベストプラクティス3: 冒頭/末尾にマッチするメタ文字とセキュリティ、文字セットの否定と範囲


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。