1class Domain < ActiveRecord::Base |
|
2 PATTERN = /\A(?:\*|\*\.[a-z]{2,}|(?:\*\.)*(?:[a-z0-9][a-z0-9-]{0,61}[a-z0-9]\.)+[a-z]{2,})\z/ |
|
4 with_options class_name: "::Domain" do |
|
5 belongs_to :canonical_domain, required: false |
|
6 has_many :aliases, foreign_key: "canonical_domain_id", dependent: :destroy |
|
7 end |
|
9 validates :name, presence: true |
|
10 validates :name, uniqueness: { case_sensitive: false } |
|
11 validates :name, format: { with: PATTERN } |
|
12 validates :name, length: { maximum: 100 } |
|
14 validates :strip_characters, length: { maximum: 10 } |
|
15 validates :strip_extension, length: { maximum: 10 } |
|
17 validate if: :aliased_domain? do |
|
18 if aliased_domain? && !canonical_domain.present? |
|
19 errors.add(:aliased_domain, :not_found) |
|
20 end |
|
21 end |
|
23 attr_writer :aliased_domain |
|
25 before_validation if: :aliased_domain? do |
|
26 self.canonical_domain = find_canonical_domain |
|
27 self.strip_characters = nil |
|
28 self.strip_extension = nil |
|
29 end |
|
31 before_validation unless: :aliased_domain? do |
|
32 self.canonical_domain = nil |
|
33 end |
|
35 class << self |
|
36 def default_scope |
|
37 preload(:canonical_domain) |
|
38 end |
|
40 def by_name |
|
41 order(:name) |
|
42 end |
|
44 def normalize(email) |
|
45 unless email.is_a?(Mail::Address) |
|
46 email = Mail::Address.new(email) |
|
47 end |
|
49 rule(email.domain).normalize(email)
|
|
50 rescue Mail::Field::ParseError |
|
51 email
|
|
52 end |
|
54 private
|
|
|
56 def candidates(domain) |
57 [domain].tap { |c| domain.scan(?.) { c << %[*.#{$'}] } } + %w[*] |
|
58 end |
|
|
60 def rules(domain) |
61 candidates(domain).lazy.map { |c| find_by(name: c) } |
|
62 end |
|
|
64 def rule(domain) |
65 rules(domain).detect(-> { default_domain }) { |d| d.present? }
|
|
66 end |
|
|
68 def default_domain |
69 begin |
|
70 find_or_create_by(name: "*", strip_extension: "+") |
|
71 rescue ActiveRecord::RecordNotUnique => e |
|
72 retry |
|
73 end |
|
74 end |
|
75 end |
|
|
77 def aliased_domain |
78 @aliased_domain || canonical_domain.try(:name) |
|
79 end |
|
|
81 def aliased_domain? |
82 aliased_domain.present?
|
|
83 end |
|
|
85 def aliased_domains |
86 aliases.by_name.pluck(:name).join(", ") |
|
87 end |
|
|
89 def alias? |
90 canonical_domain.present?
|
|
91 end |
|
|
93 def alias |
94 canonical_domain.name
|
|
95 end |
|
|
97 def name=(value) |
98 super(value.to_s.downcase.strip) |
|
99 end |
|
|
101 def strip_characters? |
102 alias? ? canonical_domain.strip_characters? : super |
|
103 end |
|
|
105 def strip_characters |
106 alias? ? canonical_domain.strip_characters : super |
|
107 end |
|
|
109 def strip_extension? |
110 alias? ? canonical_domain.strip_extension? : super |
|
111 end |
|
|
113 def strip_extension |
114 alias? ? canonical_domain.strip_extension : super |
|
115 end |
|
|
117 def normalize(email) |
118 "#{local(email)}@#{domain(email)}" |
|
119 rescue Mail::Field::ParseError |
|
120 email
|
|
121 end |
|
123 private
|
|
|
125 def find_canonical_domain |
126 self.class.find_by(name: aliased_domain) |
|
127 end |
|
|
129 def local(email) |
130 email.local.dup.tap do |normalized| |
|
131 if strip_characters? |
|
132 normalized.gsub!(characters_regexp, "") |
|
133 end |
|
135 if strip_extension? |
|
136 normalized.gsub!(extension_regexp, "\\1") |
|
137 end |
|
138 end |
|
139 end |
|
|
141 def domain(email) |
142 alias? ? canonical_domain.name : email.domain
|
|
143 end |
|
|
145 def characters_regexp |
146 Regexp.union(strip_characters.chars) |
|
147 end |
|
|
149 def extension_regexp |
150 range = Regexp.escape(strip_extension) |
|
151 /\A([^#{range}]+)[#{range}].+\z/ |
|
152 end |
|
153end |