Class: Rools::RuleSet
Constant Summary collapse
- PASS =
:pass
- FAIL =
:fail
- UNDETERMINED =
:undetermined
Instance Attribute Summary collapse
-
#facts(name, &b) ⇒ Object
readonly
facts can be created in a similar manner to rules all names are converted to strings and downcased.
-
#num_evaluated ⇒ Object
readonly
Returns the value of attribute num_evaluated.
-
#num_executed ⇒ Object
readonly
Returns the value of attribute num_executed.
-
#status ⇒ Object
readonly
Returns the value of attribute status.
Instance Method Summary collapse
-
#add_relevant_rules_for_fact(fact) ⇒ Object
for a particular fact, we need to retrieve the relevant rules and add them to the relevant list.
-
#assert(*objs) ⇒ Object
Turn passed object into facts and evaluate all relevant rules Previous facts of same type are removed.
-
#delete_facts ⇒ Object
Delete all existing facts.
-
#evaluate ⇒ Object
evaluate all relevant rules for specified facts.
-
#extend(name, &b) ⇒ Object
Use in conjunction with Rools::RuleSet#with to create a Rools::Rule dependent on another.
-
#fact(obj) ⇒ Object
A single fact can be an single object of a particular class type or a collection of objects of a particular type.
-
#fail(message = nil) ⇒ Object
Stops the current assertion and change status to :fail.
-
#get_facts ⇒ Object
returns an array of facts.
-
#get_relevant_rules ⇒ Object
get all relevant rules for all specified facts.
-
#get_rules ⇒ Object
returns all the rules defined for that set.
-
#initialize(file = nil, &b) ⇒ RuleSet
constructor
You can pass a set of Rools::Rules with a block parameter, or you can pass a file-path to evaluate.
-
#load_csv(file) ⇒ Object
Loads decision table.
-
#load_rb(file) ⇒ Object
Ruby File format loading.
-
#load_rb_rules_as_string(str) ⇒ Object
load ruby rules as a string.
-
#load_xml(fileName) ⇒ Object
XML File format loading.
-
#load_xml_rules_as_string(str) ⇒ Object
load xml rules as a string.
-
#rule(name, priority = 0, &b) ⇒ Object
rule creates a Rools::Rule.
-
#rule_assert(obj) ⇒ Object
an assert has been made within a rule.
-
#sort_relevant_rules ⇒ Object
relevant rules need to be sorted in priority order.
-
#stop(message = nil) ⇒ Object
Stops the current assertion.
-
#with(name, prio = 0, &b) ⇒ Object
Used in conjunction with Rools::RuleSet#extend to create a dependent Rools::Rule ==Example extend(‘ruby is the best’).with(‘ruby rules the world’) do condition { language.age > 15 } consequence { “In the year 2008 Ruby conquered the known universe” } end.
Methods inherited from Base
Constructor Details
#initialize(file = nil, &b) ⇒ RuleSet
You can pass a set of Rools::Rules with a block parameter, or you can pass a file-path to evaluate.
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
# File 'lib/rools/rule_set.rb', line 19 def initialize(file = nil, &b) @rules = {} @facts = {} @dependencies = {} if block_given? instance_eval(&b) elsif file # loading a file, check extension name,ext = file.split(".") logger.debug("loading ext: #{name}.#{ext}") if logger case ext when 'csv' load_csv( file ) when 'xml' load_xml( file ) when 'rb' load_rb( file ) when 'rules' # for backwards compatibility load_rb(file) else raise RuleLoadingError, "invalid file extension: #{ext}" end end end |
Instance Attribute Details
#facts(name, &b) ⇒ Object (readonly)
facts can be created in a similar manner to rules all names are converted to strings and downcased. Facts name is equivalent to a Class Name
Example
require 'rools'
rules = Rools::RuleSet.new do
facts 'Countries' do
["China", "USSR", "France", "Great Britain", "USA"]
end
rule 'Is it on Security Council?' do
parameter String
condition { countries.include?(string) }
consequence { puts "Yes, #{string} is in the country list"}
end
end
rules.assert 'France'
178 179 180 |
# File 'lib/rools/rule_set.rb', line 178 def facts @facts end |
#num_evaluated ⇒ Object (readonly)
Returns the value of attribute num_evaluated.
11 12 13 |
# File 'lib/rools/rule_set.rb', line 11 def num_evaluated @num_evaluated end |
#num_executed ⇒ Object (readonly)
Returns the value of attribute num_executed.
11 12 13 |
# File 'lib/rools/rule_set.rb', line 11 def num_executed @num_executed end |
#status ⇒ Object (readonly)
Returns the value of attribute status.
11 12 13 |
# File 'lib/rools/rule_set.rb', line 11 def status @status end |
Instance Method Details
#add_relevant_rules_for_fact(fact) ⇒ Object
for a particular fact, we need to retrieve the relevant rules and add them to the relevant list
272 273 274 275 276 277 278 279 280 281 282 283 |
# File 'lib/rools/rule_set.rb', line 272 def add_relevant_rules_for_fact fact @rules.values.select { |rule| if !@relevant_rules.include?( rule) if rule.parameters_match?(fact.value) @relevant_rules << rule logger.debug "#{rule} is relevant" if logger else logger.debug "#{rule} is not relevant" if logger end end } end |
#assert(*objs) ⇒ Object
Turn passed object into facts and evaluate all relevant rules Previous facts of same type are removed
261 262 263 264 265 266 |
# File 'lib/rools/rule_set.rb', line 261 def assert( *objs ) objs.each { |obj| fact(obj) } return evaluate() end |
#delete_facts ⇒ Object
Delete all existing facts
209 210 211 |
# File 'lib/rools/rule_set.rb', line 209 def delete_facts @facts = {} end |
#evaluate ⇒ Object
evaluate all relevant rules for specified facts
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 |
# File 'lib/rools/rule_set.rb', line 305 def evaluate @status = PASS @assert = true @num_executed = 0; @num_evaluated = 0; get_relevant_rules() logger.debug("no relevant rules") if logger && @relevant_rules.size==0 #begin #rescue # loop through the available_rules, evaluating each one, # until there are no more matching rules available begin # loop # the loop condition is reset to break by default after every iteration matches = false obj = nil #deprecated #logger.debug("available rules: #{available_rules.size.to_s}") if logger @relevant_rules.each do |rule| # RuleCheckErrors are caught and swallowed and the rule that # raised the error is removed from the working-set. logger.debug("evaluating: #{rule}") if logger begin @num_evaluated += 1 if rule.conditions_match?(obj) logger.debug("rule #{rule} matched") if logger matches = true # remove the rule from the working-set so it's not re-evaluated @relevant_rules.delete(rule) # find all parameter-matching dependencies of this rule and # add them to the working-set. if @dependencies.has_key?(rule.name) logger.debug( "found dependant rules to #{rule}") if logger @relevant_rules += @dependencies[rule.name].select do |dependency| dependency.parameters_match?(obj) end end # execute this rule logger.debug("executing rule #{rule}") if logger rule.call(obj) @num_executed += 1 # break the current iteration and start back from the first rule defined. break end # if rule.conditions_match?(obj) rescue RuleConsequenceError fail rescue RuleCheckError => e fail end # begin/rescue end # available_rules.each end while(matches && @assert) #rescue RuleConsequenceError => rce # RuleConsequenceErrors are allowed to break out of the current assertion, # then the inner error is bubbled-up to the asserting code. # @status = FAIL # raise rce.inner_error #end @assert = false return @status end |
#extend(name, &b) ⇒ Object
Use in conjunction with Rools::RuleSet#with to create a Rools::Rule dependent on another. Dependencies are created through names (converted to strings and downcased), so lax naming can get you into trouble with creating dependencies or overwriting rules you didn’t mean to.
217 218 219 220 221 222 |
# File 'lib/rools/rule_set.rb', line 217 def extend(name, &b) name.to_s.downcase! @extend_rule_name = name instance_eval(&b) if block_given? return self end |
#fact(obj) ⇒ Object
A single fact can be an single object of a particular class type or a collection of objects of a particular type
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 |
# File 'lib/rools/rule_set.rb', line 187 def fact( obj ) #begin # check if facts already exist for that class # if so, we need to add it to the existing list cls = obj.class.to_s.downcase cls.gsub!(/:/, '_') if @facts.key? cls logger.debug( "adding to facts: #{cls}") if logger @facts[cls].fact_value << obj else logger.debug( "creating facts: #{cls}") if logger arr = Array.new arr << obj proc = Proc.new { arr } @facts[cls] = Facts.new(self, cls, proc ) end #rescue Exception=> e # logger.error e if logger #end end |
#fail(message = nil) ⇒ Object
Stops the current assertion and change status to :fail
242 243 244 245 |
# File 'lib/rools/rule_set.rb', line 242 def fail( = nil) @status = FAIL @assert = false end |
#get_facts ⇒ Object
returns an array of facts
131 132 133 |
# File 'lib/rools/rule_set.rb', line 131 def get_facts @facts end |
#get_relevant_rules ⇒ Object
get all relevant rules for all specified facts
296 297 298 299 300 301 302 |
# File 'lib/rools/rule_set.rb', line 296 def get_relevant_rules @relevant_rules = Array.new @facts.each { |k,f| add_relevant_rules_for_fact f } sort_relevant_rules end |
#get_rules ⇒ Object
returns all the rules defined for that set
138 139 140 |
# File 'lib/rools/rule_set.rb', line 138 def get_rules @rules end |
#load_csv(file) ⇒ Object
Loads decision table
53 54 55 56 57 |
# File 'lib/rools/rule_set.rb', line 53 def load_csv( file ) csv = CsvTable.new( file ) logger.debug "csv rules: #{csv.rules}" if logger instance_eval(csv.rules) end |
#load_rb(file) ⇒ Object
Ruby File format loading
114 115 116 117 118 119 120 121 |
# File 'lib/rools/rule_set.rb', line 114 def load_rb( file ) begin str = IO.read(file) load_rb_rules_as_string(str) rescue Exception => e raise RuleLoadingError, "loading ruby file" end end |
#load_rb_rules_as_string(str) ⇒ Object
load ruby rules as a string
124 125 126 |
# File 'lib/rools/rule_set.rb', line 124 def load_rb_rules_as_string( str ) instance_eval(str) end |
#load_xml(fileName) ⇒ Object
XML File format loading
62 63 64 65 66 67 68 69 |
# File 'lib/rools/rule_set.rb', line 62 def load_xml( fileName ) begin str = IO.read(fileName) load_xml_rules_as_string(str) rescue Exception => e raise RuleLoadingError, "loading xml file" end end |
#load_xml_rules_as_string(str) ⇒ Object
load xml rules as a string
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
# File 'lib/rools/rule_set.rb', line 72 def load_xml_rules_as_string( str ) begin doc = REXML::Document.new str doc.elements.each( "rule-set") { |rs| facts = rs.elements.each( "facts") { |f| facts( f.attributes["name"] ) do f.text.strip end } rules = rs.elements.each( "rule") { |rule_node| rule_name = rule_node.attributes["name"] priority = rule_node.attributes["priority"] rule = Rule.new(self, rule_name, priority, nil) parameters = rule_node.elements.each("parameter") { |param| #logger.debug "xml parameter: #{param.text.strip}" rule.parameters(eval(param.text.strip)) } conditions = rule_node.elements.each("condition") { |cond| #logger.debug "xml condition #{cond}" rule.condition do eval(cond.text.strip) end } consequences = rule_node.elements.each("consequence") { |cons| #logger.debug "xml consequence #{cons}" rule.consequence do eval(cons.text.strip) end } @rules[rule_name] = rule } logger.debug( "loaded #{rules.size} rules") if logger } rescue Exception => e raise RuleLoadingError, "loading xml file" end end |
#rule(name, priority = 0, &b) ⇒ Object
rule creates a Rools::Rule. Make sure to use a descriptive name or symbol. For the purposes of extending Rules, all names are converted to strings and downcased.
Example
rule 'ruby is the best' do
condition { language.name.downcase == 'ruby' }
consequence { "#{language.name} is the best!" }
end
150 151 152 153 |
# File 'lib/rools/rule_set.rb', line 150 def rule(name, priority=0, &b) name.to_s.downcase! @rules[name] = Rule.new(self, name, priority, b) end |
#rule_assert(obj) ⇒ Object
an assert has been made within a rule
250 251 252 253 254 255 256 257 |
# File 'lib/rools/rule_set.rb', line 250 def rule_assert( obj ) # add object as a new fact f = fact(obj) # get_relevant_rules logger.debug( "Check if we need to add more rules") if logger add_relevant_rules_for_fact(f) sort_relevant_rules end |
#sort_relevant_rules ⇒ Object
relevant rules need to be sorted in priority order
288 289 290 291 292 293 |
# File 'lib/rools/rule_set.rb', line 288 def sort_relevant_rules # sort array in rule priority order @relevant_rules = @relevant_rules.sort do |r1, r2| r2.priority <=> r1.priority end end |
#stop(message = nil) ⇒ Object
Stops the current assertion. Does not indicate failure.
237 238 239 |
# File 'lib/rools/rule_set.rb', line 237 def stop( = nil) @assert = false end |
#with(name, prio = 0, &b) ⇒ Object
Used in conjunction with Rools::RuleSet#extend to create a dependent Rools::Rule
Example
extend('ruby is the best').with('ruby rules the world') do
condition { language.age > 15 }
consequence { "In the year 2008 Ruby conquered the known universe" }
end
230 231 232 233 234 |
# File 'lib/rools/rule_set.rb', line 230 def with(name, prio=0, &b) name.to_s.downcase! (@dependencies[@extend_rule_name] ||= []) << Rule.new(self, name, prio, b) #@rules[name] = Rule.new(self, name, prio, b) end |