Class: Torm::RulesEngine

Inherits:
Object
  • Object
show all
Defined in:
lib/torm/rules_engine.rb

Constant Summary collapse

DEFAULT_POLICIES =

Policies (priorities) in order of important -> least important.

[:law, :coc, :experiment, :default].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(rules: {}, conditions_whitelist: {}, dirty: false, policies: DEFAULT_POLICIES.dup, verbose: false) ⇒ RulesEngine

Returns a new instance of RulesEngine.



10
11
12
13
14
15
16
# File 'lib/torm/rules_engine.rb', line 10

def initialize(rules: {}, conditions_whitelist: {}, dirty: false, policies: DEFAULT_POLICIES.dup, verbose: false)
  @rules                = rules
  @conditions_whitelist = conditions_whitelist
  @dirty                = dirty
  @policies             = policies
  @verbose              = verbose
end

Instance Attribute Details

#conditions_whitelistObject

Returns the value of attribute conditions_whitelist.



7
8
9
# File 'lib/torm/rules_engine.rb', line 7

def conditions_whitelist
  @conditions_whitelist
end

#dirtyObject

Returns the value of attribute dirty.



8
9
10
# File 'lib/torm/rules_engine.rb', line 8

def dirty
  @dirty
end

#policiesObject

Returns the value of attribute policies.



7
8
9
# File 'lib/torm/rules_engine.rb', line 7

def policies
  @policies
end

#rulesObject (readonly)

Returns the value of attribute rules.



6
7
8
# File 'lib/torm/rules_engine.rb', line 6

def rules
  @rules
end

#verboseObject

Returns the value of attribute verbose.



7
8
9
# File 'lib/torm/rules_engine.rb', line 7

def verbose
  @verbose
end

Class Method Details

.from_json(json) ⇒ Object

Load an engine from JSON. This means we can export rules engines across systems: store rules in 1 place, run them ‘everywhere’ at native speed. Due to the high number of symbols we use, we have to convert the JSON string data for each rule on import. Good thing: we should only have to do this once on boot.



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/torm/rules_engine.rb', line 64

def self.from_json(json)
  dump   = MultiJson.load(json)
  data   = {
    policies: dump['policies'].map(&:to_sym),
  }
  engine = new(data)
  dump['rules'].each do |name, rules|
    rules.each do |rule|
      value      = rule['value']
      value      = Torm.symbolize_keys(value) if Hash === value
      policy     = rule['policy'].to_sym
      conditions = Torm.symbolize_keys(rule['conditions'])
      engine.add_rule(name, value, policy, conditions)
    end
  end
  engine.dirty = false
  engine
end

.load(rules_file: Torm.default_rules_file) ⇒ Torm::RulesEngine

Load rules from a file and create a new engine for it. Note: this does not replace the Torm::RulesEngine.instance, you have to do this yourself if required.

Returns:



92
93
94
95
96
97
98
99
# File 'lib/torm/rules_engine.rb', line 92

def self.load(rules_file: Torm.default_rules_file)
  if File.exist?(rules_file)
    json = File.read(rules_file)
    self.from_json(json)
  else
    nil
  end
end

.rules_fileObject

Where we store the rules file.



84
85
86
# File 'lib/torm/rules_engine.rb', line 84

def self.rules_file
  Rails.root.join('tmp', 'rules.json').to_s
end

Instance Method Details

#add_rule(name, value, policy, conditions = {}) ⇒ Torm::RulesEngine

Add a new rule. Will mark the engine as dirty when a rules was added.

Returns:



26
27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/torm/rules_engine.rb', line 26

def add_rule(name, value, policy, conditions={})
  raise "Illegal policy: #{policy.inspect}, must be one of: #{policies.inspect}" unless policies.include?(policy)
  rules_array = rules_for(name)
  value       = { minimum: value.min, maximum: value.max } if Range === value
  new_rule    = { value: value.freeze, policy: policy, conditions: conditions.freeze }.freeze
  unless rules_array.include?(new_rule)
    rules_array << new_rule
    # Sort rules so that the highest policy level is sorted first and then the most complex rule before the more general ones
    rules_array.sort_by! { |rule| [policies.index(rule[:policy]), -rule[:conditions].size] }
    conditions_whitelist_for(name).merge conditions.keys
    @dirty = true
  end
  self
end

#as_hashObject



50
51
52
53
54
55
# File 'lib/torm/rules_engine.rb', line 50

def as_hash
  {
    policies: policies,
    rules:    rules
  }
end

#decide(name, environment = {}) ⇒ Object



41
42
43
44
45
46
47
48
# File 'lib/torm/rules_engine.rb', line 41

def decide(name, environment={})
  raise "Unknown rule: #{name.inspect}" unless rules.has_key?(name)
  environment          = Torm.symbolize_keys(environment)
  decision_environment = Torm.slice(environment, *conditions_whitelist_for(name))
  answer               = make_decision(name, decision_environment)
  #Rails.logger.debug "DECISION: #{answer.inspect} (#{name.inspect} -> #{environment.inspect})"
  answer
end

#dirty?Boolean

Have any rules been added since the last save or load?

Returns:

  • (Boolean)


19
20
21
# File 'lib/torm/rules_engine.rb', line 19

def dirty?
  @dirty
end

#save(rules_file: self.class.rules_file) ⇒ Object

Save the current rules to a file.



102
103
104
105
106
# File 'lib/torm/rules_engine.rb', line 102

def save(rules_file: self.class.rules_file)
  Torm.atomic_save(rules_file, to_json + "\n")
  @dirty = false
  nil
end

#to_jsonObject



57
58
59
# File 'lib/torm/rules_engine.rb', line 57

def to_json
  MultiJson.dump(as_hash)
end