-
Notifications
You must be signed in to change notification settings - Fork 2
/
scratch.rb
140 lines (108 loc) · 2.83 KB
/
scratch.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# You can run this locally from the `riff` clone directory:
# bundle exec ruby scratch.rb
#
# 1. Spam post detection: Run a forum post through a series of configurable checks to give it a spam score,
# and flag the post when it crosses the threshold, with details on what led to the score.
require "bundler/setup"
require "active_record"
require "action_controller"
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Schema.define do
create_table :posts do |t|
t.references :user, null: false, index: true
t.string :title, null: false
t.text :content, null: false
end
create_table :users do |t|
end
end
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
end
class Post < ApplicationRecord
belongs_to :user
end
class User < ApplicationRecord
has_many :posts
end
module Spam
module Detectors
def self.check(post)
Check.new post, Abstract.detectors
end
class Check
def initialize(post, detectors)
@detectors = detectors.map { _1.new(post:) }
end
def score
@detectors.sum(&:score) / @detectors.size
end
end
class Abstract < Struct.new(:post, :max_hits, keyword_init: true)
def initialize(post:, max_hits: 1) = super
singleton_class.attr_reader :detectors
@detectors = []
def self.inherited(detector) = detectors << detector
def hits
hit? ? 1 : 0
end
def score
hits / max_hits.to_f
end
end
module Content
end
module Account
end
end
end
class Spam::Detectors::Account::PostCount < Spam::Detectors::Abstract
def hit?
post.user.posts.where(created_at: 1.hour.ago..).count >= 50
end
end
# The first check we did, just to start us off and then we continued from here.
# We also had checks respond to `score` but ultimately moved on to hits/max_hits though I don't quite remember the reasoning now.
class Spam::Detectors::Content::FirstPost < Spam::Detectors::Abstract
def hit?
post.content == "My first post"
end
end
class Spam::Detectors::Content::LinkCount < Spam::Detectors::Abstract
def hits
content_links.size
end
def max_hits
10
end
private
def content_links
post.content.scan /https?:.*? /
end
end
class Spam::Detectors::Content::Dictionary < Spam::Detectors::Abstract
def initialize(post, words)
super
@words = words
end
def hits
content_words.uniq.size
end
def max_hits
words.size
end
private
def content_words
post.content.scan Regexp.new(@words.join("|"))
end
end
class ApplicationController < ActionController::Base
end
class Post::SpamDetectionsController < ApplicationController
def create
@detection = Spam::Detectors.check @post
end
end
user = User.create!
post = Post.create! user:, title: "First", content: "Heyo"
binding.irb