«

»

Jul 21

Email validation in Ruby On Rails 3 or Active model without regexp

In Rails 3.0, you can use custom validator in your active_record model.
So I wanted to manage email validations without regexp matching like others do.
I find a new way to make this work thanks to ruby mail gem, a dependency of Rails 3.0

I use this with devise, see my blogpost here : Ruby Rails : How to bypass skip validation in Devise

Edit : Here is a gem https://github.com/hallelujah/valid_email

class User < ActiveRecord::Base
  validates :email, :presence => true, :email => true
end

Just put a file in app/validators/email_validator.rb

require 'mail'
class EmailValidator < ActiveModel::EachValidator
  def validate_each(record,attribute,value)
    begin
      m = Mail::Address.new(value)
      # We must check that value contains a domain and that value is an email address
      r = m.domain && m.address == value
      t = m.__send__(:tree)
      # We need to dig into treetop
      # A valid domain must have dot_atom_text elements size > 1
      # user@localhost is excluded
      # treetop must respond to domain
      # We exclude valid email values like <user@localhost.com>
      # Hence we use m.__send__(tree).domain
      r &&= (t.domain.dot_atom_text.elements.size > 1)
    rescue Exception => e   
      r = false
    end
    record.errors[attribute] << (options[:message] || "is invalid") unless r
  end
end

No regexp !! And beautiful !!

Here is the version without activerecord

require 'rubygems'
require 'active_model'
require 'active_support/all'
require 'active_model/validations'
require 'mail'
class EmailValidator < ActiveModel::EachValidator
  def validate_each(record,attribute,value)
    begin
      m = Mail::Address.new(value)
      # We must check that value contains a domain and that value is an email address
      r = m.domain && m.address == value
      t = m.__send__(:tree)
      # We need to dig into treetop
      # A valid domain must have dot_atom_text elements size > 1
      # user@localhost is excluded
      # treetop must respond to domain
      # We exclude valid email values like <user@localhost.com>
      # Hence we use m.__send__(tree).domain
      r &&= (t.domain.dot_atom_text.elements.size > 1)
    rescue Exception => e
      r = false
    end
    record.errors[attribute] << (options[:message] || "is invalid") unless r
  end
end
 
class Person
  include ActiveModel::Validations
  attr_accessor :name, :email
 
  validates :name, :presence => true, :length => { :maximum => 100 }
  validates :email, :presence => true, :email => true
end

33 comments

2 pings

Skip to comment form

  1. Sohan

    Your post has been linked at the Drink Rails blog as one of the top ruby on rails blogs of the day.

  2. Hallelujah

    Thanks a lot :)

  3. Steve Root

    Thanks for the post, just the solution I was looking for!

  4. David K

    Thanks for the post. I’m having trouble figuring out why this email: grass_1293770498_per@yahoo.com doesn’t pass validation. Any ideas?

  5. Hallelujah

    David K :

    Thanks for the post. I’m having trouble figuring out why this email: grass_1293770498_per@yahoo.com doesn’t pass validation. Any ideas?

    Well this email passes the validation …
    What’s wrong?

  6. David K

    Hallelujah :

    David K :
    Thanks for the post. I’m having trouble figuring out why this email: grass_1293770498_per@yahoo.com doesn’t pass validation. Any ideas?

    Well this email passes the validation …
    What’s wrong?

    Never mind, it works now. I am not sure why, but I got an “invalid email” error when I tried before.

  7. King

    You should make a gem out of this. I’d like to be able to simply mention it in my Gemfile and begin using it.

  8. sean

    you created your own folder ‘validators’ right? I’m using rails 3.03 but want to make sure that wasn’t one of the provided skeleton folders. sorry kinda of a newb question, but appreciate your reply.

  9. Hallelujah

    Yes I did.

  10. imohan

    How do we check email with subdomain like sg.domain.com if you use above validation it will true if somebody puts sg.domain where as it is invalid in real business case.

    -imohan

  11. Hallelujah

    Do you have an example ?

    Because it works for me :

    p = Person.new
    p.name = 'hallelujah'
    p.email = 'hallelujah@mydomain.com'
    p.valid? #=> true
     
    p.email = 'hallelujah@sg.domain.com'
    p.valid? #=> true
     
    p.email = 'sg.domain.com'
    p.valid? #=> false

    In “real business” nothing prevents you to set up an email on a subdomain : http://bit.ly/jzRLCz

  12. Ramiro Jr. Franco

    Wow, this is actually really cool, if you haven’t already you should think about making a gem out of this.

  13. HP

    Nicely done. I like being able to avoid using regexp and haven’t seen a way to do that before now. I’d making a gem out of this”.have to second the motion of “

  14. haxney

    This method seems to fail for valid email addresses like

    “name”

    Also, treetop.domain doesn’t exist in my version of Ruby (1.9.2p290), and it seems like this would suffice (sorry, I don’t know how to input code to WordPress):

    def is_valid_email?(e)
    m = Mail::Address.new(e)
    m.domain && m.address
    rescue Mail::Field::ParseError => e
    false
    end

    This method also has the advantage of returning the “addressy” part of the address (name plus domain), with any comments stripped.

  15. haxney

    Whoops, forgot an extra “end” at the end of the method.

  16. Adam

    This is great, especially for a newbie like myself. But I’m trying to write a test to prevent duplicate email addresses with different case, and it seems to fail.

    Does this validation allow for case insensitivity?

  17. Adam

    ha. sorry. got a little ahead of myself. i see why i was confused. Thanks for the great validator!

  18. James Conroy-Finn

    I’ve refactored the validation and added support for specifying a message or using I18n.

    I’ve gisted it for now, but plan to stick it in a gem at some point.

    https://gist.github.com/1188367

  19. Hallelujah

    As many want to make it a gem, it seems to be a good idea.
    However I wonder how consistent it will be in a future since it relies on the “tree” private method

  20. Hallelujah

    haxney :

    This method seems to fail for valid email addresses like

    “name”

    Also, treetop.domain doesn’t exist in my version of Ruby (1.9.2p290), and it seems like this would suffice (sorry, I don’t know how to input code to WordPress):

    def is_valid_email?(e)
    m = Mail::Address.new(e)
    m.domain && m.address
    rescue Mail::Field::ParseError => e
    false
    end

    This method also has the advantage of returning the “addressy” part of the address (name plus domain), with any comments stripped.

    Yep but a validation don’t need to return anything else than true or false.

    This piece of code was originally intended to rails application
    If you want some more features , it might be useful to create a gem upon

    Anyway for the most use cases, we do not type such rfc valid email like below in a text field (as in a signup form)

    “John Doe
    This email is valid and the mail gem can extract the user and domain part but iMHO this is not the purpose

  21. James Ferguson

    This validator seems to allow multiple email addresses:

    p = Person.new
    p.name = “Test”
    p.email = “test@example.com, test2@example.com
    p.valid? # => true

    Is that intended?

  22. José Valim

    Nice! Have you considered providing a patch to the Mail gem that adds a valid? method to Mail::Address? This way, we could just use the valid? method instead of explicitly checking the dots or worrying about Treetop details.

  23. Hallelujah

    Thanks you all for your feedbacks !!

    It seems that since this post has become quiet popular so we should create a project in github.

    @jose I don’t know yet if I will patch the mail gem but it is a good Idea

    The first step is maybe a gem (for backward compatibility) then pull request a patch to Mail gem.

    If you want to follow the project status here is the github repository :

    https://github.com/hallelujah/valid_email

  24. Hallelujah

    I know that. Many gems use what I found : email_validator, validate_email etc …
    But who cares !!! It is opensource and I am happy with it !! (a quote or a WWR vote may help me :) )

  25. Himesh

    This email is getting validated:
    cool_123@gmail.commmm
    cool_123@gmail.commmm.sahb

  26. Hallelujah

    Yes it is valid even if it does not exist yet.

    See new ICANN gTLDs

  27. Terry S

    Fabulous solution! It’s going in my default toolbox.

    Thank you!

  28. Asfand Yar Qazi

    Could I point out that the blanket-catching of Exceptions means that errors like out of memory, out of disk space, no method errors or any other exception not being expected would also get caught and ignored – could I suggest replacing with it ‘rescue RuntimeError’ as a matter of best practice? Also as a matter of best practice, shouldn’t the begin/rescue/end be wrapped around just the bit or bits that could raise exceptions rather than everything?

    Also, the fact that a private method is being called __send__ means that this solution will break the moment that method is changed or disappears, since it is not part of the public API.

    Regards,

    Asfand Yar Qazi

  29. Hallelujah

    @asfand

    You are totally right : using a private method may break the behaviour in the future.
    I will try to provide a patch for the mail gem as suggested by Jose Valim. It will be nicer but I don’t know where to begin.

  30. Jean-Charles Mourey

    I’m working through this Rails tutorial.

    To be clever, I thought I’d use your valid_email gem instead of the REGEX method used in the tutorial, but for some reason, the tests fail because the gem passes these addresses as valid:

    foo@bar_baz.com
    foo@bar+baz.com

    Is this normal? I didn’t think _ and + were legal in domain names (although they are both legal in the user name).

    Regards,
    Jean-Charles Mourey

  31. Hallelujah

    You’re right Jean-Charles !!

    I will dig into it deeper. Maybe as Joshua stated before, we should implement validation in the Mail gem directly

  32. Marry Laurenceau

    rational decision, especially pleased there is no need to use regexp. Thanks!

  1. Rails : How to skip or remove validations in a Model « La rolls des blogs

    [...] my last post with email validations, I was looking for a solution to integrate it with [...]

  2. Email Validator for Rails 3 Without Regex | Rob Conery

    [...] read about this over here but thought I’d add it here as I want to make sure I remember it. Works like a [...]

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>