diff --git a/app/helpers/email_helper.rb b/app/helpers/email_helper.rb deleted file mode 100644 index 0800601f98b..00000000000 --- a/app/helpers/email_helper.rb +++ /dev/null @@ -1,18 +0,0 @@ -# frozen_string_literal: true - -module EmailHelper - def self.included(base) - base.extend(self) - end - - def email_to_canonical_email(str) - username, domain = str.downcase.split('@', 2) - username, = username.delete('.').split('+', 2) - - "#{username}@#{domain}" - end - - def email_to_canonical_email_hash(str) - Digest::SHA2.new(256).hexdigest(email_to_canonical_email(str)) - end -end diff --git a/app/models/canonical_email_block.rb b/app/models/canonical_email_block.rb index d09df6f5e2a..4ed160fc262 100644 --- a/app/models/canonical_email_block.rb +++ b/app/models/canonical_email_block.rb @@ -12,24 +12,29 @@ # class CanonicalEmailBlock < ApplicationRecord - include EmailHelper + include CanonicalEmail include Paginable belongs_to :reference_account, class_name: 'Account', optional: true validates :canonical_email_hash, presence: true, uniqueness: true - scope :matching_email, ->(email) { where(canonical_email_hash: email_to_canonical_email_hash(email)) } + scope :matching_email, ->(email) { where(canonical_email_hash: digest(normalize_value_for(:email, email))) } + + def self.block?(email) + matching_email(email).exists? + end + + def self.digest(value) + Digest::SHA256.hexdigest(value) + end def to_log_human_identifier canonical_email_hash end def email=(email) - self.canonical_email_hash = email_to_canonical_email_hash(email) - end - - def self.block?(email) - matching_email(email).exists? + super + self.canonical_email_hash = self.class.digest(self.email) end end diff --git a/app/models/concerns/canonical_email.rb b/app/models/concerns/canonical_email.rb new file mode 100644 index 00000000000..bbc529ff085 --- /dev/null +++ b/app/models/concerns/canonical_email.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module CanonicalEmail + extend ActiveSupport::Concern + + included do + normalizes :email, with: ->(value) { canonicalize_email(value) } + end + + class_methods do + def canonicalize_email(email) + email + .downcase + .split('@', 2) + .then { |local, domain| [canonical_username(local), domain] } + .join('@') + end + + def canonical_username(username) + username + .to_s + .delete('.') + .split('+', 2) + .first + end + end +end diff --git a/spec/models/canonical_email_block_spec.rb b/spec/models/canonical_email_block_spec.rb index 0b8dcfb9c19..f531fd214e0 100644 --- a/spec/models/canonical_email_block_spec.rb +++ b/spec/models/canonical_email_block_spec.rb @@ -7,6 +7,14 @@ RSpec.describe CanonicalEmailBlock do it { is_expected.to belong_to(:reference_account).class_name('Account').optional } end + describe 'Normalizations' do + describe 'email' do + it { is_expected.to normalize(:email).from('TEST@HOST.EXAMPLE').to('test@host.example') } + it { is_expected.to normalize(:email).from('test+more@host.example').to('test@host.example') } + it { is_expected.to normalize(:email).from('test.user@host.example').to('testuser@host.example') } + end + end + describe 'Scopes' do describe '.matching_email' do subject { described_class.matching_email(email) }