Class: ApprovalEngine::RuleEvaluator

Inherits:
Object
  • Object
show all
Defined in:
app/services/approval_engine/rule_evaluator.rb

Overview

Resolves which track (if any) a host event should spawn by evaluating tenant-scoped JSON Logic rules against a flat payload.

The payload is produced by the host model’s ‘exposes_for_approval` DSL, so the engine never reaches into the host’s domain directly — it only sees the whitelisted attributes it was handed.

ApprovalEngine::RuleEvaluator.call(
  event_name: "invoice.created",
  tenant_id:  .id,
  target:     invoice,
  payload:    invoice.serialize_for_approval
)

‘call` returns the spawned Approval, the quarantine Approval on a rule failure, or nil when no rule matched. `preview` runs the identical matching logic but writes nothing — see ApprovalPlan.

Defined Under Namespace

Classes: EvaluationError

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(event_name:, tenant_id:, target:, payload:) ⇒ RuleEvaluator

Returns a new instance of RuleEvaluator.



41
42
43
44
45
46
# File 'app/services/approval_engine/rule_evaluator.rb', line 41

def initialize(event_name:, tenant_id:, target:, payload:)
  @event_name = event_name
  @tenant_id  = tenant_id
  @target     = target
  @payload    = payload
end

Class Method Details

.call(event_name:, tenant_id:, target:, payload:) ⇒ Object



24
25
26
# File 'app/services/approval_engine/rule_evaluator.rb', line 24

def self.call(event_name:, tenant_id:, target:, payload:)
  new(event_name: event_name, tenant_id: tenant_id, target: target, payload: payload).call
end

.candidates(event_name:, tenant_id:, target:, payload:) ⇒ Object

Side-effect-free: every rule that matches, in priority order, so the host can let a user choose which to trigger instead of auto-picking the top one. Returns an array of ApprovalPlan. Broken rules are skipped, not surfaced.



37
38
39
# File 'app/services/approval_engine/rule_evaluator.rb', line 37

def self.candidates(event_name:, tenant_id:, target:, payload:)
  new(event_name: event_name, tenant_id: tenant_id, target: target, payload: payload).candidates
end

.preview(event_name:, tenant_id:, target:, payload:) ⇒ Object

Side-effect-free dry run: what would this event trigger? Writes nothing, never quarantines, never raises. Returns an ApprovalPlan.



30
31
32
# File 'app/services/approval_engine/rule_evaluator.rb', line 30

def self.preview(event_name:, tenant_id:, target:, payload:)
  new(event_name: event_name, tenant_id: tenant_id, target: target, payload: payload).preview
end

Instance Method Details

#callObject



48
49
50
51
52
53
54
55
56
57
58
# File 'app/services/approval_engine/rule_evaluator.rb', line 48

def call
  status, rule = find_match
  case status
  when :match
    ApprovalBuilder.build!(template: rule.track_template, target: target, event_name: event_name, trigger_rule: rule)
  when :error
    raise EvaluationError, @failure_reason if ApprovalEngine.config.raise_on_rule_errors

    quarantine(rule)
  end
end

#candidatesObject



65
66
67
68
69
# File 'app/services/approval_engine/rule_evaluator.rb', line 65

def candidates
  candidate_rules.filter_map do |rule|
    ApprovalPlan.new(status: :match, template: rule.track_template, target: target) if evaluate(rule) == :match
  end
end

#previewObject



60
61
62
63
# File 'app/services/approval_engine/rule_evaluator.rb', line 60

def preview
  status, rule = find_match
  ApprovalPlan.new(status: status, template: rule&.track_template, target: target, reason: @failure_reason)
end