Skip to content

Commit

Permalink
add validator for typoed emails
Browse files Browse the repository at this point in the history
It happens quite often that people typo their email address (I blame
phone keyboards for that).  Catch common cases in a validator and
tell the user that they have a typo in their email.

Why yes, I did write the tests for this first, thanks for asking!
  • Loading branch information
nilsding committed Jan 11, 2022
1 parent 515e6d0 commit 29923fa
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 1 deletion.
2 changes: 1 addition & 1 deletion app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class User < ApplicationRecord
screen_name.strip!
end

validates :email, fake_email: true
validates :email, fake_email: true, typoed_email: true
validates :screen_name, presence: true, format: { with: SCREEN_NAME_REGEX }, uniqueness: { case_sensitive: false }, screen_name: true

mount_uploader :profile_picture, ProfilePictureUploader, mount_on: :profile_picture_file_name
Expand Down
44 changes: 44 additions & 0 deletions app/validators/typoed_email_validator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# frozen_string_literal: true

class TypoedEmailValidator < ActiveModel::EachValidator
# this array contains "forbidden" email address endings
INVALID_ENDINGS = [
# without @:
*%w[
.con
.coom
],

# with @:
*%w[
fmail.com
gemail.com
gmail.co
gmaile.com
gmaill.com
icluod.com
proton.mail
].map { "@#{_1}" }
].freeze

def validate_each(record, attribute, value)
return if valid?(value)

record.errors[attribute] << "contains a typo"
end

private

def valid?(value)
# needs an @
return false unless value.include?('@')

# part after the @ needs to have at least one period
return false if value.split('@', 2).last.count('.') == 0

# finally, common typos
return false if INVALID_ENDINGS.any? { value.end_with?(_1) }

true
end
end
55 changes: 55 additions & 0 deletions spec/models/user_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,61 @@
end
end

describe 'email validation' do
subject do
FactoryBot.build(:user, email: email).tap(&:validate).errors[:email]
end

shared_examples_for 'valid email' do |example_email|
context "when email is #{example_email}" do
let(:email) { example_email }

it "does not have validation errors" do
expect(subject).to be_empty
end
end
end

shared_examples_for 'invalid email' do |example_email|
context "when email is #{example_email}" do
let(:email) { example_email }

it "has validation errors" do
expect(subject).not_to be_empty
end
end
end

include_examples 'valid email', '[email protected]'
include_examples 'valid email', '[email protected]'
include_examples 'valid email', '[email protected]'
include_examples 'valid email', '[email protected]'
include_examples 'valid email', 'fritz.fantom@enterprise.k8s.420stripes.k8s.needs.more.k8s.jira.atlassian.k8s.eu-central-1.s3.amazonaws.com'
include_examples 'invalid email', '@jack'

# examples from the real world:

# .con is not a valid TLD
include_examples 'invalid email', '[email protected]'
include_examples 'invalid email', '[email protected]'
# neither is .coom
include_examples 'invalid email', '[email protected]'
# common typos:
include_examples 'invalid email', '[email protected]'
include_examples 'invalid email', '[email protected]'
include_examples 'invalid email', '[email protected]'
include_examples 'invalid email', 'fritz.fantom@gmailcom'
include_examples 'invalid email', '[email protected]'
include_examples 'invalid email', '[email protected]'
include_examples 'invalid email', 'fritz.fantom@hotmailcom'
include_examples 'invalid email', '[email protected]'
# no TLD
include_examples 'invalid email', 'fritz.fantom@gmail'
include_examples 'invalid email', 'fritz.fantom@protonmail'
# not registered as of 2022-01-11
include_examples 'invalid email', '[email protected]'
end

# -- User::TimelineMethods --

shared_examples_for 'result is blank' do
Expand Down

0 comments on commit 29923fa

Please sign in to comment.