├── .rspec
├── Gemfile
├── lib
├── validates_email_format_of
│ ├── version.rb
│ ├── railtie.rb
│ ├── rspec_matcher.rb
│ └── active_model.rb
└── validates_email_format_of.rb
├── rakefile.rb
├── .github
├── dependabot.yml
└── workflows
│ └── ci.yml
├── gemfiles
├── rails_7.0.gemfile
├── rails_7.1.gemfile
├── rails_6.1.gemfile
├── rails_6.0.gemfile
├── rails_4.2.gemfile
├── rails_5.1.gemfile
├── rails_5.0.gemfile
└── rails_5.2.gemfile
├── .gitignore
├── config
└── locales
│ ├── ja.yml
│ ├── pl.yml
│ ├── tr.yml
│ ├── en.yml
│ ├── id.yml
│ ├── pt.yml
│ ├── fr.yml
│ ├── it.yml
│ ├── pt-BR.yml
│ └── de.yml
├── spec
├── rspec_matcher_spec.rb
├── spec_helper.rb
└── validates_email_format_of_spec.rb
├── Appraisals
├── validates_email_format_of.gemspec
├── MIT-LICENSE
├── CHANGELOG.md
└── README.md
/.rspec:
--------------------------------------------------------------------------------
1 | --color
2 | --order random
3 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "http://rubygems.org"
2 |
3 | gemspec
4 |
--------------------------------------------------------------------------------
/lib/validates_email_format_of/version.rb:
--------------------------------------------------------------------------------
1 | module ValidatesEmailFormatOf
2 | VERSION = "1.8.2"
3 | end
4 |
--------------------------------------------------------------------------------
/rakefile.rb:
--------------------------------------------------------------------------------
1 | require "bundler/gem_tasks"
2 | require "standard/rake"
3 |
4 | task default: [:spec, "standard:fix"]
5 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "github-actions"
4 | directory: "/"
5 | schedule:
6 | interval: "monthly"
7 |
--------------------------------------------------------------------------------
/gemfiles/rails_7.0.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "http://rubygems.org"
4 |
5 | gem "rails", "~> 7.0"
6 |
7 | gemspec path: "../"
8 |
--------------------------------------------------------------------------------
/gemfiles/rails_7.1.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "http://rubygems.org"
4 |
5 | gem "rails", "~> 7.1"
6 |
7 | gemspec path: "../"
8 |
--------------------------------------------------------------------------------
/gemfiles/rails_6.1.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "http://rubygems.org"
4 |
5 | gem "rails", "~> 6.1.0"
6 |
7 | gemspec path: "../"
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | pkg
2 | test/debug.log
3 | rdoc
4 | Gemfile.lock
5 | *.gem
6 | *.sqlite3
7 | *.swp
8 | .ruby-version
9 | gemfiles/.bundle
10 | gemfiles/*.gemfile.lock
11 |
--------------------------------------------------------------------------------
/gemfiles/rails_6.0.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "http://rubygems.org"
4 |
5 | gem "rails", "~> 6.0.3", ">= 6.0.3.2"
6 |
7 | gemspec path: "../"
8 |
--------------------------------------------------------------------------------
/gemfiles/rails_4.2.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "http://rubygems.org"
4 |
5 | gem "rails", "~> 4.2.0"
6 | gem "i18n", "< 1"
7 |
8 | gemspec path: "../"
9 |
--------------------------------------------------------------------------------
/gemfiles/rails_5.1.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "http://rubygems.org"
4 |
5 | gem "rails", "~> 5.1.7"
6 | gem "i18n", "< 1"
7 |
8 | gemspec path: "../"
9 |
--------------------------------------------------------------------------------
/gemfiles/rails_5.0.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "http://rubygems.org"
4 |
5 | gem "rails", "~> 5.0.7", ">= 5.0.7.2"
6 | gem "i18n", "< 1"
7 |
8 | gemspec path: "../"
9 |
--------------------------------------------------------------------------------
/gemfiles/rails_5.2.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "http://rubygems.org"
4 |
5 | gem "rails", "~> 5.2.4", ">= 5.2.4.3"
6 | gem "i18n", "< 1"
7 |
8 | gemspec path: "../"
9 |
--------------------------------------------------------------------------------
/config/locales/ja.yml:
--------------------------------------------------------------------------------
1 | ja:
2 | activemodel: &errors
3 | errors:
4 | messages:
5 | invalid_email_address: 'は妥当なメールアドレスでは無いようです。'
6 | email_address_not_routable: 'は到達不能です。'
7 | activerecord:
8 | <<: *errors
9 |
--------------------------------------------------------------------------------
/config/locales/pl.yml:
--------------------------------------------------------------------------------
1 | pl:
2 | activemodel: &errors
3 | errors:
4 | messages:
5 | invalid_email_address: 'nieprawidłowy adres email'
6 | email_address_not_routable: 'jest nieosiągalny'
7 | activerecord:
8 | <<: *errors
9 |
--------------------------------------------------------------------------------
/config/locales/tr.yml:
--------------------------------------------------------------------------------
1 | tr:
2 | activemodel: &errors
3 | errors:
4 | messages:
5 | invalid_email_address: 'geçerli bir eposta adresi değil'
6 | email_address_not_routable: 'ulaşılabilir değil'
7 | activerecord:
8 | <<: *errors
9 |
--------------------------------------------------------------------------------
/lib/validates_email_format_of/railtie.rb:
--------------------------------------------------------------------------------
1 | module ValidatesEmailFormatOf
2 | class Railtie < Rails::Railtie
3 | initializer "validates_email_format_of.load_i18n_locales" do |app|
4 | ValidatesEmailFormatOf.load_i18n_locales
5 | end
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | en:
2 | activemodel: &errors
3 | errors:
4 | messages:
5 | invalid_email_address: 'does not appear to be a valid email address'
6 | email_address_not_routable: 'is not routable'
7 | activerecord:
8 | <<: *errors
9 |
--------------------------------------------------------------------------------
/config/locales/id.yml:
--------------------------------------------------------------------------------
1 | id:
2 | activemodel: &errors
3 | errors:
4 | messages:
5 | invalid_email_address: 'tampaknya bukan alamat email yang valid'
6 | email_address_not_routable: 'tidak dapat dirutekan'
7 | activerecord:
8 | <<: *errors
9 |
--------------------------------------------------------------------------------
/config/locales/pt.yml:
--------------------------------------------------------------------------------
1 | pt:
2 | activemodel: &errors
3 | errors:
4 | messages:
5 | invalid_email_address: 'não parece ser um endereço de email válido'
6 | email_address_not_routable: 'não é acessível'
7 | activerecord:
8 | <<: *errors
9 |
--------------------------------------------------------------------------------
/config/locales/fr.yml:
--------------------------------------------------------------------------------
1 | fr:
2 | activemodel: &errors
3 | errors:
4 | messages:
5 | invalid_email_address: "ne semble pas être une adresse email valide"
6 | email_address_not_routable: "n'est pas routable"
7 | activerecord:
8 | <<: *errors
9 |
--------------------------------------------------------------------------------
/config/locales/it.yml:
--------------------------------------------------------------------------------
1 | it:
2 | activemodel: &errors
3 | errors:
4 | messages:
5 | invalid_email_address: 'non sembra un indirizzo email valido'
6 | email_address_not_routable: 'dominio non raggiungibile'
7 | activerecord:
8 | <<: *errors
9 |
--------------------------------------------------------------------------------
/config/locales/pt-BR.yml:
--------------------------------------------------------------------------------
1 | pt-BR:
2 | activemodel: &errors
3 | errors:
4 | messages:
5 | invalid_email_address: 'não parece ser um endereço de email válido'
6 | email_address_not_routable: 'não é acessível'
7 | activerecord:
8 | <<: *errors
9 |
--------------------------------------------------------------------------------
/config/locales/de.yml:
--------------------------------------------------------------------------------
1 | de:
2 | activemodel: &errors
3 | errors:
4 | messages:
5 | invalid_email_address: 'ist anscheinend keine gültige E-Mail-Adresse'
6 | email_address_not_routable: 'kann nicht erreicht werden'
7 | activerecord:
8 | <<: *errors
9 |
--------------------------------------------------------------------------------
/spec/rspec_matcher_spec.rb:
--------------------------------------------------------------------------------
1 | require "#{__dir__}/spec_helper"
2 | require "validates_email_format_of/rspec_matcher"
3 |
4 | class Person
5 | attr_accessor :email_address
6 | include ::ActiveModel::Validations
7 | validates_email_format_of :email_address
8 | end
9 |
10 | describe Person do
11 | it { should validate_email_format_of(:email_address) }
12 | end
13 |
--------------------------------------------------------------------------------
/lib/validates_email_format_of/rspec_matcher.rb:
--------------------------------------------------------------------------------
1 | require "validates_email_format_of"
2 |
3 | RSpec::Matchers.define :validate_email_format_of do |attribute|
4 | match do
5 | actual = subject.is_a?(Class) ? subject.new : subject
6 | actual.send(:"#{attribute}=", "invalid@example.")
7 | expect(actual).to be_invalid
8 | @expected_message ||= ValidatesEmailFormatOf.default_message
9 | expect(actual.errors.added?(attribute, @expected_message)).to be_truthy
10 | end
11 | chain :with_message do |message|
12 | @expected_message = message
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/Appraisals:
--------------------------------------------------------------------------------
1 | # run `bundle exec appraisal install` after making changes here
2 | appraise "rails-7.1" do
3 | gem "rails", "~> 7.1"
4 | end
5 |
6 | appraise "rails-7.0" do
7 | gem "rails", "~> 7.0"
8 | end
9 |
10 | appraise "rails-6.1" do
11 | gem "rails", "~> 6.1.0"
12 | end
13 |
14 | appraise "rails-6.0" do
15 | gem "rails", "~> 6.0.3", ">= 6.0.3.2"
16 | end
17 |
18 | appraise "rails-5.2" do
19 | gem "rails", "~> 5.2.4", ">= 5.2.4.3"
20 | gem "i18n", "< 1"
21 | end
22 |
23 | appraise "rails-5.1" do
24 | gem "rails", "~> 5.1.7"
25 | gem "i18n", "< 1"
26 | end
27 |
28 | appraise "rails-5.0" do
29 | gem "rails", "~> 5.0.7", ">= 5.0.7.2"
30 | gem "i18n", "< 1"
31 | end
32 |
33 | appraise "rails-4.2" do
34 | gem "rails", "~> 4.2.0"
35 | gem "i18n", "< 1"
36 | end
37 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | require "active_model"
2 | require "active_support"
3 |
4 | RSpec::Matchers.define :have_errors_on_email do
5 | match do |user|
6 | actual = user.errors.full_messages
7 | expect(user.errors.added?(:email, ValidatesEmailFormatOf::ERROR_MESSAGE_I18N_KEY))
8 | expect(actual).not_to be_nil, "#{actual} should not be nil"
9 | expect(actual).not_to be_empty, "#{actual} should not be empty"
10 | expect(actual.size).to eq(@reasons.size), "#{actual} should have #{@reasons.size} elements"
11 | @reasons.each do |reason|
12 | reason = "Email #{reason}"
13 | expect(actual).to include(reason), "#{actual} should contain #{reason}"
14 | end
15 | end
16 | chain :because do |reason|
17 | (@reasons ||= []) << reason
18 | end
19 | chain :and_because do |reason|
20 | (@reasons ||= []) << reason
21 | end
22 | match_when_negated do |user|
23 | expect(user.errors).to(be_empty)
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/lib/validates_email_format_of/active_model.rb:
--------------------------------------------------------------------------------
1 | require "validates_email_format_of"
2 | require "active_model"
3 |
4 | if ActiveModel::VERSION::MAJOR < 2 || (ActiveModel::VERSION::MAJOR == 2 && ActiveModel::VERSION::MINOR < 1)
5 | puts "WARNING: ActiveModel validation helper methods in validates_email_format_of gem are not compatible with ActiveModel < 2.1.0. Please use ValidatesEmailFormatOf::validate_email_format(email, options) or upgrade ActiveModel"
6 | end
7 |
8 | module ActiveModel
9 | module Validations
10 | class EmailFormatValidator < EachValidator
11 | def validate_each(record, attribute, value)
12 | (ValidatesEmailFormatOf.validate_email_format(value, options) || []).each do |error|
13 | record.errors.add(attribute, error)
14 | end
15 | end
16 | end
17 |
18 | module HelperMethods
19 | def validates_email_format_of(*attr_names)
20 | validates_with EmailFormatValidator, _merge_attributes(attr_names)
21 | end
22 | end
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/validates_email_format_of.gemspec:
--------------------------------------------------------------------------------
1 | $:.unshift File.expand_path("../lib", __FILE__)
2 | require "validates_email_format_of/version"
3 |
4 | Gem::Specification.new do |s|
5 | s.name = "validates_email_format_of"
6 | s.version = ValidatesEmailFormatOf::VERSION
7 | s.summary = "Validate email addresses against RFC 2822 and RFC 3696."
8 | s.description = s.summary
9 | s.authors = ["Alex Dunae", "Isaac Betesh"]
10 | s.email = ["code@dunae.ca", "iybetesh@gmail.com"]
11 | s.homepage = "https://github.com/validates-email-format-of/validates_email_format_of"
12 | s.license = "MIT"
13 | s.files = `git ls-files`.split($/)
14 | s.require_paths = ["lib"]
15 |
16 | if RUBY_VERSION < "1.9.3"
17 | s.add_dependency "i18n", "< 0.7.0"
18 | else
19 | s.add_dependency "i18n", ">= 0.8.0"
20 | end
21 |
22 | s.add_dependency "simpleidn"
23 | s.add_development_dependency "activemodel"
24 | s.add_development_dependency "bundler"
25 | s.add_development_dependency "rspec"
26 | s.add_development_dependency "standard"
27 | s.add_development_dependency "appraisal"
28 | end
29 |
--------------------------------------------------------------------------------
/MIT-LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2006-11 Alex Dunae
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | - pull_request
5 |
6 | permissions:
7 | contents: read
8 |
9 | jobs:
10 | test:
11 | name: "Ruby ${{ matrix.ruby }}, Rails ${{ matrix.gemfile }}"
12 |
13 | runs-on: ubuntu-latest
14 |
15 | strategy:
16 | matrix:
17 | ruby: ["2.6", "2.7", "3.0", "3.1", "3.2", "3.3", "jruby-head"]
18 | gemfile: ["4.2", "5.0", "5.1", "5.2", "6.0", "6.1", "7.0", "7.1"]
19 |
20 | exclude:
21 | - gemfile: "4.2"
22 | ruby: "3.0"
23 | - gemfile: "4.2"
24 | ruby: "3.1"
25 | - gemfile: "4.2"
26 | ruby: "3.2"
27 | - gemfile: "4.2"
28 | ruby: "3.3"
29 | - gemfile: "4.2"
30 | ruby: "jruby-head"
31 | - gemfile: "5.0"
32 | ruby: "3.0"
33 | - gemfile: "5.0"
34 | ruby: "3.1"
35 | - gemfile: "5.0"
36 | ruby: "3.2"
37 | - gemfile: "5.0"
38 | ruby: "3.3"
39 | - gemfile: "5.0"
40 | ruby: "jruby-head"
41 | - gemfile: "5.1"
42 | ruby: "3.0"
43 | - gemfile: "5.1"
44 | ruby: "3.1"
45 | - gemfile: "5.1"
46 | ruby: "3.2"
47 | - gemfile: "5.1"
48 | ruby: "3.3"
49 | - gemfile: "5.1"
50 | ruby: "jruby-head"
51 | - gemfile: "5.2"
52 | ruby: "3.0"
53 | - gemfile: "5.2"
54 | ruby: "3.1"
55 | - gemfile: "5.2"
56 | ruby: "3.2"
57 | - gemfile: "5.2"
58 | ruby: "3.3"
59 | - gemfile: "5.2"
60 | ruby: "jruby-head"
61 | - gemfile: "6.0"
62 | ruby: "3.2"
63 | - gemfile: "6.0"
64 | ruby: "3.3"
65 | - gemfile: "6.1"
66 | ruby: "3.2"
67 | - gemfile: "6.1"
68 | ruby: "3.3"
69 | - gemfile: "7.0"
70 | ruby: "2.5"
71 | - gemfile: "7.0"
72 | ruby: "2.6"
73 | - gemfile: "7.0"
74 | ruby: "2.7"
75 | - gemfile: "7.1"
76 | ruby: "2.5"
77 | - gemfile: "7.1"
78 | ruby: "2.6"
79 | - gemfile: "7.1"
80 | ruby: "2.7"
81 |
82 |
83 | env:
84 | BUNDLE_GEMFILE: gemfiles/rails_${{ matrix.gemfile }}.gemfile
85 | RAILS_ENV: test
86 |
87 | steps:
88 | - uses: actions/checkout@v4
89 |
90 | - name: "Install Ruby ${{ matrix.ruby }}"
91 | # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
92 | # change this to (see https://github.com/ruby/setup-ruby#versioning):
93 | # uses: ruby/setup-ruby@v1
94 | uses: ruby/setup-ruby@v1
95 | with:
96 | bundler: 1
97 | ruby-version: ${{ matrix.ruby }}
98 | bundler-cache: true # runs 'bundle install' and caches installed gems automatically
99 |
100 | - name: Run specs
101 | run: bundle exec rspec
102 |
103 | - name: Run standard.rb
104 | run: bundle exec rake standard
105 | if: ${{ ! startsWith(matrix.ruby, '2.') }}
106 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # CHANGELOG
2 |
3 | ## [Unreleased]
4 |
5 | [Unreleased]: https://github.com/validates-email-format-of/validates_email_format_of/compare/v1.8.2...master
6 |
7 | ## [1.8.2]
8 |
9 | * Improve German translations - https://github.com/validates-email-format-of/validates_email_format_of/pull/111
10 |
11 | [1.8.2]: https://github.com/validates-email-format-of/validates_email_format_of/compare/v1.8.1...1.8.2
12 |
13 | ## [1.8.1]
14 |
15 | * Fix IDN->Punycode conversion when domain names start with periods - https://github.com/validates-email-format-of/validates_email_format_of/issues/109
16 | * Add jruby to test matrix - https://github.com/validates-email-format-of/validates_email_format_of/pull/108
17 |
18 | [1.8.1]: https://github.com/validates-email-format-of/validates_email_format_of/compare/v1.8.0...1.8.1
19 |
20 | ## [1.8.0]
21 |
22 | * Add Internationalized Domain Name support - https://github.com/validates-email-format-of/validates_email_format_of/pull/103 - thanks https://github.com/sbilharz !
23 | * Add Turkish locale - https://github.com/validates-email-format-of/validates_email_format_of/pull/101 - thanks https://github.com/@krmbzds !
24 | * Added Indonesian locale - https://github.com/validates-email-format-of/validates_email_format_of/commit/129ebfc3a3b432b4df0334bcbdd74b1d17d765e0 - thanks https://github.com/khoerodin !
25 | * Fix inconsistent `generate_messages` behaviour - https://github.com/validates-email-format-of/validates_email_format_of/pull/105
26 | * ⚠️ Deprecate `:with` option - https://github.com/validates-email-format-of/validates_email_format_of/issues/42
27 | * Require i18n >= 0.8.0 in modern Ruby versions - https://github.com/advisories/GHSA-34hf-g744-jw64
28 |
29 | [1.8.0]: https://github.com/validates-email-format-of/validates_email_format_of/compare/v1.7.2...1.8.0
30 |
31 | ## [1.7.2]
32 |
33 | * Fix regression that disallowed domains starting with number - https://github.com/validates-email-format-of/validates_email_format_of/issues/88
34 |
35 | [1.7.2]: https://github.com/validates-email-format-of/validates_email_format_of/compare/v1.7.1...v1.7.2
36 |
37 | ## [1.7.1] (3 Aug 2022)
38 |
39 | * Fix invalid symbols being allowed in the local part - https://github.com/validates-email-format-of/validates_email_format_of/issues/86
40 | * Fix rspec_matcher when using a custom error message - https://github.com/validates-email-format-of/validates_email_format_of/pull/85 - thanks https://github.com/luuqnhu !
41 |
42 | [1.7.1]: https://github.com/validates-email-format-of/validates_email_format_of/compare/v1.7.0...v1.7.1
43 |
44 | ## [1.7.0] (29 July 2022)
45 |
46 | * Use Standard.rb for internal code formatting - https://github.com/validates-email-format-of/validates_email_format_of/commit/db1b0a86af58e478b7f9f2f269bf93bf48dc13c1
47 | * Add support for comments in the local part and improve quoted character handling - https://github.com/validates-email-format-of/validates_email_format_of/issues/69
48 | * Improve grammar for parsing domain part and validate domain part lengths - https://github.com/validates-email-format-of/validates_email_format_of/commit/2554b55e547c1fae6599d13b0c99296752888c91
49 | * Do not strip spaces before validating - https://github.com/validates-email-format-of/validates_email_format_of/issues/61 and https://github.com/validates-email-format-of/validates_email_format_of/issues/72
50 | * Allow setting check_mx_timeout and reduce the default timeout to 3 seconds - https://github.com/validates-email-format-of/validates_email_format_of/issues/66
51 | * Fix regex duplicate character warning - https://github.com/validates-email-format-of/validates_email_format_of/pull/71
52 | * Update CI to include Ruby 2.6 to 3.1 and Rails 4.2 to 7.0
53 |
54 | [1.7.0]: https://github.com/validates-email-format-of/validates_email_format_of/compare/v1.6.1...v1.7.0
55 | ## [1.6.1] (8 Sept 2014)
56 |
57 | * In a Rails context, this gem now uses ActiveModel's default logic for constructing I18n keys, to make it easier to override them on a model/attribute basis.
58 |
59 | [1.6.1]: https://github.com/validates-email-format-of/validates_email_format_of/compare/v1.6.0...v1.6.1
60 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # validates_email_format_of
2 |
3 | []( https://github.com/validates-email-format-of/validates_email_format_of/actions/workflows/ci.yml?query=branch%3Amaster)
4 |
5 | A Ruby gem to validate email addresses against RFC 2822 and RFC 5322, with optional domain name lookups.
6 |
7 | ## Why this email validator?
8 |
9 | This gem is the O.G. email validation gem for Rails. It was started back in 2006.
10 |
11 | Why use this validator? Instead of trying to validate email addresses with one giant regular expression, this library parses addresses character by character. This lets us handle weird cases like [nested comments](https://www.rfc-editor.org/rfc/rfc5322#appendix-A.5). Gross, but technically allowed.
12 |
13 | In reality, most email validating scripts will get you where you need to go. This library just aims to go all the way.
14 |
15 | ## Installation
16 |
17 | Add the gem to your Gemfile with:
18 |
19 | ```sh
20 | gem 'validates_email_format_of'
21 | ```
22 |
23 | ### Usage in a Rails app
24 |
25 | ```ruby
26 | class Person < ActiveRecord::Base
27 | validates :email, :email_format => { :message => "is not looking good" }
28 |
29 | # OR
30 |
31 | validates_email_format_of :email, :message => "is not looking good"
32 | end
33 | ```
34 |
35 | You can use the included `rspec` matcher as well:
36 |
37 | ```ruby
38 | require "validates_email_format_of/rspec_matcher"
39 |
40 | describe Person do
41 | it { should validate_email_format_of(:email).with_message("is not looking good") }
42 | end
43 | ```
44 |
45 | ### Usage without Rails
46 |
47 | ```ruby
48 | ValidatesEmailFormatOf::validate_email_format("example@mydomain.com") # => nil
49 | ValidatesEmailFormatOf::validate_email_format("invalid@") # => ["does not appear to be a valid email address"]
50 |
51 | # Optional, if you want error messages to be in your language
52 | ValidatesEmailFormatOf::load_i18n_locales
53 | I18n.locale = :pl
54 |
55 | ValidatesEmailFormatOf::validate_email_format("invalid@") # => ["nieprawidłowy adres email"]
56 | ```
57 |
58 | ## Internationalized Domain Names (IDN) and Punycode
59 |
60 | As of v1.8.0, this gem can validate email addresses using internationalized domain names (like `test@пример.рф`) as well as domains that have already been converted to Punycode code (like `test@xn--test@-3weu6azakd.xn--p1ai`).
61 |
62 | If you would like to forbid internationalized domains, you can pass the `idn: false` option. Punycode is always accepted.
63 |
64 |
65 | ## Options
66 |
67 | | Option | Type | Description |
68 | | --- | --- | --- |
69 | | `:message` | String | A custom error message when the email format is invalid (default is: "does not appear to be a valid email address") |
70 | | `:check_mx` | Boolean | Check domain for a valid MX record (default is false) |
71 | | `:check_mx_timeout` | Integer | Timeout in seconds for checking MX records before a `ResolvTimeout` is raised (default is 3). |
72 | | `:idn` | Boolean | Allowed internationalized domain names like `test@exämple.com` and `test@пример.рф`. Otherwise only domains that have already been converted to [Punycode](https://en.wikipedia.org/wiki/Punycode) are supported. (default is true) |
73 | | `:mx_message` | String | A custom error message when the domain does not match a valid MX record (default is: "is not routable"). Ignored unless :check_mx option is true. |
74 | | `:local_length` |Integer | Maximum number of characters allowed in the local part (everything before the '@') (default is 64) |
75 | | `:domain_length` | Integer | Maximum number of characters allowed in the domain part (everything after the '@') (default is 255) |
76 | | `:generate_message` | Boolean | Return the I18n key of the error message instead of the error message itself (default is false) |
77 |
78 | The standard ActiveModel validation options (`:on`, `:if`, `:unless`, `:allow_nil`, `:allow_blank`, etc...) all work as well when using the gem as part of a Rails application.
79 | ## Testing
80 |
81 | The gem is tested against Rails 4.2 and onward across a bunch of Ruby versions including jruby. You can see our [current Ruby and Rails test matrix here](.github/workflows/ci.yml).
82 |
83 | To execute the unit tests against [all the Rails versions we support run](gemfiles/) bundle exec appraisal rspec or run against an individual version with bundle exec appraisal rails-6.0 rspec.
84 | ## Contributing
85 |
86 | If you think we're letting some rules about valid email formats slip through the cracks, don't just update the parser. Instead, add a failing test and demonstrate that the described email address should be treated differently. A link to an appropriate RFC is the best way to do this. Then change the gem code to make the test pass.
87 |
88 | ```ruby
89 | describe "i_think_this_is_not_a_v@lid_email_addre.ss" do
90 | # According to http://..., this email address IS NOT valid.
91 | it { should have_errors_on_email.because("does not appear to be valid") }
92 | end
93 |
94 | describe "i_think_this_is_a_v@lid_email_addre.ss" do
95 | # According to http://..., this email address IS valid.
96 | it { should_not have_errors_on_email }
97 | end
98 | ```
99 |
100 | Yes, our Rspec syntax is that simple!
101 |
102 | ## Homepage
103 |
104 | * https://github.com/validates-email-format-of/validates_email_format_of
105 |
106 | ## Credits
107 |
108 | Written by [Alex Dunae](https://dunae.ca), 2006-24.
109 |
110 | Many thanks to the plugin's recent contributors: https://github.com/alexdunae/validates_email_format_of/contributors
111 |
112 | Thanks to [Francis Hwang](http://fhwang.net/) at Diversion Media for creating the 1.1 update.
113 |
114 | Thanks to Travis Sinnott for creating the 1.3 update.
115 |
116 | Thanks to Denis Ahearn at [Riverock Technologies](http://www.riverocktech.com/) for creating the 1.4 update.
117 |
118 | Thanks to [George Anderson](http://github.com/george) and ['history'](http://github.com/history) for creating the 1.4.1 update.
119 |
120 | Thanks to [Isaac Betesh](https://github.com/betesh) for converting tests to Rspec and refactoring for version 1.6.0.
121 |
--------------------------------------------------------------------------------
/lib/validates_email_format_of.rb:
--------------------------------------------------------------------------------
1 | require "validates_email_format_of/version"
2 | require "simpleidn"
3 |
4 | module ValidatesEmailFormatOf
5 | def self.load_i18n_locales
6 | require "i18n"
7 | I18n.load_path += Dir.glob(File.expand_path(File.join(File.dirname(__FILE__), "..", "config", "locales", "*.yml")))
8 | end
9 |
10 | require "resolv"
11 |
12 | # Characters that are allowed in to appear in the local part unquoted
13 | # https://www.rfc-editor.org/rfc/rfc5322#section-3.2.3
14 | #
15 | # An addr-spec is a specific Internet identifier that contains a
16 | # locally interpreted string followed by the at-sign character ("@",
17 | # ASCII value 64) followed by an Internet domain. The locally
18 | # interpreted string is either a quoted-string or a dot-atom. If the
19 | # string can be represented as a dot-atom (that is, it contains no
20 | # characters other than atext characters or "." surrounded by atext
21 | # characters), then the dot-atom form SHOULD be used and the quoted-
22 | # string form SHOULD NOT be used. Comments and folding white space
23 | # SHOULD NOT be used around the "@" in the addr-spec.
24 | #
25 | # atext = ALPHA / DIGIT /
26 | # "!" / "#" / "$" / "%" / "&" / "'" / "*" /
27 | # "+" / "-" / "/" / "=" / "?" / "^" / "_" /
28 | # "`" / "{" / "|" / "}" / "~"
29 | # dot-atom-text = 1*atext *("." 1*atext)
30 | # dot-atom = [CFWS] dot-atom-text [CFWS]
31 | ATEXT = /\A[A-Z0-9!\#$%&'*\-\/=?+\^_`{|}~]\z/i
32 |
33 | # Characters that are allowed to appear unquoted in comments
34 | # https://www.rfc-editor.org/rfc/rfc5322#section-3.2.2
35 | #
36 | # ctext = %d33-39 / %d42-91 / %d93-126
37 | # ccontent = ctext / quoted-pair / comment
38 | # comment = "(" *([FWS] ccontent) [FWS] ")"
39 | # CFWS = (1*([FWS] comment) [FWS]) / FWS
40 | CTEXT = /\A[#{Regexp.escape([33..39, 42..91, 93..126].map { |ascii_range| ascii_range.map(&:chr) }.flatten.join)}\s]/i
41 |
42 | # https://www.rfc-editor.org/rfc/rfc5322#section-3.2.4
43 | #
44 | # Strings of characters that include characters other than those
45 | # allowed in atoms can be represented in a quoted string format, where
46 | # the characters are surrounded by quote (DQUOTE, ASCII value 34)
47 | # characters.
48 | #
49 | # qtext = %d33 / ; Printable US-ASCII
50 | # %d35-91 / ; characters not including
51 | # %d93-126 / ; "\" or the quote character
52 | # obs-qtext
53 | #
54 | # qcontent = qtext / quoted-pair
55 | # quoted-string = [CFWS]
56 | # DQUOTE *([FWS] qcontent) [FWS] DQUOTE
57 | # [CFWS]
58 | QTEXT = /\A[#{Regexp.escape([33..33, 35..91, 93..126].map { |ascii_range| ascii_range.map(&:chr) }.flatten.join)}\s]/i
59 |
60 | IP_OCTET = /\A[0-9]+\Z/
61 |
62 | # From https://datatracker.ietf.org/doc/html/rfc1035#section-2.3.1
63 | #
64 | # > The labels must follow the rules for ARPANET host names. They must
65 | # > start with a letter, end with a letter or digit, and have as interior
66 | # > characters only letters, digits, and hyphen. There are also some
67 | # > restrictions on the length. Labels must be 63 characters or less.
68 | #
69 | #