rools 0.2 → 0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +7 -0
- data/RAKEFILE +13 -3
- data/README +1 -1
- data/lib/rools/default_parameter_proc.rb +11 -13
- data/lib/rools/rule.rb +32 -18
- data/lib/rools/rule_set.rb +55 -34
- data/lib/rools/version.rb +2 -2
- metadata +2 -2
data/CHANGELOG
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
= Rools CHANGELOG
|
2
2
|
|
3
|
+
== Rools - 0.3
|
4
|
+
* facts can now have a defined namespace
|
5
|
+
* fixed csv_test
|
6
|
+
* added load_xml_rules_as_string and load_rb_rules_as_string
|
7
|
+
* 100% test coverage
|
8
|
+
* Applied doc patch from John Mettraux
|
9
|
+
|
3
10
|
== Rools - 0.2 released 2007/05/22
|
4
11
|
* specifications have been created using RSpec
|
5
12
|
* fix bug #10985 regarding the support of rule extension
|
data/RAKEFILE
CHANGED
@@ -144,20 +144,30 @@ end
|
|
144
144
|
task :upload_website => [:doc] do
|
145
145
|
sh """
|
146
146
|
rsync -azv -e ssh \
|
147
|
+
--exclude='.svn' --delete-excluded \
|
147
148
|
html/ \
|
148
|
-
|
149
|
+
cappelaere@rubyforge.org:/var/www/gforge-projects/rools \
|
149
150
|
"""
|
150
151
|
sh """
|
151
152
|
rsync -azv -e ssh \
|
152
153
|
--exclude='.svn' --delete-excluded \
|
153
154
|
doc/res/defs \
|
154
|
-
|
155
|
+
cappelaere@rubyforge.org:/var/www/gforge-projects/rools \
|
155
156
|
"""
|
156
157
|
sh """
|
157
158
|
rsync -azv -e ssh \
|
158
159
|
--exclude='.svn' --delete-excluded \
|
159
160
|
examples \
|
160
|
-
|
161
|
+
cappelaere@rubyforge.org:/var/www/gforge-projects/rools \
|
162
|
+
"""
|
163
|
+
end
|
164
|
+
|
165
|
+
task :upload_html do
|
166
|
+
sh """
|
167
|
+
rsync -azv -e ssh \
|
168
|
+
--exclude='.svn' \
|
169
|
+
html \
|
170
|
+
cappelaere@rubyforge.org:/var/www/gforge-projects/rools \
|
161
171
|
"""
|
162
172
|
end
|
163
173
|
|
data/README
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
|
2
2
|
= Rools -- A pure ruby rules-engine
|
3
3
|
|
4
4
|
Rools is a rules engine for abstracting business logic and program-flow. It's ideally suited to processing applications where the business logic undergoes frequent modification.
|
@@ -25,15 +25,15 @@ module Rools
|
|
25
25
|
raise ArgumentError.new('The "rule" parameter must respond to an :assert method') unless rule.respond_to?(:assert)
|
26
26
|
@rule = rule
|
27
27
|
@proc = b
|
28
|
-
|
28
|
+
#@working_object = nil
|
29
29
|
end
|
30
30
|
|
31
31
|
# Call the bound block and set the working object so that it
|
32
32
|
# can be referred to by method_missing
|
33
33
|
def call(obj)
|
34
|
-
|
34
|
+
#@working_object = obj
|
35
35
|
status = instance_eval(&@proc)
|
36
|
-
|
36
|
+
#@working_object = nil
|
37
37
|
return status
|
38
38
|
end
|
39
39
|
|
@@ -45,22 +45,20 @@ module Rools
|
|
45
45
|
# Parameterless method calls by the attached block are assumed to
|
46
46
|
# be references to the working object
|
47
47
|
def method_missing(sym, *args)
|
48
|
-
#puts "method missing: #{sym}"
|
48
|
+
# puts "method missing: #{sym} args:#{args.inspect}"
|
49
49
|
# check if it is a fact first
|
50
|
-
begin
|
50
|
+
#begin
|
51
51
|
facts = @rule.rule_set.get_facts
|
52
52
|
if facts.has_key?( sym.to_s )
|
53
53
|
#puts "return fact #{facts[sym.to_s].value}"
|
54
54
|
return facts[sym.to_s].value
|
55
|
-
|
56
|
-
|
55
|
+
else
|
56
|
+
raise Exception, "symbol: #{sym} not found in facts"
|
57
57
|
end
|
58
|
-
rescue Exception => e
|
59
|
-
|
60
|
-
|
61
|
-
end
|
62
|
-
#return @working_object if @working_object && args.size == 0
|
63
|
-
#return nil
|
58
|
+
#rescue Exception => e
|
59
|
+
# puts "miss exception #{e} #{e.backtrace.join("\n")}"
|
60
|
+
# return nil
|
61
|
+
#end
|
64
62
|
end
|
65
63
|
|
66
64
|
# Stops the current assertion. Does not indicate failure.
|
data/lib/rools/rule.rb
CHANGED
@@ -60,8 +60,16 @@ module Rools
|
|
60
60
|
# To verify that the asserted object is an Employee, that inherits from
|
61
61
|
# Person, and responds to :department
|
62
62
|
def parameters(*matches)
|
63
|
-
logger.debug( "Adding parameters: #{matches}") if logger
|
64
|
-
|
63
|
+
logger.debug( "Adding parameters: #{matches.inspect}") if logger
|
64
|
+
# @parameters += matches
|
65
|
+
@parameters << matches
|
66
|
+
end
|
67
|
+
|
68
|
+
#
|
69
|
+
# returns parameters of rule
|
70
|
+
#
|
71
|
+
def get_parameters
|
72
|
+
@parameters
|
65
73
|
end
|
66
74
|
|
67
75
|
# parameters is aliased to aid in readability if you decide
|
@@ -70,25 +78,31 @@ module Rools
|
|
70
78
|
|
71
79
|
# Checks to see if this Rule's parameters match the asserted object
|
72
80
|
def parameters_match?(obj)
|
73
|
-
@parameters.each do |p|
|
74
|
-
logger.debug( "#{self} match p:#{p} obj:#{obj} sym:#{Symbol}") if logger
|
75
|
-
if p.is_a?(Symbol)
|
76
|
-
#return false unless obj.respond_to?(p)
|
77
|
-
return true if obj.respond_to?(p)
|
78
|
-
else
|
79
|
-
logger.debug( "#{self} is_a p:#{p} obj:#{obj} #{obj.is_a?(p)}") if logger
|
80
|
-
#return false unless obj.is_a?(p)
|
81
|
-
return true if obj.is_a?(p)
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
81
|
# if parameters are not specified, let's assume that the rule is always relevant
|
86
82
|
if @parameters.size == 0
|
87
83
|
logger.debug "no parameters defined for rule: #{self}" if logger
|
88
84
|
return true
|
89
85
|
end
|
90
86
|
|
91
|
-
|
87
|
+
@parameters.each do |params|
|
88
|
+
match = false
|
89
|
+
|
90
|
+
params.each do |p|
|
91
|
+
logger.debug( "#{self} match p:#{p} obj:#{obj}") if logger
|
92
|
+
|
93
|
+
if p.is_a?(Symbol)
|
94
|
+
if obj.respond_to?(p)
|
95
|
+
match = true
|
96
|
+
else
|
97
|
+
return false
|
98
|
+
end
|
99
|
+
elsif obj.is_a?(p)
|
100
|
+
match = true
|
101
|
+
end
|
102
|
+
end
|
103
|
+
return true if match
|
104
|
+
end
|
105
|
+
|
92
106
|
return false
|
93
107
|
end
|
94
108
|
|
@@ -98,7 +112,7 @@ module Rools
|
|
98
112
|
begin
|
99
113
|
@conditions.each { |c| return false unless c.call(obj) }
|
100
114
|
rescue StandardError => e
|
101
|
-
logger.error( "
|
115
|
+
logger.error( "conditions_match? StandardError #{e} #{e.backtrace.join("\n")}") if logger
|
102
116
|
raise RuleCheckError.new(self, e)
|
103
117
|
end
|
104
118
|
|
@@ -118,10 +132,10 @@ module Rools
|
|
118
132
|
@consequences.each do |c|
|
119
133
|
c.call(obj)
|
120
134
|
end
|
121
|
-
rescue
|
135
|
+
rescue Exception => e
|
122
136
|
# discontinue the Rools::RuleSet#assert if any consequence fails
|
123
137
|
logger.error( "rule RuleConsequenceError #{e.to_s} #{e.backtrace.join("\n")}") if logger
|
124
|
-
raise RuleConsequenceError.new(
|
138
|
+
raise RuleConsequenceError.new(self, e)
|
125
139
|
end
|
126
140
|
end
|
127
141
|
|
data/lib/rools/rule_set.rb
CHANGED
@@ -61,8 +61,17 @@ module Rools
|
|
61
61
|
#
|
62
62
|
def load_xml( fileName )
|
63
63
|
begin
|
64
|
-
|
65
|
-
|
64
|
+
str = IO.read(fileName)
|
65
|
+
load_xml_rules_as_string(str)
|
66
|
+
rescue Exception => e
|
67
|
+
raise RuleLoadingError, "loading xml file"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# load xml rules as a string
|
72
|
+
def load_xml_rules_as_string( str )
|
73
|
+
begin
|
74
|
+
doc = REXML::Document.new str
|
66
75
|
doc.elements.each( "rule-set") { |rs|
|
67
76
|
facts = rs.elements.each( "facts") { |f|
|
68
77
|
facts( f.attributes["name"] ) do f.text.strip end
|
@@ -100,10 +109,20 @@ module Rools
|
|
100
109
|
end
|
101
110
|
|
102
111
|
#
|
103
|
-
# Ruby File format
|
112
|
+
# Ruby File format loading
|
104
113
|
#
|
105
114
|
def load_rb( file )
|
106
|
-
|
115
|
+
begin
|
116
|
+
str = IO.read(file)
|
117
|
+
load_rb_rules_as_string(str)
|
118
|
+
rescue Exception => e
|
119
|
+
raise RuleLoadingError, "loading ruby file"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# load ruby rules as a string
|
124
|
+
def load_rb_rules_as_string( str )
|
125
|
+
instance_eval(str)
|
107
126
|
end
|
108
127
|
|
109
128
|
#
|
@@ -136,25 +155,28 @@ module Rools
|
|
136
155
|
# facts can be created in a similar manner to rules
|
137
156
|
# all names are converted to strings and downcased.
|
138
157
|
# Facts name is equivalent to a Class Name
|
158
|
+
#
|
139
159
|
# ==Example
|
140
|
-
#
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
160
|
+
#
|
161
|
+
# require 'rools'
|
162
|
+
#
|
163
|
+
# rules = Rools::RuleSet.new do
|
164
|
+
#
|
165
|
+
# facts 'Countries' do
|
166
|
+
# ["China", "USSR", "France", "Great Britain", "USA"]
|
167
|
+
# end
|
168
|
+
#
|
169
|
+
# rule 'Is it on Security Council?' do
|
170
|
+
# parameter String
|
171
|
+
# condition { countries.include?(string) }
|
172
|
+
# consequence { puts "Yes, #{string} is in the country list"}
|
173
|
+
# end
|
174
|
+
# end
|
154
175
|
#
|
155
|
-
|
176
|
+
# rules.assert 'France'
|
156
177
|
#
|
157
178
|
def facts(name, &b)
|
179
|
+
name.gsub!(/:/, '_')
|
158
180
|
name.to_s.downcase!
|
159
181
|
@facts[name] = Facts.new(self, name, b)
|
160
182
|
logger.debug( "created facts: #{name}") if logger
|
@@ -163,11 +185,11 @@ module Rools
|
|
163
185
|
# A single fact can be an single object of a particular class type
|
164
186
|
# or a collection of objects of a particular type
|
165
187
|
def fact( obj )
|
166
|
-
begin
|
188
|
+
#begin
|
167
189
|
# check if facts already exist for that class
|
168
190
|
# if so, we need to add it to the existing list
|
169
191
|
cls = obj.class.to_s.downcase
|
170
|
-
|
192
|
+
cls.gsub!(/:/, '_')
|
171
193
|
if @facts.key? cls
|
172
194
|
logger.debug( "adding to facts: #{cls}") if logger
|
173
195
|
@facts[cls].fact_value << obj
|
@@ -178,9 +200,9 @@ module Rools
|
|
178
200
|
proc = Proc.new { arr }
|
179
201
|
@facts[cls] = Facts.new(self, cls, proc )
|
180
202
|
end
|
181
|
-
rescue Exception=> e
|
182
|
-
|
183
|
-
end
|
203
|
+
#rescue Exception=> e
|
204
|
+
# logger.error e if logger
|
205
|
+
#end
|
184
206
|
end
|
185
207
|
|
186
208
|
# Delete all existing facts
|
@@ -289,7 +311,7 @@ module Rools
|
|
289
311
|
get_relevant_rules()
|
290
312
|
logger.debug("no relevant rules") if logger && @relevant_rules.size==0
|
291
313
|
|
292
|
-
begin #rescue
|
314
|
+
#begin #rescue
|
293
315
|
|
294
316
|
# loop through the available_rules, evaluating each one,
|
295
317
|
# until there are no more matching rules available
|
@@ -331,23 +353,22 @@ module Rools
|
|
331
353
|
break
|
332
354
|
end # if rule.conditions_match?(obj)
|
333
355
|
|
356
|
+
rescue RuleConsequenceError
|
357
|
+
fail
|
334
358
|
rescue RuleCheckError => e
|
335
|
-
|
336
|
-
logger.error( "RuleCheckError") if logger
|
337
|
-
@relevant_rules.delete(e.rule)
|
338
|
-
@status = fail
|
359
|
+
fail
|
339
360
|
end # begin/rescue
|
340
361
|
|
341
362
|
end # available_rules.each
|
342
363
|
|
343
364
|
end while(matches && @assert)
|
344
365
|
|
345
|
-
rescue RuleConsequenceError => rce
|
366
|
+
#rescue RuleConsequenceError => rce
|
346
367
|
# RuleConsequenceErrors are allowed to break out of the current assertion,
|
347
368
|
# then the inner error is bubbled-up to the asserting code.
|
348
|
-
|
349
|
-
|
350
|
-
end
|
369
|
+
# @status = FAIL
|
370
|
+
# raise rce.inner_error
|
371
|
+
#end
|
351
372
|
|
352
373
|
@assert = false
|
353
374
|
|
@@ -355,4 +376,4 @@ module Rools
|
|
355
376
|
end
|
356
377
|
|
357
378
|
end # class RuleSet
|
358
|
-
end # module Rools
|
379
|
+
end # module Rools
|
data/lib/rools/version.rb
CHANGED
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.2
|
|
3
3
|
specification_version: 1
|
4
4
|
name: rools
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: "0.
|
7
|
-
date: 2007-
|
6
|
+
version: "0.3"
|
7
|
+
date: 2007-07-18 00:00:00 -04:00
|
8
8
|
summary: A Rules Engine written in Ruby
|
9
9
|
require_paths:
|
10
10
|
- lib
|