Skip to content

Commit b188850

Browse files
rosaclaude
andcommitted
Add ClientStub, Rails module, and Railtie for multi-app support
Adds three new components for Rails integration: - Rspamd::ClientStub: null-object client that returns ham for check and no-ops for training, used when rspamd is disabled (dev/test) - Rspamd::Rails: thread-safe client management with config loading via setup(config) and client_for(:outbound) - Rspamd::Railtie: auto-loads config/rspamd.yml on Rails boot Also adds activesupport as a runtime dependency for deep_symbolize_keys. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 109f986 commit b188850

8 files changed

Lines changed: 366 additions & 56 deletions

File tree

Gemfile.lock

Lines changed: 166 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,90 +1,144 @@
11
GIT
22
remote: https://github.com/basecamp/house-style.git
3-
revision: b3ad65254828e8e8019a0d9a6205aff9ad206a77
3+
revision: 6f73ca5c3fd5002b48aeede2d980f9b8fe047d55
44
branch: main
55
specs:
6-
rubocop-37signals (1.0.0)
7-
rubocop
8-
rubocop-minitest
9-
rubocop-performance
10-
rubocop-rails
6+
rubocop-37signals (1.2.1)
7+
rubocop (>= 1.72)
8+
rubocop-minitest (>= 0.37.0)
9+
rubocop-performance (>= 1.24)
10+
rubocop-rails (>= 2.30)
1111

1212
PATH
1313
remote: .
1414
specs:
1515
rspamd-ruby (1.0.0)
16+
activesupport (>= 6.0)
1617

1718
GEM
1819
remote: https://rubygems.org/
1920
specs:
20-
activesupport (6.1.7.4)
21-
concurrent-ruby (~> 1.0, >= 1.0.2)
21+
activesupport (8.1.2)
22+
base64
23+
bigdecimal
24+
concurrent-ruby (~> 1.0, >= 1.3.1)
25+
connection_pool (>= 2.2.5)
26+
drb
2227
i18n (>= 1.6, < 2)
28+
json
29+
logger (>= 1.4.2)
2330
minitest (>= 5.1)
24-
tzinfo (~> 2.0)
25-
zeitwerk (~> 2.3)
26-
addressable (2.8.1)
27-
public_suffix (>= 2.0.2, < 6.0)
28-
ast (2.4.2)
29-
concurrent-ruby (1.2.0)
30-
crack (0.4.3)
31-
safe_yaml (~> 1.0.0)
32-
debug (1.7.1)
33-
irb (>= 1.5.0)
34-
reline (>= 0.3.1)
35-
hashdiff (1.0.0)
36-
i18n (1.12.0)
31+
securerandom (>= 0.3)
32+
tzinfo (~> 2.0, >= 2.0.5)
33+
uri (>= 0.13.1)
34+
addressable (2.8.9)
35+
public_suffix (>= 2.0.2, < 8.0)
36+
ast (2.4.3)
37+
base64 (0.3.0)
38+
bigdecimal (4.0.1)
39+
concurrent-ruby (1.3.6)
40+
connection_pool (3.0.2)
41+
crack (1.0.1)
42+
bigdecimal
43+
rexml
44+
date (3.5.1)
45+
debug (1.11.1)
46+
irb (~> 1.10)
47+
reline (>= 0.3.8)
48+
drb (2.2.3)
49+
erb (6.0.2)
50+
hashdiff (1.2.1)
51+
i18n (1.14.8)
3752
concurrent-ruby (~> 1.0)
38-
io-console (0.6.0)
39-
irb (1.6.2)
40-
reline (>= 0.3.0)
41-
json (2.6.3)
42-
minitest (5.17.0)
43-
parallel (1.22.1)
44-
parser (3.2.1.0)
53+
io-console (0.8.2)
54+
irb (1.17.0)
55+
pp (>= 0.6.0)
56+
prism (>= 1.3.0)
57+
rdoc (>= 4.0.0)
58+
reline (>= 0.4.2)
59+
json (2.18.1)
60+
json-schema (6.1.0)
61+
addressable (~> 2.8)
62+
bigdecimal (>= 3.1, < 5)
63+
language_server-protocol (3.17.0.5)
64+
lint_roller (1.1.0)
65+
logger (1.7.0)
66+
mcp (0.8.0)
67+
json-schema (>= 4.1)
68+
minitest (6.0.2)
69+
drb (~> 2.0)
70+
prism (~> 1.5)
71+
parallel (1.27.0)
72+
parser (3.3.10.2)
4573
ast (~> 2.4.1)
46-
public_suffix (5.0.0)
47-
rack (3.0.8)
74+
racc
75+
pp (0.6.3)
76+
prettyprint
77+
prettyprint (0.2.0)
78+
prism (1.9.0)
79+
psych (5.3.1)
80+
date
81+
stringio
82+
public_suffix (7.0.5)
83+
racc (1.8.1)
84+
rack (3.2.5)
4885
rainbow (3.1.1)
49-
rake (13.0.1)
50-
regexp_parser (2.7.0)
51-
reline (0.3.2)
86+
rake (13.3.1)
87+
rdoc (7.2.0)
88+
erb
89+
psych (>= 4.0.0)
90+
tsort
91+
regexp_parser (2.11.3)
92+
reline (0.6.3)
5293
io-console (~> 0.5)
53-
rexml (3.2.5)
54-
rubocop (1.45.1)
94+
rexml (3.4.4)
95+
rubocop (1.85.0)
5596
json (~> 2.3)
97+
language_server-protocol (~> 3.17.0.2)
98+
lint_roller (~> 1.1.0)
99+
mcp (~> 0.6)
56100
parallel (~> 1.10)
57-
parser (>= 3.2.0.0)
101+
parser (>= 3.3.0.2)
58102
rainbow (>= 2.2.2, < 4.0)
59-
regexp_parser (>= 1.8, < 3.0)
60-
rexml (>= 3.2.5, < 4.0)
61-
rubocop-ast (>= 1.24.1, < 2.0)
103+
regexp_parser (>= 2.9.3, < 3.0)
104+
rubocop-ast (>= 1.49.0, < 2.0)
62105
ruby-progressbar (~> 1.7)
63-
unicode-display_width (>= 2.4.0, < 3.0)
64-
rubocop-ast (1.26.0)
65-
parser (>= 3.2.1.0)
66-
rubocop-minitest (0.27.0)
67-
rubocop (>= 0.90, < 2.0)
68-
rubocop-performance (1.16.0)
69-
rubocop (>= 1.7.0, < 2.0)
70-
rubocop-ast (>= 0.4.0)
71-
rubocop-rails (2.19.1)
106+
unicode-display_width (>= 2.4.0, < 4.0)
107+
rubocop-ast (1.49.0)
108+
parser (>= 3.3.7.2)
109+
prism (~> 1.7)
110+
rubocop-minitest (0.39.1)
111+
lint_roller (~> 1.1)
112+
rubocop (>= 1.75.0, < 2.0)
113+
rubocop-ast (>= 1.38.0, < 2.0)
114+
rubocop-performance (1.26.1)
115+
lint_roller (~> 1.1)
116+
rubocop (>= 1.75.0, < 2.0)
117+
rubocop-ast (>= 1.47.1, < 2.0)
118+
rubocop-rails (2.34.3)
72119
activesupport (>= 4.2.0)
120+
lint_roller (~> 1.1)
73121
rack (>= 1.1)
74-
rubocop (>= 1.33.0, < 2.0)
75-
ruby-progressbar (1.11.0)
76-
safe_yaml (1.0.5)
122+
rubocop (>= 1.75.0, < 2.0)
123+
rubocop-ast (>= 1.44.0, < 2.0)
124+
ruby-progressbar (1.13.0)
125+
securerandom (0.4.1)
126+
stringio (3.2.0)
127+
tsort (0.2.0)
77128
tzinfo (2.0.6)
78129
concurrent-ruby (~> 1.0)
79-
unicode-display_width (2.4.2)
80-
webmock (3.8.0)
81-
addressable (>= 2.3.6)
130+
unicode-display_width (3.2.0)
131+
unicode-emoji (~> 4.1)
132+
unicode-emoji (4.2.0)
133+
uri (1.1.1)
134+
webmock (3.26.1)
135+
addressable (>= 2.8.0)
82136
crack (>= 0.3.2)
83137
hashdiff (>= 0.4.0, < 2.0.0)
84-
zeitwerk (2.6.8)
85138

86139
PLATFORMS
87140
ruby
141+
x86_64-linux
88142

89143
DEPENDENCIES
90144
debug
@@ -94,5 +148,61 @@ DEPENDENCIES
94148
rubocop-37signals!
95149
webmock (~> 3.0)
96150

151+
CHECKSUMS
152+
activesupport (8.1.2) sha256=88842578ccd0d40f658289b0e8c842acfe9af751afee2e0744a7873f50b6fdae
153+
addressable (2.8.9) sha256=cc154fcbe689711808a43601dee7b980238ce54368d23e127421753e46895485
154+
ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383
155+
base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
156+
bigdecimal (4.0.1) sha256=8b07d3d065a9f921c80ceaea7c9d4ae596697295b584c296fe599dd0ad01c4a7
157+
concurrent-ruby (1.3.6) sha256=6b56837e1e7e5292f9864f34b69c5a2cbc75c0cf5338f1ce9903d10fa762d5ab
158+
connection_pool (3.0.2) sha256=33fff5ba71a12d2aa26cb72b1db8bba2a1a01823559fb01d29eb74c286e62e0a
159+
crack (1.0.1) sha256=ff4a10390cd31d66440b7524eb1841874db86201d5b70032028553130b6d4c7e
160+
date (3.5.1) sha256=750d06384d7b9c15d562c76291407d89e368dda4d4fff957eb94962d325a0dc0
161+
debug (1.11.1) sha256=2e0b0ac6119f2207a6f8ac7d4a73ca8eb4e440f64da0a3136c30343146e952b6
162+
drb (2.2.3) sha256=0b00d6fdb50995fe4a45dea13663493c841112e4068656854646f418fda13373
163+
erb (6.0.2) sha256=9fe6264d44f79422c87490a1558479bd0e7dad4dd0e317656e67ea3077b5242b
164+
hashdiff (1.2.1) sha256=9c079dbc513dfc8833ab59c0c2d8f230fa28499cc5efb4b8dd276cf931457cd1
165+
i18n (1.14.8) sha256=285778639134865c5e0f6269e0b818256017e8cde89993fdfcbfb64d088824a5
166+
io-console (0.8.2) sha256=d6e3ae7a7cc7574f4b8893b4fca2162e57a825b223a177b7afa236c5ef9814cc
167+
irb (1.17.0) sha256=168c4ddb93d8a361a045c41d92b2952c7a118fa73f23fe14e55609eb7a863aae
168+
json (2.18.1) sha256=fe112755501b8d0466b5ada6cf50c8c3f41e897fa128ac5d263ec09eedc9f986
169+
json-schema (6.1.0) sha256=6bf70a2cfb6dfd5a06da28093fa8190f324c88eabd36a7f47097f227321dc702
170+
language_server-protocol (3.17.0.5) sha256=fd1e39a51a28bf3eec959379985a72e296e9f9acfce46f6a79d31ca8760803cc
171+
lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87
172+
logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
173+
mcp (0.8.0) sha256=ae8bd146bb8e168852866fd26f805f52744f6326afb3211e073f78a95e0c34fb
174+
minitest (6.0.2) sha256=db6e57956f6ecc6134683b4c87467d6dd792323c7f0eea7b93f66bd284adbc3d
175+
parallel (1.27.0) sha256=4ac151e1806b755fb4e2dc2332cbf0e54f2e24ba821ff2d3dcf86bf6dc4ae130
176+
parser (3.3.10.2) sha256=6f60c84aa4bdcedb6d1a2434b738fe8a8136807b6adc8f7f53b97da9bc4e9357
177+
pp (0.6.3) sha256=2951d514450b93ccfeb1df7d021cae0da16e0a7f95ee1e2273719669d0ab9df6
178+
prettyprint (0.2.0) sha256=2bc9e15581a94742064a3cc8b0fb9d45aae3d03a1baa6ef80922627a0766f193
179+
prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85
180+
psych (5.3.1) sha256=eb7a57cef10c9d70173ff74e739d843ac3b2c019a003de48447b2963d81b1974
181+
public_suffix (7.0.5) sha256=1a8bb08f1bbea19228d3bed6e5ed908d1cb4f7c2726d18bd9cadf60bc676f623
182+
racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
183+
rack (3.2.5) sha256=4cbd0974c0b79f7a139b4812004a62e4c60b145cba76422e288ee670601ed6d3
184+
rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
185+
rake (13.3.1) sha256=8c9e89d09f66a26a01264e7e3480ec0607f0c497a861ef16063604b1b08eb19c
186+
rdoc (7.2.0) sha256=8650f76cd4009c3b54955eb5d7e3a075c60a57276766ebf36f9085e8c9f23192
187+
regexp_parser (2.11.3) sha256=ca13f381a173b7a93450e53459075c9b76a10433caadcb2f1180f2c741fc55a4
188+
reline (0.6.3) sha256=1198b04973565b36ec0f11542ab3f5cfeeec34823f4e54cebde90968092b1835
189+
rexml (3.4.4) sha256=19e0a2c3425dfbf2d4fc1189747bdb2f849b6c5e74180401b15734bc97b5d142
190+
rspamd-ruby (1.0.0)
191+
rubocop (1.85.0) sha256=317407feb681a07d54f64d2f9e1d6b6af1ce7678e51cd658e3ad8bd66da48c01
192+
rubocop-37signals (1.2.1)
193+
rubocop-ast (1.49.0) sha256=49c3676d3123a0923d333e20c6c2dbaaae2d2287b475273fddee0c61da9f71fd
194+
rubocop-minitest (0.39.1) sha256=998398d6da4026d297f0f9bf709a1eac5f2b6947c24431f94af08138510cf7ed
195+
rubocop-performance (1.26.1) sha256=cd19b936ff196df85829d264b522fd4f98b6c89ad271fa52744a8c11b8f71834
196+
rubocop-rails (2.34.3) sha256=10d37989024865ecda8199f311f3faca990143fbac967de943f88aca11eb9ad2
197+
ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33
198+
securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1
199+
stringio (3.2.0) sha256=c37cb2e58b4ffbd33fe5cd948c05934af997b36e0b6ca6fdf43afa234cf222e1
200+
tsort (0.2.0) sha256=9650a793f6859a43b6641671278f79cfead60ac714148aabe4e3f0060480089f
201+
tzinfo (2.0.6) sha256=8daf828cc77bcf7d63b0e3bdb6caa47e2272dcfaf4fbfe46f8c3a9df087a829b
202+
unicode-display_width (3.2.0) sha256=0cdd96b5681a5949cdbc2c55e7b420facae74c4aaf9a9815eee1087cb1853c42
203+
unicode-emoji (4.2.0) sha256=519e69150f75652e40bf736106cfbc8f0f73aa3fb6a65afe62fefa7f80b0f80f
204+
uri (1.1.1) sha256=379fa58d27ffb1387eaada68c749d1426738bd0f654d812fcc07e7568f5c57c6
205+
webmock (3.26.1) sha256=4f696fb57c90a827c20aadb2d4f9058bbff10f7f043bd0d4c3f58791143b1cd7
206+
97207
BUNDLED WITH
98-
2.4.17
208+
4.0.6

lib/rspamd-ruby.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
11
require "rspamd/client"
2+
require "rspamd/client_stub"
23
require "rspamd/errors"
4+
require "rspamd/rails"
5+
require "rspamd/railtie" if defined?(::Rails::Railtie)

lib/rspamd/client_stub.rb

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
module Rspamd
2+
class ClientStub
3+
HAM_RESULT = Check::Result.new(
4+
"score" => 0.0,
5+
"required_score" => 15.0,
6+
"action" => "no action",
7+
"is_skipped" => false,
8+
"symbols" => {},
9+
"urls" => [],
10+
"emails" => []
11+
).freeze
12+
13+
def ping
14+
true
15+
end
16+
17+
def check(message, headers: {})
18+
HAM_RESULT
19+
end
20+
21+
def spam!(message)
22+
true
23+
end
24+
25+
def ham!(message)
26+
true
27+
end
28+
29+
def add_fuzzy(message, flag: 1, weight: 1)
30+
true
31+
end
32+
33+
def delete_fuzzy(message, flag: 1)
34+
true
35+
end
36+
end
37+
end

lib/rspamd/rails.rb

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
require "active_support/core_ext/hash/keys"
2+
require "rspamd/client"
3+
require "rspamd/client_stub"
4+
5+
module Rspamd
6+
module Rails
7+
class << self
8+
def setup(config)
9+
@config = config.deep_symbolize_keys
10+
@clients = {}
11+
end
12+
13+
def client_for(name)
14+
return ClientStub.new unless enabled?
15+
16+
@clients[name] ||= build_client(name)
17+
end
18+
19+
def reset!
20+
@config = nil
21+
@clients = {}
22+
end
23+
24+
private
25+
def enabled?
26+
@config&.dig(:enabled)
27+
end
28+
29+
def build_client(name)
30+
settings = @config.fetch(name) { raise ArgumentError, "No rspamd configuration for #{name.inspect}" }
31+
Client.new(**settings.slice(:host, :port, :password))
32+
end
33+
end
34+
end
35+
end

lib/rspamd/railtie.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
require "rspamd/rails"
2+
3+
module Rspamd
4+
class Railtie < ::Rails::Railtie
5+
initializer "rspamd.setup" do
6+
config_path = ::Rails.root.join("config/rspamd.yml")
7+
8+
if config_path.exist?
9+
Rspamd::Rails.setup(::Rails.application.config_for(:rspamd))
10+
end
11+
end
12+
end
13+
end

rspamd-ruby.gemspec

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ Gem::Specification.new do |s|
1010

1111
s.required_ruby_version = ">= 2.7.8"
1212

13+
s.add_dependency "activesupport", ">= 6.0"
14+
1315
s.add_development_dependency "rake", "~> 13.0"
1416
s.add_development_dependency "minitest", "> 5.11"
1517
s.add_development_dependency "webmock", "~> 3.0"

0 commit comments

Comments
 (0)