«

»

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

27 comments

2 pings

  1. Sohan says:

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

  2. Hallelujah says:

    Thanks a lot :)

  3. Steve Root says:

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

  4. David K says:

    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 says:

    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 says:

    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 says:

    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 says:

    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 says:

    Yes I did.

  10. imohan says:

    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 says:

    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 says:

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

  13. HP says:

    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 says:

    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 says:

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

  16. Adam says:

    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 says:

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

  18. James Conroy-Finn says:

    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 says:

    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 says:

    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 says:

    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 says:

    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 says:

    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. Paco Guzmán says:

    I was using this for a while

    https://github.com/cesario/activevalidators/blob/master/lib/active_model/validations/email_validator.rb

  25. Hallelujah says:

    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 :) )

  26. Himesh says:

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

  27. Hallelujah says:

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

    See new ICANN gTLDs

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

    [...] 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 says:

    [...] 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.

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> <pre lang="" line="" escaped="" highlight="">