web-dev-qa-db-fra.com

Comment faire la validation de courrier électronique dans Ruby sur Rails?

Je suis en train de valider mon email dans Rails avec:

validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i

En outre, je fais la validation HTML5 dans le frontend, mais les adresses e-mail comme

[email protected]
[email protected]

sont toujours valables. Qu'est-ce que je rate?

42
Haseeb Ahmad

J'utilise la constante intégrée à l'URI dans la bibliothèque standard Ruby

validates :email, format: { with: URI::MailTo::EMAIL_REGEXP } 
116
Joshua Hunter

Mise à jour: je viens de trouver le valid_email2 bijou qui a l'air génial.

N'utilisez pas d'expression régulière pour la validation d'adresse e-mail. C'est un piège. Il existe bien plus de formats d'adresses électroniques valides que vous ne le pensez. Pourtant! La gemme mail (requise par ActionMailer, vous l'avez donc) analysera les adresses électroniques - avec un analyseur approprié - pour vous:

require 'mail'
a = Mail::Address.new('[email protected]')

Cela jettera un Mail::Field::ParseError s'il s'agit d'une adresse électronique non conforme. (Nous n'entrons pas dans des choses comme faire une recherche d'adresse MX ou quoi que ce soit.)

Si vous voulez la bonne expérience Rails, vous pouvez faire app/models/concerns/email_validatable.rb:

require 'mail'

module EmailValidatable
  extend ActiveSupport::Concern

  class EmailValidator < ActiveModel::EachValidator
    def validate_each(record, attribute, value)
      begin
        a = Mail::Address.new(value)
      rescue Mail::Field::ParseError
        record.errors[attribute] << (options[:message] || "is not an email")
      end
    end
  end
end

et ensuite dans votre modèle, vous pouvez:

include EmailValidatable
validates :email, email: true

Comme le commentaire ci-dessous d'Iwo Dziechciarow le mentionne, tout ce qui est une adresse valide "A:" est transmis. Donc, quelque chose comme Foo Bar <[email protected]> est valable. Cela pourrait être un problème pour vous, peut-être pas; c'est vraiment est une adresse valide, après tout.

Si vous ne voulez que l’adresse:

a = Mail::Address.new('Foo Bar <[email protected]>')
a.address
=> "[email protected]"

Comme Björn Weinbrenne le note ci-dessous, il existe des solutions beaucoup plus valables adresses RFC2822 que vous ne le pensez probablement (toutes les adresses répertoriées ici sont conformes et peuvent recevoir du courrier en fonction des configurations système). pourquoi je ne recommande pas d'essayer une expression rationnelle, mais d'utiliser un analyseur conforme.

Si vous vous souciez vraiment de savoir si vous pouvez envoyer un courrier électronique à une adresse, alors votre meilleur choix - de loin - consiste à envoyer un message avec un lien de vérification.

19
Nate

Si vous utilisez déjà la gemme Devise dans votre application, il peut être opportun d'utiliser

email =~ Devise.email_regexp

... ce qui signifie également que différents endroits de l'application utilisent la même validation.

13
Martin T.

@Nate Merci beaucoup d'avoir préparé cette réponse. Je ne savais pas que la validation des e-mails comportait autant de nuances avant de consulter votre extrait de code.

J'ai remarqué que le joyau actuel du courrier: mail-2.6.5 ne génère pas d'erreur pour un email de "abc". Exemples:

>> a = Mail::Address.new('abc')
=> #<Mail::Address:70343701196060 Address: |abc| >
>> a.address # this is weird
=> "abc"
>> a = Mail::Address.new('"Jon Doe" <[email protected]>')
=> #<Mail::Address:70343691638900 Address: |Jon Doe <[email protected]>| >
>> a.address
=> "[email protected]"
>> a.display_name
=> "Jon Doe"
>> Mail::Address.new('"Jon Doe <jon')
Mail::Field::ParseError: Mail::AddressList can not parse |"Jon Doe <jon|
Reason was: Only able to parse up to "Jon Doe <jon
  from (irb):3:in `new'
  from (irb):3
>>

Il jette Mail::Field::ParseError _ erreurs pour "Jon Doe <jon qui est super. Je crois que va vérifier pour le simple "modèle abc" aussi.

Dans app/models/concerns/pretty_email_validatable.rb:

require 'mail'

module PrettyEmailValidatable
  extend ActiveSupport::Concern

  class PrettyEmailValidator < ActiveModel::EachValidator

    def validate_each(record, attribute, value)
      begin
        a = Mail::Address.new(value)
      rescue Mail::Field::ParseError
        record.errors[attribute] << (options[:message] || "is not an email")
      end

      # regexp from http://guides.rubyonrails.org/active_record_validations.html
      value = a.address
      unless value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
        record.errors[attribute] << (options[:message] || "is not an email")
      end
    end

  end
end

et ensuite dans votre modèle, vous pouvez:

include PrettyEmailValidatable
validates :pretty_email, email: true

J'utilise donc ce qui précède pour la validation du "joli courrier électronique" et le https://github.com/balexand/email_validator pour la validation du courrier électronique standard.

7
tongueroo
5
thaleshcv

Si quelqu'un d'autre est très concentré sur le TDD: je voulais quelque chose pour lequel je pourrais écrire des tests et que je pourrais améliorer plus tard si nécessaire, sans lier les tests à un autre modèle.

Construit à partir du code de Nate et tongueroo (Merci Nate et tongueroo !), Cela a été fait en Rails 5, Ruby 2.4.1. Voici ce que j'ai jeté dans app/validators/email_validator.rb:

require 'mail'

class EmailValidator < ActiveModel::EachValidator
  def add_error(record, attribute)
    record.errors.add(attribute, (options[:message] || "is not a valid email address"))
  end

  def validate_each(record, attribute, value)
    begin
      a = Mail::Address.new(value)
    rescue Mail::Field::ParseError
      add_error(record, attribute)
    end

    # regexp from http://guides.rubyonrails.org/active_record_validations.html
    value = a.address unless a.nil?
    unless value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
      add_error(record, attribute)
    end
  end
end

Et ceci n’est nullement exhaustif, mais voici ce que j’ai jeté dans spec/validators/email_validator_spec.rb:

require 'Rails_helper'

RSpec.describe EmailValidator do
  subject do
    Class.new do
      include ActiveModel::Validations
      attr_accessor :email
      validates :email, email: true
    end.new
  end

  context 'when the email address is valid' do
    let(:email) { Faker::Internet.email }

    it 'allows the input' do
      subject.email = email
      expect(subject).to be_valid
    end
  end

  context 'when the email address is invalid' do
    let(:invalid_message) { 'is not a valid email address' }

    it 'invalidates the input' do
      subject.email = 'not_valid@'
      expect(subject).not_to be_valid
    end

    it 'alerts the consumer' do
      subject.email = 'notvalid'
      subject.valid?
      expect(subject.errors[:email]).to include(invalid_message)
    end
  end
end

J'espère que ça aide!

4
Jack Barry

Voici la nouvelle façon Rails de faire la validation de courrier électronique:

validates :email, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, on: :create }

Reportez-vous à la documentation sur les valises Rails .

3
Cyzanfar

La réponse simple est: n'utilisez pas une expression rationnelle. Il y a trop de cas Edge, de faux négatifs et de faux positifs. Recherchez un signe @ et envoyez un mail à l'adresse pour le valider:

https://www.youtube.com/watch?v=xxX81WmXjPg

2
Martin T.
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
!("[email protected]" =~ VALID_EMAIL_REGEX).nil?
2
A H K

Ruby a une expression régulière intégrée pour valider les emails:

validates :email, format: {
    with: URI::MailTo::EMAIL_REGEXP,
    message: 'Only valid emails allowed'
  }

Source: bibliothèque standard Ruby: URI :: MailTo

1
Will

Tu peux essayer ça

VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i
1
vipin cp

Est préférable de suivre Rails documentation:

https://guides.rubyonrails.org/active_record_validations.html#custom-validators

class EmailValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    unless value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
      record.errors[attribute] << (options[:message] || "is not an email")
    end
  end
end

class Person < ApplicationRecord
  validates :email, presence: true, email: true
end
1
Adriano Tadao

Sachez que pour l'instant email_validator gem n'a pas de règles de validation complexes, mais seulement:

/[^\s]@[^\s]/

https://github.com/balexand/email_validator/blob/master/lib/email_validator.rb#L1

L'argumentation est dans https://medium.com/hackernoon/the-100-correct-way-to-validate-email-addresses-7c4818f2464

1
gayavat

essaye ça.

validates_format_of  :email, :with => /^[\+A-Z0-9\._%-]+@([A-Z0-9-]+\.)+[A-Z]{2,4}$/i
1
jithya