From dee4b8dbeb661fe0f8dd1df421c743f8b94af5c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Schr=C3=B6der?= Date: Sun, 22 Feb 2026 16:48:16 +0100 Subject: [PATCH 1/2] specific error messages --- Gemfile | 4 ++++ lib/valid_email2/email_validator.rb | 34 +++++++++++++++++++---------- spec/spec_helper.rb | 2 ++ spec/valid_email2_spec.rb | 29 ++++++++++++++++++++++++ 4 files changed, 57 insertions(+), 12 deletions(-) diff --git a/Gemfile b/Gemfile index 26850508..8616e82b 100644 --- a/Gemfile +++ b/Gemfile @@ -2,3 +2,7 @@ source 'https://rubygems.org' # Specify your gem's dependencies in valid_email2.gemspec gemspec + +group :development do + gem "ruby-lsp-rspec", require: false +end diff --git a/lib/valid_email2/email_validator.rb b/lib/valid_email2/email_validator.rb index c14e5571..a88f77f5 100644 --- a/lib/valid_email2/email_validator.rb +++ b/lib/valid_email2/email_validator.rb @@ -6,7 +6,16 @@ module ValidEmail2 class EmailValidator < ActiveModel::EachValidator def default_options - { disposable: false, mx: false, strict_mx: false, disallow_subaddressing: false, multiple: false, dns_timeout: 5, dns_nameserver: nil } + { + disposable: false, + mx: false, + strict_mx: false, + disallow_subaddressing: false, + multiple: false, + dns_timeout: 5, + dns_nameserver: nil, + specific_error_messages: false + } end def validate_each(record, attribute, value) @@ -19,39 +28,39 @@ def validate_each(record, attribute, value) error(record, attribute) && return unless addresses.all?(&:valid?) if options[:disallow_dotted] - error(record, attribute) && return if addresses.any?(&:dotted?) + error(record, attribute, :disallow_dotted) && return if addresses.any?(&:dotted?) end if options[:disallow_subaddressing] - error(record, attribute) && return if addresses.any?(&:subaddressed?) + error(record, attribute, :disallow_subaddressing) && return if addresses.any?(&:subaddressed?) end if options[:disposable] - error(record, attribute) && return if addresses.any?(&:disposable?) + error(record, attribute, :disposable) && return if addresses.any?(&:disposable?) end if options[:disposable_domain] - error(record, attribute) && return if addresses.any?(&:disposable_domain?) + error(record, attribute, :disposable_domain) && return if addresses.any?(&:disposable_domain?) end if options[:disposable_with_allow_list] - error(record, attribute) && return if addresses.any? { |address| address.disposable? && !address.allow_listed? } + error(record, attribute, :disposable_with_allow_list) && return if addresses.any? { |address| address.disposable? && !address.allow_listed? } end if options[:disposable_domain_with_allow_list] - error(record, attribute) && return if addresses.any? { |address| address.disposable_domain? && !address.allow_listed? } + error(record, attribute, :disposable_domain_with_allow_list) && return if addresses.any? { |address| address.disposable_domain? && !address.allow_listed? } end if options[:deny_list] - error(record, attribute) && return if addresses.any?(&:deny_listed?) + error(record, attribute, :deny_list) && return if addresses.any?(&:deny_listed?) end if options[:mx] - error(record, attribute) && return unless addresses.all?(&:valid_mx?) + error(record, attribute, :mx) && return unless addresses.all?(&:valid_mx?) end if options[:strict_mx] - error(record, attribute) && return unless addresses.all?(&:valid_strict_mx?) + error(record, attribute, :strict_mx) && return unless addresses.all?(&:valid_strict_mx?) end end @@ -67,10 +76,11 @@ def sanitized_values(input) email_list.reject(&:empty?) end - def error(record, attribute) + def error(record, attribute, error_key = :invalid) message = options[:message].respond_to?(:call) ? options[:message].call : options[:message] + message ||= options[:specific_error_messages] ? error_key : :invalid - record.errors.add(attribute, message || :invalid) + record.errors.add(attribute, message) end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 6c67224e..9ab414db 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,4 +1,6 @@ $:.unshift File.expand_path("../lib",__FILE__) +require "bundler/setup" +require "rspec" require "valid_email2" require "debug" diff --git a/spec/valid_email2_spec.rb b/spec/valid_email2_spec.rb index 83c63d6f..9d3ad9ec 100644 --- a/spec/valid_email2_spec.rb +++ b/spec/valid_email2_spec.rb @@ -7,6 +7,10 @@ class TestUser < TestModel validates :email, 'valid_email_2/email': true end +class TestUserSpecificErrorMessage < TestModel + validates :email, 'valid_email_2/email': { disallow_dotted: true, specific_error_messages: true } +end + class TestUserDotted < TestModel validates :email, 'valid_email_2/email': { disallow_dotted: true } end @@ -418,6 +422,7 @@ def set_allow_list it "is invalid when address cotains dots" do user = TestUserDotted.new(email: "john.doe@gmail.com") expect(user.valid?).to be_falsey + expect(user.errors.full_messages).to include("Email is invalid") end end @@ -433,6 +438,30 @@ def set_allow_list end end + context "when detail error messages are enabled" do + describe "with custom error message" do + it "supports settings a custom error message" do + with_translations(:en, { errors: { messages: { disallow_dotted: "dotted message" } } }) do + user = TestUserSpecificErrorMessage.new(email: "john.doe@gmail.com") + user.valid? + + expect(user.errors.full_messages).to include("Email dotted message") + end + end + end + end + + def with_translations(locale, translations) + original_backend = I18n.backend + + I18n.backend = I18n::Backend::KeyValue.new({}, true) + I18n.backend.store_translations locale, translations + + yield + ensure + I18n.backend = original_backend + end + context "when message is present" do describe "with custom error message" do it "supports settings a custom error message" do From 7173f17c6124a2ac83b5eb0c6f05e0d01cb3f12f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Schr=C3=B6der?= Date: Sun, 22 Feb 2026 16:56:51 +0100 Subject: [PATCH 2/2] add to readme --- README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/README.md b/README.md index 932443ed..6b5557a4 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,26 @@ To validate create your own custom message: validates :email, 'valid_email_2/email': { message: "is not a valid email" } ``` +To validate create your dedicated validation error-message: +```yaml +# set custom validation messages for each type of check in translations ie en.yml +errors: + messages: + disallow_dotted: "contains a dot" + disallow_subaddressing: "..." + disposable: "..." + disposable_domain: "..." + disposable_with_allow_list: "..." + disposable_domain_with_allow_list: "..." + deny_list: "..." + mx: "..." + strict_mx: "..." +``` + +```ruby +validates :email, 'valid_email_2/email': { disallow_dotted: true, specific_error_messages: true } +``` + To allow multiple addresses separated by comma: ```ruby validates :email, 'valid_email_2/email': { multiple: true }