lib/torm/rules_engine.rb in torm-0.0.1 vs lib/torm/rules_engine.rb in torm-0.1.0

- old
+ new

@@ -1,23 +1,24 @@ module Torm - class RulesEngine + class RulesEngine # Policies (priorities) in order of important -> least important. DEFAULT_POLICIES = [:law, :coc, :experiment, :default].freeze - attr_reader :rules - attr_accessor :verbose, :policies, :conditions_whitelist - attr_accessor :dirty + attr_reader :rules, :conditions_whitelist + attr_accessor :policies + attr_accessor :dirty, :rules_file - def initialize(rules: {}, conditions_whitelist: {}, dirty: false, policies: DEFAULT_POLICIES.dup, verbose: false) + def initialize(rules: {}, dirty: false, policies: DEFAULT_POLICIES.dup, rules_file: Torm.default_rules_file) @rules = rules - @conditions_whitelist = conditions_whitelist @dirty = dirty @policies = policies - @verbose = verbose + @rules_file = rules_file + @conditions_whitelist = {} end # Have any rules been added since the last save or load? + # @return [true, false] def dirty? @dirty end # Add a new rule. @@ -36,26 +37,56 @@ @dirty = true end self end + # Simple helper class to add the block DSL to add_rules + class RuleVariationHelper + def initialize(engine, name) + @engine = engine + @name = name + end + + def variation(value, policy, **conditions) + @engine.add_rule(@name, value, policy, conditions) + end + end + + def add_rules(name, value, policy) + # Add the default rule + add_rule(name, value, policy) + + rule_variation = RuleVariationHelper.new(self, name) + yield rule_variation if block_given? + + self + end + + # Evaluate a rule and return its result. Depending on the rule, different values are returned. + # + # @raise [RuntimeError] Raise when the rule is not defined. 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 + # Return a hash with all rules and policies, useful for serialisation. + # + # @return [Hash] def as_hash { policies: policies, rules: rules } end + # Serialise the data from +as_hash+. + # + # @return [String] def to_json MultiJson.dump(as_hash) end # 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. @@ -78,38 +109,34 @@ end engine.dirty = false engine end - # Where we store the rules file. - def self.rules_file - Rails.root.join('tmp', 'rules.json').to_s - end - # 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. # # @return [Torm::RulesEngine] A new engine with the loaded rules def self.load(rules_file: Torm.default_rules_file) if File.exist?(rules_file) - json = File.read(rules_file) - self.from_json(json) + json = File.read(rules_file) + engine = self.from_json(json) + engine.rules_file = rules_file + engine else nil end end - # Save the current rules to a file. - def save(rules_file: self.class.rules_file) + # Save the current rules to the file. + def save Torm.atomic_save(rules_file, to_json + "\n") @dirty = false nil end private - # TODO: Refactor once useful def make_decision(name, environment={}) # Fetch all rules for this decision. Duplicate to allow us to manipulate the Array with #reject! relevant_rules = rules_for(name).dup # Filter through all rules. Eliminate the rules not matching our environment. @@ -172,16 +199,8 @@ conditions_whitelist[name] ||= Set.new end def rules_for(name) rules[name] ||= [] - end - - def puts(message) - Kernel.puts(message) if verbose - end - - def pp(object) - Kernel.pp(object) if verbose end end end