Class: ActiveModel::Validations::NondisposableValidator
- Inherits:
-
EachValidator
- Object
- EachValidator
- ActiveModel::Validations::NondisposableValidator
- Defined in:
- lib/nondisposable/email_validator.rb
Instance Method Summary collapse
-
#validate_each(record, attribute, value) ⇒ Object
Three independent questions about one address, in the order a human would ask them, and AT MOST ONE error however many of them fire — stacking "provider is not allowed" on top of "did you mean gmail.com?" helps nobody:.
Instance Method Details
#validate_each(record, attribute, value) ⇒ Object
Three independent questions about one address, in the order a human would ask them, and AT MOST ONE error however many of them fire — stacking "provider is not allowed" on top of "did you mean gmail.com?" helps nobody:
1. Is this a throwaway provider? (always on — the gem's whole job)
2. Can we name the address they meant? (config.reject_lookalike_domains,
or any invalid TLD we can correct — see below)
3. Is the TLD real, and allowed? (config.check_tld)
WHY A NAMEABLE CORRECTION OUTRANKS THE GENERIC TLD ERROR
user@gmail.con fails both 2 and 3. "Doesn't look like a real email
address" is true but useless; "Did you mean user@gmail.com?" is the same
verdict with the fix attached. So whenever we can name a correction we
use that message, whether the TLD was invalid or merely lookalike. The
rule is: a suggestion, if we have one, always phrases the rejection.
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
# File 'lib/nondisposable/email_validator.rb', line 23 def validate_each(record, attribute, value) return if value.blank? begin domain = value.to_s.split('@').last&.downcase return if domain.nil? # Invalid email format config = Nondisposable.configuration if Nondisposable::DisposableDomain.disposable?(domain) return reject(record, attribute, [:message] || config.) end return unless config.check_tld || config.reject_lookalike_domains # No "@" at all is a malformed value, and this validator has never # judged shape — that is what `format:` is for (see the README). Note # `example@gmailmcom` DOES have one: a domain missing its dot is a real # address at an impossible domain, which is very much our business. return unless value.to_s.include?('@') return unless Nondisposable::Tld.judgeable?(value) # nil here means the domain has NO TLD (`example@gmailmcom` — a # production-shaped typo that missed the dot), which fails "must end in a real TLD" # just as surely as `.con` does. tld = Nondisposable::Tld.extract(value) invalid = config.check_tld && (tld.nil? || !Nondisposable::Tld.valid?(tld)) blocked = config.check_tld && !invalid && Nondisposable::Tld.blocked?(tld) if invalid || config.reject_lookalike_domains suggestion = Nondisposable::Suggestion.for(value) if suggestion return reject(record, attribute, config..sub('%{suggestion}', suggestion)) end end return reject(record, attribute, config.) if invalid return reject(record, attribute, config.) if blocked rescue StandardError => e if Nondisposable.configuration.on_check_failure == :reject Rails.logger.error "[nondisposable] Nondisposable validation error: #{e.} — rejecting #{attribute} (on_check_failure = :reject)" record.errors.add(attribute, "is an invalid email address, cannot check if it's disposable") else Rails.logger.error "[nondisposable] Nondisposable validation error: #{e.} — ALLOWING #{attribute} through without a disposable check (on_check_failure = :allow). Set config.on_check_failure = :reject to fail closed instead." end end end |