changes RuleSet/Rule structure and introduce RulesEvaluator

This commit is contained in:
Jérémy Lecour 2021-01-22 14:50:56 +01:00 committed by Jérémy Lecour
parent 0a6fb01008
commit b65363cffd
10 changed files with 139 additions and 24 deletions

View file

@ -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

View file

@ -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

View file

@ -1,5 +1,7 @@
# frozen_string_literal: true
class Action < ApplicationRecord
belongs_to :ruleset
belongs_to :rule_set
scope :enabled, -> { where(enabled: true) }
end

View file

@ -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

View file

@ -1,4 +1,8 @@
# frozen_string_literal: true
class RuleSet < ApplicationRecord
has_many :rules
has_many :actions
scope :enabled, -> { where(enabled: true) }
end

View 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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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