changes RuleSet/Rule structure and introduce RulesEvaluator
This commit is contained in:
parent
0a6fb01008
commit
b65363cffd
2
IDEAS.md
2
IDEAS.md
|
@ -13,7 +13,7 @@ Rule
|
|||
* enabled?
|
||||
* criteria_type (Header, Body…)
|
||||
* criteria_value
|
||||
* operator (match, equal, start, end, contain)
|
||||
* operator (match, equal, start, end, contain, exist, empty)
|
||||
* operator_inverted?
|
||||
* value
|
||||
|
||||
|
|
|
@ -1,9 +1,22 @@
|
|||
class InMailbox < ApplicationMailbox
|
||||
def process
|
||||
email_importer = EmailImporter.new()
|
||||
email_importer = EmailImporter.new
|
||||
repository = EmailRepository.new
|
||||
rules_evaluator = RulesEvaluator.new
|
||||
|
||||
email = email_importer.import(mail)
|
||||
|
||||
RuleSet.enabled.each do |rule_set|
|
||||
if rules_evaluator.evaluate(email, rule_set)
|
||||
rule_set.actions.enabled.each do |action|
|
||||
klass = action.class_name.constantize
|
||||
email = klass.new.process(email)
|
||||
rescue NameError => ex
|
||||
# TODO: log a warning
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
repository.save(email)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Action < ApplicationRecord
|
||||
belongs_to :ruleset
|
||||
belongs_to :rule_set
|
||||
|
||||
scope :enabled, -> { where(enabled: true) }
|
||||
end
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Rule < ApplicationRecord
|
||||
belongs_to :ruleset
|
||||
belongs_to :rule_set
|
||||
has_many :actions
|
||||
|
||||
scope :enabled, -> { where(enabled: true) }
|
||||
end
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RuleSet < ApplicationRecord
|
||||
has_many :rules
|
||||
has_many :actions
|
||||
|
||||
scope :enabled, -> { where(enabled: true) }
|
||||
end
|
||||
|
|
89
app/services/rules_evaluator.rb
Normal file
89
app/services/rules_evaluator.rb
Normal file
|
@ -0,0 +1,89 @@
|
|||
class RulesEvaluator
|
||||
|
||||
def evaluate(email, rule_set)
|
||||
rules_state = true
|
||||
|
||||
rule_set.rules.enabled.each do |rule|
|
||||
subjects = prepare_subjects(email, rule)
|
||||
result = apply_rule(subjects, rule)
|
||||
result = ! result if rule.inverted?
|
||||
rules_state = apply_operator(rules_state, rule_set.operator, result)
|
||||
|
||||
break unless rules_state
|
||||
end
|
||||
|
||||
rules_state
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def prepare_subjects(email, rule)
|
||||
case rule.subject_type
|
||||
when "header"
|
||||
Array(email.header_values(rule.subject_value))
|
||||
when "body"
|
||||
Array(email.plain_body)
|
||||
else
|
||||
raise ArgumentError, "Unrecognized subject type '#{rule.subject_type}'"
|
||||
end
|
||||
end
|
||||
|
||||
def apply_rule(subjects, rule)
|
||||
case rule.condition_type
|
||||
when "match", "matches"
|
||||
subjects.any? { |subject|
|
||||
pattern = Regexp.new(rule.condition_value)
|
||||
subject.match?
|
||||
}
|
||||
when "equal", "equals"
|
||||
subjects.any? { |subject|
|
||||
subject == rule.condition_value
|
||||
}
|
||||
when "start", "starts"
|
||||
subjects.any? { |subject|
|
||||
subject.starts_with? rule.condition_value
|
||||
}
|
||||
when "end", "ends"
|
||||
subjects.any? { |subject|
|
||||
subject.ends_with? rule.condition_value
|
||||
}
|
||||
when "contain", "contains"
|
||||
subjects.any? { |subject|
|
||||
subject.include? rule.condition_value
|
||||
}
|
||||
when "exist", "exists"
|
||||
subjects.any? { |subject|
|
||||
subject.exists?
|
||||
}
|
||||
when "empty"
|
||||
subjects.all? { |subject|
|
||||
subject.empty?
|
||||
}
|
||||
when "date_before"
|
||||
# subjects.all? { |subject|
|
||||
# subject.empty?
|
||||
# }
|
||||
when "date_after"
|
||||
# subjects.all? { |subject|
|
||||
# subject.empty?
|
||||
# }
|
||||
else
|
||||
true
|
||||
end
|
||||
|
||||
def apply_operator(state, operator, result)
|
||||
case operator
|
||||
when "AND"
|
||||
(state and result)
|
||||
when "OR"
|
||||
(state or result)
|
||||
when "XOR"
|
||||
(state or result) and not (rules_state and result)
|
||||
else
|
||||
raise ArgumentError, "Unrecognized logic '#{rule_set.operator}'"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
|
@ -4,6 +4,8 @@ class CreateRuleSets < ActiveRecord::Migration[6.1]
|
|||
t.string :name
|
||||
t.text :description
|
||||
t.boolean :enabled, default: true
|
||||
t.string :operator, default: "AND", limit: 3
|
||||
t.boolean :inverted, default: false
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
class CreateRules < ActiveRecord::Migration[6.1]
|
||||
def change
|
||||
create_table :rules do |t|
|
||||
t.references :ruleset, null: false, foreign_key: true
|
||||
t.references :rule_set, null: false, foreign_key: true
|
||||
t.string :name
|
||||
t.boolean :enabled, default: true
|
||||
t.string :criteria_type
|
||||
t.string :criteria_value
|
||||
t.string :operator, default: "contain"
|
||||
t.boolean :operator_inverted, default: false
|
||||
t.string :value
|
||||
t.string :subject_type
|
||||
t.string :subject_value
|
||||
t.string :condition_type, default: "contain"
|
||||
t.string :condition_value
|
||||
t.boolean :inverted, default: false
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
class CreateActions < ActiveRecord::Migration[6.1]
|
||||
def change
|
||||
create_table :actions do |t|
|
||||
t.references :ruleset, null: false, foreign_key: true
|
||||
t.references :rule_set, null: false, foreign_key: true
|
||||
t.string :name
|
||||
t.boolean :enabled, default: true
|
||||
t.string :action_class
|
||||
t.string :class_name
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
|
|
26
db/schema.rb
26
db/schema.rb
|
@ -22,13 +22,13 @@ ActiveRecord::Schema.define(version: 2021_01_18_132809) do
|
|||
end
|
||||
|
||||
create_table "actions", force: :cascade do |t|
|
||||
t.integer "ruleset_id", null: false
|
||||
t.integer "rule_set_id", null: false
|
||||
t.string "name"
|
||||
t.boolean "enabled", default: true
|
||||
t.string "action_class"
|
||||
t.string "class_name"
|
||||
t.datetime "created_at", precision: 6, null: false
|
||||
t.datetime "updated_at", precision: 6, null: false
|
||||
t.index ["ruleset_id"], name: "index_actions_on_ruleset_id"
|
||||
t.index ["rule_set_id"], name: "index_actions_on_rule_set_id"
|
||||
end
|
||||
|
||||
create_table "active_storage_attachments", force: :cascade do |t|
|
||||
|
@ -91,22 +91,24 @@ ActiveRecord::Schema.define(version: 2021_01_18_132809) do
|
|||
t.string "name"
|
||||
t.text "description"
|
||||
t.boolean "enabled", default: true
|
||||
t.string "operator", limit: 3, default: "AND"
|
||||
t.boolean "inverted", default: false
|
||||
t.datetime "created_at", precision: 6, null: false
|
||||
t.datetime "updated_at", precision: 6, null: false
|
||||
end
|
||||
|
||||
create_table "rules", force: :cascade do |t|
|
||||
t.integer "ruleset_id", null: false
|
||||
t.integer "rule_set_id", null: false
|
||||
t.string "name"
|
||||
t.boolean "enabled", default: true
|
||||
t.string "criteria_type"
|
||||
t.string "criteria_value"
|
||||
t.string "operator", default: "contain"
|
||||
t.boolean "operator_inverted", default: false
|
||||
t.string "value"
|
||||
t.string "subject_type"
|
||||
t.string "subject_value"
|
||||
t.string "condition_type", default: "contain"
|
||||
t.string "condition_value"
|
||||
t.boolean "inverted", default: false
|
||||
t.datetime "created_at", precision: 6, null: false
|
||||
t.datetime "updated_at", precision: 6, null: false
|
||||
t.index ["ruleset_id"], name: "index_rules_on_ruleset_id"
|
||||
t.index ["rule_set_id"], name: "index_rules_on_rule_set_id"
|
||||
end
|
||||
|
||||
create_table "users", force: :cascade do |t|
|
||||
|
@ -126,8 +128,8 @@ ActiveRecord::Schema.define(version: 2021_01_18_132809) do
|
|||
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
|
||||
end
|
||||
|
||||
add_foreign_key "actions", "rulesets"
|
||||
add_foreign_key "actions", "rule_sets"
|
||||
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
|
||||
add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
|
||||
add_foreign_key "rules", "rulesets"
|
||||
add_foreign_key "rules", "rule_sets"
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue