Class: Nodepile::RuleRecordEvaluator
- Inherits:
-
Object
- Object
- Nodepile::RuleRecordEvaluator
- Defined in:
- lib/nodepile/rule_eval.rb
Overview
Represents a single rule record as represernted by a KeyedArrayAccessor object. Note that this class does not rely on metadata of the KeyedArrayAccessor and doesn’t even verify that the object it represents actually contains any formulas.
Generally speaking, any field whose first character is a question mark is considered to have a calculation/formula in it. Formulas (not counting the question mark) are simply ruby expressions. The formulas in the id fields (‘_id’, ‘_links_to’, and ‘_links_from’) are given special treatment as described below.
Dynamic calculations must start with the question mark character ‘?’. They will use the Ruby language itself with a tightly constrained binding that defines one primary object simply named “v” (standing for values). That object only supports a handful of operations including:
v['fielaname'] to evaluate a field
v.include?('fieldname') to determine whether the field exists
v[:this] to evaluate this field without having to explicitly name it
of fields. Note that blank “fields” will often return nil and also non-existent fields will be nil.
Note, that some exceptional calculation rules may be triggered if this calculation is for an id field. See the #uses_id_calcs?() method.
Defined Under Namespace
Constant Summary collapse
- EDGE_ID_FIELDS =
['_links_from','_links_to'].freeze
- NODE_ID_FIELDS =
['_id'].freeze
- ID_FIELD_NAMES =
(NODE_ID_FIELDS + EDGE_ID_FIELDS).freeze
Class Method Summary collapse
-
.eval_calc(rule_field_defn, this_field_name, eval_against_key_value_map) ⇒ Object
Evaluate a calculation using standard logic.
Instance Method Summary collapse
-
#calculate_rule(otr) ⇒ KeyedArrayAccessor
Calculates the “value” of the rule when applied to a specific record NOTE: this does not test for #match_record?() which you probably want to do first.
-
#initialize(rule_record_kaa) ⇒ RuleRecordEvaluator
constructor
A new instance of RuleRecordEvaluator.
-
#match_record?(otr) ⇒ Boolean
Confirm that this particular rule applies to the given node or edge.
-
#uses_dynamic_match? ⇒ Boolean
Returns true if the calculation used by #match_record?() uses complex calculation logic.
Constructor Details
#initialize(rule_record_kaa) ⇒ RuleRecordEvaluator
Returns a new instance of RuleRecordEvaluator.
33 34 35 36 37 |
# File 'lib/nodepile/rule_eval.rb', line 33 def initialize(rule_record_kaa) @kaa = rule_record_kaa @match_fields = @kaa['_id'].nil? ? EDGE_ID_FIELDS : NODE_ID_FIELDS # assuming it's well formed @match_type = nil end |
Class Method Details
.eval_calc(rule_field_defn, this_field_name, eval_against_key_value_map) ⇒ Object
Evaluate a calculation using standard logic. Formulas may use syntax to reference the values of the eval_against_key_value_map object. For example “?v[‘column X’].to_f > 17.2 ? ‘red’ : ‘black’” calcs based on ‘column X’ contents
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
# File 'lib/nodepile/rule_eval.rb', line 93 def self.eval_calc(rule_field_defn,this_field_name,eval_against_key_value_map) return rule_field_defn unless rule_field_defn.start_with?('?') begin ruby_code = rule_field_defn.dup.tap{|s| s[0] = ' '} # get rid of leading question mark val = EvalFrame.evaluate(ruby_code,this_field_name,eval_against_key_value_map) rescue StandardError => e #TODO: Probably will need to remove this and replace with more fault # tolerant strategy that fails gracefully (perhaps by nil-valuing the field) raise "Error attempting to evaluate this formula { #{rule_field_defn} } : #{e.}" end case val when true,false,nil #no-op when Regexp if /^?\s*\//.match?(rule_field_defn) && ID_FIELD_NAMES.include?(this_field_name) val = val.match?(eval_against_key_value_map[this_field_name]) else raise "Rule should evaluate to a 'boolean' except when triggering Regex/glob matching on an id field." end when String if /^?\s*['"]/.match?(rule_field_defn) && ID_FIELD_NAMES.include?(this_field_name) val = File.fnmatch?(val,eval_against_key_value_map[this_field_name]) else #no-op... returning a string is a good behavior for calculations end else raise "For field [#{this_field_name}] the rule expression must evaluate to true, false, or nil except when triggering regex/glob matching on an id field." end #case return val end |
Instance Method Details
#calculate_rule(otr) ⇒ KeyedArrayAccessor
Calculates the “value” of the rule when applied to a specific record NOTE: this does not test for #match_record?() which you probably want to do first. Note that the three id fields are ALWAYS left as nil after this method. Calculated values will be coerced to string via #to_s so if the default behavior isn’t the right one for you, you should convert to string yourself This also doesn’t test whether the records conform.
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/nodepile/rule_eval.rb', line 74 def calculate_rule(otr) kaa = @kaa.dup kaa.kv_map!{|k,v| if v.nil? #no-op elsif ID_FIELD_NAMES.include?(k) nil # we never overlay key fields elsif v.start_with?('?') self.class.eval_calc(v,k,otr)&.to_s else v # leave field unaltered by calculation logic end } return kaa end |
#match_record?(otr) ⇒ Boolean
Confirm that this particular rule applies to the given node or edge
44 45 46 47 48 49 50 51 52 53 |
# File 'lib/nodepile/rule_eval.rb', line 44 def match_record?(otr) return @match_fields.all?{|key| myval = @kaa[key] if myval.start_with?('?') self.class.eval_calc(myval,key,otr) else myval == otr[key] end } end |
#uses_dynamic_match? ⇒ Boolean
Returns true if the calculation used by #match_record?() uses complex calculation logic
57 58 59 60 61 62 |
# File 'lib/nodepile/rule_eval.rb', line 57 def uses_dynamic_match? @match_type ||= @match_fields.any?{|k| @kaa[k].then{|v| v.start_with?('?') && v.include?('v[')} } ? :dynamic : :static return @match_type == :dynamic end |