email_domain_checker 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/README.md +27 -112
- data/docs/configuration/cache.md +165 -0
- data/docs/configuration/options.md +68 -0
- data/docs/configuration/test-mode.md +40 -0
- data/docs/contributing.md +9 -0
- data/docs/getting-started/installation.md +28 -0
- data/docs/getting-started/quick-start.md +47 -0
- data/docs/index.md +37 -0
- data/docs/usage/activemodel-integration.md +68 -0
- data/docs/usage/checker-class.md +60 -0
- data/docs/usage/module-methods.md +95 -0
- data/docs-templates/index.html +71 -0
- data/lib/email_domain_checker/cache/base_adapter.rb +67 -0
- data/lib/email_domain_checker/cache/memory_adapter.rb +91 -0
- data/lib/email_domain_checker/cache/redis_adapter.rb +66 -0
- data/lib/email_domain_checker/cache.rb +54 -0
- data/lib/email_domain_checker/config.rb +116 -2
- data/lib/email_domain_checker/dns_resolver.rb +26 -3
- data/lib/email_domain_checker/domain_validator.rb +2 -1
- data/lib/email_domain_checker/version.rb +1 -1
- data/lib/email_domain_checker.rb +32 -0
- data/mkdocs.yml +65 -0
- data/requirements.txt +2 -0
- metadata +18 -1
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Using Checker Class
|
|
2
|
+
|
|
3
|
+
The `EmailDomainChecker::Checker` class provides a more object-oriented approach to email validation.
|
|
4
|
+
|
|
5
|
+
## Basic Usage
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
require 'email_domain_checker'
|
|
9
|
+
|
|
10
|
+
# Basic validation
|
|
11
|
+
checker = EmailDomainChecker::Checker.new("user@example.com")
|
|
12
|
+
if checker.valid?
|
|
13
|
+
puts "Valid email with valid domain"
|
|
14
|
+
end
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Validation Methods
|
|
18
|
+
|
|
19
|
+
### Format Validation Only
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
checker = EmailDomainChecker::Checker.new("user@example.com", validate_domain: false)
|
|
23
|
+
checker.format_valid? # => true
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Domain Validation with MX Records
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
29
|
+
checker = EmailDomainChecker::Checker.new("user@example.com", check_mx: true)
|
|
30
|
+
checker.domain_valid? # => true if MX records exist
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Email Transformations
|
|
34
|
+
|
|
35
|
+
### Normalized Email
|
|
36
|
+
|
|
37
|
+
Get the normalized (lowercase) version of the email:
|
|
38
|
+
|
|
39
|
+
```ruby
|
|
40
|
+
checker = EmailDomainChecker::Checker.new("User@Example.COM")
|
|
41
|
+
checker.normalized_email # => "user@example.com"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Canonical Email
|
|
45
|
+
|
|
46
|
+
Get the canonical version of the email (handles Gmail-style aliases):
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
checker = EmailDomainChecker::Checker.new("user.name+tag@gmail.com")
|
|
50
|
+
checker.canonical_email # => "username@gmail.com"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Redacted Email
|
|
54
|
+
|
|
55
|
+
Get a redacted version of the email for privacy (shows hash instead of local part):
|
|
56
|
+
|
|
57
|
+
```ruby
|
|
58
|
+
checker = EmailDomainChecker::Checker.new("user@example.com")
|
|
59
|
+
checker.redacted_email # => "{hash}@example.com"
|
|
60
|
+
```
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Module-level Convenience Methods
|
|
2
|
+
|
|
3
|
+
The `EmailDomainChecker` module provides convenient class methods for quick validation and configuration.
|
|
4
|
+
|
|
5
|
+
## Validation Methods
|
|
6
|
+
|
|
7
|
+
### `valid?`
|
|
8
|
+
|
|
9
|
+
Quick validation with optional domain checking:
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
EmailDomainChecker.valid?("user@example.com", validate_domain: false) # => true
|
|
13
|
+
EmailDomainChecker.valid?("user@example.com", check_mx: true) # => true/false
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
### `format_valid?`
|
|
17
|
+
|
|
18
|
+
Validate email format only (skips domain validation):
|
|
19
|
+
|
|
20
|
+
```ruby
|
|
21
|
+
EmailDomainChecker.format_valid?("user@example.com") # => true
|
|
22
|
+
EmailDomainChecker.format_valid?("invalid-email") # => false
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### `domain_valid?`
|
|
26
|
+
|
|
27
|
+
Validate domain only (skips format validation):
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
EmailDomainChecker.domain_valid?("user@example.com", check_mx: true) # => true/false
|
|
31
|
+
EmailDomainChecker.domain_valid?("user@nonexistent.com", check_mx: true) # => false
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Utility Methods
|
|
35
|
+
|
|
36
|
+
### `normalize`
|
|
37
|
+
|
|
38
|
+
Normalize email address to lowercase:
|
|
39
|
+
|
|
40
|
+
```ruby
|
|
41
|
+
EmailDomainChecker.normalize("User@Example.COM") # => "user@example.com"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Configuration
|
|
45
|
+
|
|
46
|
+
### Global Configuration
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
EmailDomainChecker.configure(timeout: 10, check_mx: true)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Block Configuration
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
EmailDomainChecker.configure do |config|
|
|
56
|
+
config.test_mode = true
|
|
57
|
+
config.cache_ttl = 3600
|
|
58
|
+
config.cache_enabled = true
|
|
59
|
+
end
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Cache Management
|
|
63
|
+
|
|
64
|
+
### Clear All Cache
|
|
65
|
+
|
|
66
|
+
```ruby
|
|
67
|
+
EmailDomainChecker.clear_cache
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Clear Cache for Specific Domain
|
|
71
|
+
|
|
72
|
+
```ruby
|
|
73
|
+
EmailDomainChecker.clear_cache_for_domain("example.com")
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Using Cache with Blocks
|
|
77
|
+
|
|
78
|
+
```ruby
|
|
79
|
+
# Method 1: Direct access via EmailDomainChecker.cache (recommended)
|
|
80
|
+
result = EmailDomainChecker.cache.with("my_key", ttl: 3600) do
|
|
81
|
+
# This block executes only when cache misses
|
|
82
|
+
expensive_computation
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Method 2: Using convenience method
|
|
86
|
+
result = EmailDomainChecker.with_cache("my_key", ttl: 3600) do
|
|
87
|
+
expensive_computation
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Force cache refresh
|
|
91
|
+
result = EmailDomainChecker.with_cache("my_key", force: true) do
|
|
92
|
+
# This block always executes
|
|
93
|
+
updated_computation
|
|
94
|
+
end
|
|
95
|
+
```
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>EmailDomainChecker Documentation</title>
|
|
7
|
+
<style>
|
|
8
|
+
body {
|
|
9
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
10
|
+
max-width: 800px;
|
|
11
|
+
margin: 0 auto;
|
|
12
|
+
padding: 2rem;
|
|
13
|
+
background: #f5f5f5;
|
|
14
|
+
}
|
|
15
|
+
.container {
|
|
16
|
+
background: white;
|
|
17
|
+
border-radius: 8px;
|
|
18
|
+
padding: 2rem;
|
|
19
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
20
|
+
}
|
|
21
|
+
h1 {
|
|
22
|
+
color: #333;
|
|
23
|
+
margin-bottom: 0.5rem;
|
|
24
|
+
}
|
|
25
|
+
.subtitle {
|
|
26
|
+
color: #666;
|
|
27
|
+
margin-bottom: 2rem;
|
|
28
|
+
}
|
|
29
|
+
.versions {
|
|
30
|
+
list-style: none;
|
|
31
|
+
padding: 0;
|
|
32
|
+
}
|
|
33
|
+
.versions li {
|
|
34
|
+
margin: 0.5rem 0;
|
|
35
|
+
}
|
|
36
|
+
.versions a {
|
|
37
|
+
display: inline-block;
|
|
38
|
+
padding: 0.75rem 1.5rem;
|
|
39
|
+
background: #6200ea;
|
|
40
|
+
color: white;
|
|
41
|
+
text-decoration: none;
|
|
42
|
+
border-radius: 4px;
|
|
43
|
+
transition: background 0.2s;
|
|
44
|
+
}
|
|
45
|
+
.versions a:hover {
|
|
46
|
+
background: #7c3aed;
|
|
47
|
+
}
|
|
48
|
+
.version-label {
|
|
49
|
+
font-weight: 600;
|
|
50
|
+
margin-right: 0.5rem;
|
|
51
|
+
}
|
|
52
|
+
.latest-badge {
|
|
53
|
+
background: #4caf50;
|
|
54
|
+
color: white;
|
|
55
|
+
padding: 0.2rem 0.5rem;
|
|
56
|
+
border-radius: 3px;
|
|
57
|
+
font-size: 0.8rem;
|
|
58
|
+
margin-left: 0.5rem;
|
|
59
|
+
}
|
|
60
|
+
</style>
|
|
61
|
+
</head>
|
|
62
|
+
<body>
|
|
63
|
+
<div class="container">
|
|
64
|
+
<h1>EmailDomainChecker Documentation</h1>
|
|
65
|
+
<p class="subtitle">Select a version to view documentation</p>
|
|
66
|
+
<ul class="versions">
|
|
67
|
+
{{VERSION_LIST}}
|
|
68
|
+
</ul>
|
|
69
|
+
</div>
|
|
70
|
+
</body>
|
|
71
|
+
</html>
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module EmailDomainChecker
|
|
4
|
+
module Cache
|
|
5
|
+
# Base class for cache adapters
|
|
6
|
+
# All cache adapters must implement these methods
|
|
7
|
+
class BaseAdapter
|
|
8
|
+
# Get cached value for a key
|
|
9
|
+
# @param key [String] cache key
|
|
10
|
+
# @return [Object, nil] cached value or nil if not found
|
|
11
|
+
def get(key)
|
|
12
|
+
raise NotImplementedError, "Subclasses must implement #get"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Set a value in cache
|
|
16
|
+
# @param key [String] cache key
|
|
17
|
+
# @param value [Object] value to cache
|
|
18
|
+
# @param ttl [Integer, nil] time to live in seconds (nil for no expiration)
|
|
19
|
+
# @return [void]
|
|
20
|
+
def set(key, value, ttl: nil)
|
|
21
|
+
raise NotImplementedError, "Subclasses must implement #set"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Delete a key from cache
|
|
25
|
+
# @param key [String] cache key
|
|
26
|
+
# @return [void]
|
|
27
|
+
def delete(key)
|
|
28
|
+
raise NotImplementedError, "Subclasses must implement #delete"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Clear all cache entries
|
|
32
|
+
# @return [void]
|
|
33
|
+
def clear
|
|
34
|
+
raise NotImplementedError, "Subclasses must implement #clear"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Check if a key exists in cache
|
|
38
|
+
# @param key [String] cache key
|
|
39
|
+
# @return [Boolean] true if key exists
|
|
40
|
+
def exists?(key)
|
|
41
|
+
get(key) != nil
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Fetch value from cache or execute block and cache the result
|
|
45
|
+
# Similar to Rails.cache.fetch
|
|
46
|
+
# @param key [String] cache key
|
|
47
|
+
# @param ttl [Integer, nil] time to live in seconds (nil for no expiration)
|
|
48
|
+
# @param force [Boolean] if true, always execute block and update cache
|
|
49
|
+
# @yield Block to execute when cache miss
|
|
50
|
+
# @return [Object] cached value or block result
|
|
51
|
+
def with(key, ttl: nil, force: false, &block)
|
|
52
|
+
raise ArgumentError, "Block is required" unless block_given?
|
|
53
|
+
|
|
54
|
+
# Return cached value if not forcing and cache exists
|
|
55
|
+
unless force
|
|
56
|
+
return get(key) if exists?(key)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Execute block and cache the result
|
|
60
|
+
# Rails.cache.fetch also caches nil values, so we do the same
|
|
61
|
+
value = yield
|
|
62
|
+
set(key, value, ttl: ttl)
|
|
63
|
+
value
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_adapter"
|
|
4
|
+
|
|
5
|
+
module EmailDomainChecker
|
|
6
|
+
module Cache
|
|
7
|
+
# In-memory cache adapter using a simple hash
|
|
8
|
+
# This is the default cache adapter when Redis is not available
|
|
9
|
+
class MemoryAdapter < BaseAdapter
|
|
10
|
+
def initialize
|
|
11
|
+
@store = {}
|
|
12
|
+
@expirations = {}
|
|
13
|
+
@nil_keys = {} # Track keys that have nil values
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def get(key)
|
|
17
|
+
# Check if key exists (including nil values)
|
|
18
|
+
return nil unless @store.key?(key) || @nil_keys.key?(key)
|
|
19
|
+
|
|
20
|
+
# Check if expired
|
|
21
|
+
if @expirations.key?(key) && Time.now > @expirations[key]
|
|
22
|
+
delete(key)
|
|
23
|
+
return nil
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Return nil if it was explicitly cached, otherwise return stored value
|
|
27
|
+
return nil if @nil_keys.key?(key)
|
|
28
|
+
|
|
29
|
+
@store[key]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def set(key, value, ttl: nil)
|
|
33
|
+
if value.nil?
|
|
34
|
+
# Store nil separately to distinguish from "not cached"
|
|
35
|
+
@nil_keys[key] = true
|
|
36
|
+
@store.delete(key)
|
|
37
|
+
else
|
|
38
|
+
@store[key] = value
|
|
39
|
+
@nil_keys.delete(key)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
if ttl
|
|
43
|
+
@expirations[key] = Time.now + ttl
|
|
44
|
+
else
|
|
45
|
+
@expirations.delete(key)
|
|
46
|
+
end
|
|
47
|
+
value
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def delete(key)
|
|
51
|
+
@store.delete(key)
|
|
52
|
+
@nil_keys.delete(key)
|
|
53
|
+
@expirations.delete(key)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def clear
|
|
57
|
+
@store.clear
|
|
58
|
+
@nil_keys.clear
|
|
59
|
+
@expirations.clear
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def exists?(key)
|
|
63
|
+
return false unless @store.key?(key) || @nil_keys.key?(key)
|
|
64
|
+
|
|
65
|
+
# Check if expired
|
|
66
|
+
if @expirations.key?(key) && Time.now > @expirations[key]
|
|
67
|
+
delete(key)
|
|
68
|
+
return false
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
true
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Get cache size (for debugging/monitoring)
|
|
75
|
+
def size
|
|
76
|
+
# Clean expired entries first
|
|
77
|
+
clean_expired
|
|
78
|
+
@store.size + @nil_keys.size
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
def clean_expired
|
|
84
|
+
now = Time.now
|
|
85
|
+
@expirations.each do |key, expiration|
|
|
86
|
+
delete(key) if now > expiration
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_adapter"
|
|
4
|
+
|
|
5
|
+
module EmailDomainChecker
|
|
6
|
+
module Cache
|
|
7
|
+
# Redis cache adapter
|
|
8
|
+
# Requires the 'redis' gem to be available
|
|
9
|
+
class RedisAdapter < BaseAdapter
|
|
10
|
+
def initialize(redis_client = nil)
|
|
11
|
+
@redis = redis_client || create_default_redis_client
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def get(key)
|
|
15
|
+
value = @redis.get(key)
|
|
16
|
+
return nil unless value
|
|
17
|
+
|
|
18
|
+
# Parse JSON value
|
|
19
|
+
JSON.parse(value)
|
|
20
|
+
rescue JSON::ParserError, Redis::BaseError
|
|
21
|
+
nil
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def set(key, value, ttl: nil)
|
|
25
|
+
json_value = JSON.generate(value)
|
|
26
|
+
if ttl
|
|
27
|
+
@redis.setex(key, ttl, json_value)
|
|
28
|
+
else
|
|
29
|
+
@redis.set(key, json_value)
|
|
30
|
+
end
|
|
31
|
+
value
|
|
32
|
+
rescue Redis::BaseError
|
|
33
|
+
# Silently fail if Redis is unavailable
|
|
34
|
+
value
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def delete(key)
|
|
38
|
+
@redis.del(key)
|
|
39
|
+
rescue Redis::BaseError
|
|
40
|
+
# Silently fail if Redis is unavailable
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def clear
|
|
44
|
+
@redis.flushdb
|
|
45
|
+
rescue Redis::BaseError
|
|
46
|
+
# Silently fail if Redis is unavailable
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def exists?(key)
|
|
50
|
+
@redis.exists?(key)
|
|
51
|
+
rescue Redis::BaseError
|
|
52
|
+
false
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def create_default_redis_client
|
|
58
|
+
require "redis"
|
|
59
|
+
require "json"
|
|
60
|
+
Redis.new
|
|
61
|
+
rescue LoadError
|
|
62
|
+
raise LoadError, "Redis gem is required for RedisAdapter. Please add 'gem \"redis\"' to your Gemfile."
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "cache/base_adapter"
|
|
4
|
+
require_relative "cache/memory_adapter"
|
|
5
|
+
|
|
6
|
+
# Conditionally load Redis adapter if available
|
|
7
|
+
begin
|
|
8
|
+
require "redis"
|
|
9
|
+
require "json"
|
|
10
|
+
require_relative "cache/redis_adapter"
|
|
11
|
+
rescue LoadError
|
|
12
|
+
# Redis is not available, skip Redis adapter
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
module EmailDomainChecker
|
|
16
|
+
module Cache
|
|
17
|
+
# Factory method to create cache adapter based on configuration
|
|
18
|
+
#
|
|
19
|
+
# @param type [Symbol, Class, Object] Cache adapter type, class, or instance
|
|
20
|
+
# - Symbol: :memory or :redis
|
|
21
|
+
# - Class: A custom adapter class (must inherit from BaseAdapter)
|
|
22
|
+
# - Object: A custom adapter instance (must be an instance of BaseAdapter)
|
|
23
|
+
# @param redis_client [Redis, nil] Redis client instance (only used for :redis type)
|
|
24
|
+
# @return [BaseAdapter] Cache adapter instance
|
|
25
|
+
def self.create_adapter(type: :memory, redis_client: nil)
|
|
26
|
+
# If type is already an adapter instance, return it
|
|
27
|
+
return type if type.is_a?(BaseAdapter)
|
|
28
|
+
|
|
29
|
+
# If type is a Class, instantiate it
|
|
30
|
+
if type.is_a?(Class)
|
|
31
|
+
unless type < BaseAdapter
|
|
32
|
+
raise ArgumentError, "Custom cache adapter class must inherit from EmailDomainChecker::Cache::BaseAdapter"
|
|
33
|
+
end
|
|
34
|
+
return type.new
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Handle symbol types
|
|
38
|
+
case type.to_sym
|
|
39
|
+
when :memory
|
|
40
|
+
MemoryAdapter.new
|
|
41
|
+
when :redis
|
|
42
|
+
if defined?(RedisAdapter)
|
|
43
|
+
RedisAdapter.new(redis_client)
|
|
44
|
+
else
|
|
45
|
+
# Fallback to memory if Redis is not available
|
|
46
|
+
warn "Redis adapter requested but 'redis' gem is not available. Falling back to memory cache."
|
|
47
|
+
MemoryAdapter.new
|
|
48
|
+
end
|
|
49
|
+
else
|
|
50
|
+
raise ArgumentError, "Unknown cache adapter type: #{type}. Available: :memory, :redis, or a custom BaseAdapter class/instance"
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "cache"
|
|
4
|
+
|
|
3
5
|
module EmailDomainChecker
|
|
4
6
|
class Config
|
|
5
7
|
DEFAULT_OPTIONS = {
|
|
@@ -11,7 +13,7 @@ module EmailDomainChecker
|
|
|
11
13
|
}.freeze
|
|
12
14
|
|
|
13
15
|
class << self
|
|
14
|
-
attr_accessor :default_options, :test_mode
|
|
16
|
+
attr_accessor :default_options, :test_mode, :cache_enabled, :cache_type, :cache_ttl, :cache_adapter, :cache_adapter_instance, :redis_client
|
|
15
17
|
|
|
16
18
|
def configure(options = {}, &block)
|
|
17
19
|
if block_given?
|
|
@@ -28,6 +30,12 @@ module EmailDomainChecker
|
|
|
28
30
|
def reset
|
|
29
31
|
@default_options = DEFAULT_OPTIONS.dup
|
|
30
32
|
@test_mode = false
|
|
33
|
+
@cache_enabled = true
|
|
34
|
+
@cache_type = :memory
|
|
35
|
+
@cache_ttl = 3600
|
|
36
|
+
@cache_adapter = nil
|
|
37
|
+
@cache_adapter_instance = nil
|
|
38
|
+
@redis_client = nil
|
|
31
39
|
end
|
|
32
40
|
|
|
33
41
|
def test_mode=(value)
|
|
@@ -37,12 +45,86 @@ module EmailDomainChecker
|
|
|
37
45
|
def test_mode?
|
|
38
46
|
@test_mode == true
|
|
39
47
|
end
|
|
48
|
+
|
|
49
|
+
def cache_enabled?
|
|
50
|
+
@cache_enabled == true
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def cache_adapter
|
|
54
|
+
return nil unless cache_enabled?
|
|
55
|
+
|
|
56
|
+
# If custom adapter instance is set, use it directly
|
|
57
|
+
return @cache_adapter_instance if @cache_adapter_instance
|
|
58
|
+
|
|
59
|
+
# Otherwise, create adapter from type
|
|
60
|
+
@cache_adapter ||= Cache.create_adapter(type: cache_type, redis_client: redis_client)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def cache_adapter_instance=(instance)
|
|
64
|
+
validate_cache_adapter_instance!(instance)
|
|
65
|
+
@cache_adapter_instance = instance
|
|
66
|
+
# Reset the auto-created adapter when custom instance is set
|
|
67
|
+
@cache_adapter = nil
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def cache_type=(value)
|
|
71
|
+
@cache_type = value
|
|
72
|
+
reset_cache_adapter_if_needed
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def clear_cache
|
|
76
|
+
cache_adapter&.clear
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def clear_cache_for_domain(domain)
|
|
80
|
+
return unless cache_enabled?
|
|
81
|
+
|
|
82
|
+
adapter = cache_adapter
|
|
83
|
+
return unless adapter
|
|
84
|
+
|
|
85
|
+
# Clear both MX and A record cache entries
|
|
86
|
+
dns_cache_keys_for_domain(domain).each do |key|
|
|
87
|
+
adapter.delete(key)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def reset_cache_adapter_if_needed
|
|
92
|
+
# Reset cache adapter when changing cache type (unless custom instance is set)
|
|
93
|
+
@cache_adapter = nil unless @cache_adapter_instance
|
|
94
|
+
# Clear cache_adapter_instance if setting a type (Symbol or String), not a Class
|
|
95
|
+
@cache_adapter_instance = nil if @cache_type.is_a?(Symbol) || @cache_type.is_a?(String)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def reset_cache_adapter_on_enabled_change(new_value, old_value)
|
|
99
|
+
@cache_adapter = nil if new_value != old_value
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def reset_cache_adapter_if_redis
|
|
103
|
+
@cache_adapter = nil if @cache_type == :redis
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def validate_cache_adapter_instance!(instance)
|
|
107
|
+
unless instance.nil? || instance.is_a?(Cache::BaseAdapter)
|
|
108
|
+
raise ArgumentError, "cache_adapter_instance must be an instance of EmailDomainChecker::Cache::BaseAdapter or nil"
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
private
|
|
113
|
+
|
|
114
|
+
def dns_cache_keys_for_domain(domain)
|
|
115
|
+
["mx:#{domain}", "a:#{domain}"]
|
|
116
|
+
end
|
|
40
117
|
end
|
|
41
118
|
|
|
42
|
-
attr_accessor :test_mode
|
|
119
|
+
attr_accessor :test_mode, :cache_enabled, :cache_type, :cache_ttl, :cache_adapter_instance, :redis_client
|
|
43
120
|
|
|
44
121
|
def initialize
|
|
45
122
|
@test_mode = self.class.test_mode || false
|
|
123
|
+
@cache_enabled = self.class.cache_enabled.nil? ? true : self.class.cache_enabled
|
|
124
|
+
@cache_type = self.class.cache_type || :memory
|
|
125
|
+
@cache_ttl = self.class.cache_ttl || 3600
|
|
126
|
+
@cache_adapter_instance = self.class.cache_adapter_instance
|
|
127
|
+
@redis_client = self.class.redis_client
|
|
46
128
|
end
|
|
47
129
|
|
|
48
130
|
def test_mode=(value)
|
|
@@ -50,6 +132,38 @@ module EmailDomainChecker
|
|
|
50
132
|
self.class.test_mode = value
|
|
51
133
|
end
|
|
52
134
|
|
|
135
|
+
def cache_enabled=(value)
|
|
136
|
+
old_value = self.class.cache_enabled
|
|
137
|
+
@cache_enabled = value
|
|
138
|
+
self.class.cache_enabled = value
|
|
139
|
+
# Reset cache adapter when enabling/disabling cache
|
|
140
|
+
self.class.reset_cache_adapter_on_enabled_change(value, old_value)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def cache_type=(value)
|
|
144
|
+
@cache_type = value
|
|
145
|
+
self.class.cache_type = value
|
|
146
|
+
self.class.reset_cache_adapter_if_needed
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def cache_adapter_instance=(instance)
|
|
150
|
+
self.class.validate_cache_adapter_instance!(instance)
|
|
151
|
+
@cache_adapter_instance = instance
|
|
152
|
+
self.class.cache_adapter_instance = instance
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def cache_ttl=(value)
|
|
156
|
+
@cache_ttl = value
|
|
157
|
+
self.class.cache_ttl = value
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def redis_client=(value)
|
|
161
|
+
@redis_client = value
|
|
162
|
+
self.class.redis_client = value
|
|
163
|
+
# Reset cache adapter when changing redis client
|
|
164
|
+
self.class.reset_cache_adapter_if_redis
|
|
165
|
+
end
|
|
166
|
+
|
|
53
167
|
reset
|
|
54
168
|
end
|
|
55
169
|
end
|