ruler 1.2.0 → 1.4.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. data/Gemfile +1 -0
  2. data/VERSION +1 -1
  3. data/lib/ruler.rb +37 -17
  4. data/spec/ruler_spec.rb +44 -0
  5. metadata +27 -13
data/Gemfile CHANGED
@@ -12,3 +12,4 @@ group :development do
12
12
  gem "rcov", ">= 0"
13
13
  gem "mocha"
14
14
  end
15
+ gem 'uuid'
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.2.0
1
+ 1.4.2
@@ -1,3 +1,5 @@
1
+ require 'rubygems'
2
+ require 'uuid'
1
3
  # This module provides a set of methods to manage and run sets of facts and rules.
2
4
  # These rules take an array of fact names and all of the facts are true
3
5
  # the block passed to the rule is executed. By default, only one rule can fire in a given ruleset.
@@ -17,6 +19,12 @@ end
17
19
  class BadNotCall < RulerError
18
20
  end
19
21
 
22
+ class BadFact < RulerError
23
+ end
24
+
25
+ class UnknownFact < RulerError
26
+ end
27
+
20
28
  module Ruler
21
29
  # This module uses thread local storage and should be threadsafe.
22
30
  #
@@ -55,10 +63,17 @@ module Ruler
55
63
  # puts result
56
64
 
57
65
  def ruleset singletary = true,&blk
58
- Thread.current[:singletary] = singletary
59
- Thread.current[:rulematched] = nil
60
- Thread.current[:working_memory] = {}
61
- yield
66
+ @rulesetids ||= []
67
+ @rulesetids << UUID.generate
68
+ puts "Ruleset: #{@rulesetids.last} of #{@rulesetids.length}" if @DEBUG
69
+ Thread.current[@rulesetids.last] = {:singletary => singletary, :rulematched => nil, :working_memory => {}}
70
+ _rval = nil
71
+ begin
72
+ _rval = yield
73
+ ensure
74
+ @rulesetids.pop
75
+ end
76
+ _rval
62
77
  end
63
78
 
64
79
  # multi_ruleset is a helper function to define rulesets that allow
@@ -79,16 +94,21 @@ module Ruler
79
94
  # will never be used.
80
95
  def fact name, dval = nil, &blk
81
96
  if dval.nil?
82
- Thread.current[:working_memory][name] = {:value => yield }
97
+ _res = begin
98
+ yield
99
+ rescue
100
+ raise BadFact.new($!)
101
+ end
102
+ Thread.current[@rulesetids.last][:working_memory][name] = {:value => _res }
83
103
  else
84
- Thread.current[:working_memory][name] = {:value => dval }
104
+ Thread.current[@rulesetids.last][:working_memory][name] = {:value => dval }
85
105
  end
86
106
  end
87
107
 
88
108
  # a dynamic_fact is evaulated every time the fact is checked. Unlike a normal fact, which
89
109
  # is only evaluated once, dynamic facts are evaluated once for each rule they appear in
90
110
  def dynamic_fact name, &blk
91
- Thread.current[:working_memory][name] = {:transient => true, :block => blk }
111
+ Thread.current[@rulesetids.last][:working_memory][name] = {:transient => true, :block => blk }
92
112
  end
93
113
 
94
114
  # allows for a fact to be NOT another fact. notf cannot be used with a dynamic_fact
@@ -96,10 +116,10 @@ module Ruler
96
116
  # fact :one, 10 == 10
97
117
  # fact :notfone, not(:one)
98
118
  def notf name
99
- if Thread.current[:working_memory][name][:transient]
119
+ if Thread.current[@rulesetids.last][:working_memory][name][:transient]
100
120
  raise BadNotCall.new("Cannot call notf on dynamic fact")
101
121
  else
102
- not(Thread.current[:working_memory][name][:value])
122
+ not(Thread.current[@rulesetids.last][:working_memory][name][:value])
103
123
  end
104
124
  end
105
125
 
@@ -116,7 +136,8 @@ module Ruler
116
136
  # there is no check to see if fact names are valid, and facts can be (re)defined
117
137
  #inside of rules. Fact names are false if they are not defined.
118
138
  def rule vlist,docstr = nil,&blk
119
- dbg = lambda {|va| puts Thread.current[:working_memory][va][:transient].nil? ? "|=-\t#{va} = #{Thread.current[:working_memory][va][:value]}" : "|=-\t#{va} = #{Thread.current[:working_memory][va][:block].call()}" }
139
+ conditional_call = lambda {|n| begin Thread.current[@rulesetids.last][:working_memory][n][:transient].nil? ? Thread.current[@rulesetids.last][:working_memory][n][:value] : Thread.current[@rulesetids.last][:working_memory][n][:block].call() rescue false end }
140
+ dbg = lambda {|va| puts begin "|=-\t#{va} = #{conditional_call.call(va)}" rescue "|>=- ERROR: #{$!}" end }
120
141
  if @DEBUG
121
142
  puts "---------------------------------------"
122
143
  puts vlist.join(" & ")
@@ -124,11 +145,10 @@ module Ruler
124
145
  vlist.each {|v| dbg.call(v) }
125
146
  puts "---------------------------------------"
126
147
  end
127
- if Thread.current[:singletary] && Thread.current[:rulematched]
128
- Thread.current[:rulematched]
148
+ if Thread.current[@rulesetids.last][:singletary] && Thread.current[@rulesetids.last][:rulematched]
149
+ Thread.current[@rulesetids.last][:rulematched]
129
150
  else
130
- conditional_call = lambda {|n| Thread.current[:working_memory][n][:transient].nil? ? Thread.current[:working_memory][n][:value] : Thread.current[:working_memory][n][:block].call() }
131
- Thread.current[:rulematched] = if vlist.inject(true) {|k,v| k ? k && conditional_call.call(v) : false }
151
+ Thread.current[@rulesetids.last][:rulematched] = if vlist.inject(true) {|k,v| raise UnknownFact.new("Fact: #{v} is unknown") if Thread.current[@rulesetids.last][:working_memory][v].nil? ;k ? k && conditional_call.call(v) : false }
132
152
  yield
133
153
  end
134
154
  end
@@ -138,11 +158,11 @@ module Ruler
138
158
  # a rule that should fire if no others fire. You cannot have a default rule if you allow more
139
159
  # than one match. The BadDefaultRule exception is raised.
140
160
  def default_rule &blk
141
- raise BadDefaultRule.new("Can't have a default rule when multiple matches are allowed") unless Thread.current[:singletary]
142
- if Thread.current[:singletary] && !Thread.current[:rulematched]
161
+ raise BadDefaultRule.new("Can't have a default rule when multiple matches are allowed") unless Thread.current[@rulesetids.last][:singletary]
162
+ if Thread.current[@rulesetids.last][:singletary] && !Thread.current[@rulesetids.last][:rulematched]
143
163
  yield
144
164
  else
145
- Thread.current[:rulematched]
165
+ Thread.current[@rulesetids.last][:rulematched]
146
166
  end
147
167
  end
148
168
  end
@@ -170,6 +170,35 @@ I think.
170
170
  end
171
171
  end
172
172
  end
173
+
174
+ def test_twelve
175
+ ruleset do
176
+ fact :one do
177
+ raise StandardError.new("This Should be Caught")
178
+ end
179
+ end
180
+ end
181
+
182
+ def test_thirteen
183
+ ruleset do
184
+ fact :one,true
185
+ fact :two, test_one
186
+
187
+ rule [:one,:two] do
188
+ "Nested Works"
189
+ end
190
+ end
191
+ end
192
+
193
+ def test_fourteen
194
+ ruleset do
195
+ fact :one, true
196
+ rule [:noe] do
197
+ "Should Break"
198
+ end
199
+ end
200
+ end
201
+
173
202
  end
174
203
 
175
204
  describe Rules do
@@ -231,5 +260,20 @@ describe Rules do
231
260
  lambda { r.test_eleven }.should raise_error
232
261
  end
233
262
 
263
+ it "should catch an exception thrown in a fact" do
264
+ r = Rules.new
265
+ lambda {r.test_twelve}.should raise_error(BadFact)
266
+ end
267
+
268
+ it "should support nested rulesets" do
269
+ r = Rules.new
270
+ r.test_thirteen.should == "Nested Works"
271
+ end
272
+
273
+ it "should thrown an error if an unknown fact is checked" do
274
+ r = Rules.new
275
+ lambda { r.test_fourteen }.should raise_error(UnknownFact)
276
+ end
277
+
234
278
  end
235
279
 
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruler
3
3
  version: !ruby/object:Gem::Version
4
- hash: 31
4
+ hash: 3
5
5
  prerelease: false
6
6
  segments:
7
7
  - 1
8
+ - 4
8
9
  - 2
9
- - 0
10
- version: 1.2.0
10
+ version: 1.4.2
11
11
  platform: ruby
12
12
  authors:
13
13
  - Joshua Smith
@@ -15,12 +15,26 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-01-31 00:00:00 -05:00
18
+ date: 2011-02-01 00:00:00 -05:00
19
19
  default_executable: example.rb
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
22
22
  prerelease: false
23
23
  version_requirements: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 3
29
+ segments:
30
+ - 0
31
+ version: "0"
32
+ name: uuid
33
+ requirement: *id001
34
+ type: :runtime
35
+ - !ruby/object:Gem::Dependency
36
+ prerelease: false
37
+ version_requirements: &id002 !ruby/object:Gem::Requirement
24
38
  none: false
25
39
  requirements:
26
40
  - - ~>
@@ -32,11 +46,11 @@ dependencies:
32
46
  - 0
33
47
  version: 2.3.0
34
48
  name: rspec
35
- requirement: *id001
49
+ requirement: *id002
36
50
  type: :development
37
51
  - !ruby/object:Gem::Dependency
38
52
  prerelease: false
39
- version_requirements: &id002 !ruby/object:Gem::Requirement
53
+ version_requirements: &id003 !ruby/object:Gem::Requirement
40
54
  none: false
41
55
  requirements:
42
56
  - - ~>
@@ -48,11 +62,11 @@ dependencies:
48
62
  - 0
49
63
  version: 1.0.0
50
64
  name: bundler
51
- requirement: *id002
65
+ requirement: *id003
52
66
  type: :development
53
67
  - !ruby/object:Gem::Dependency
54
68
  prerelease: false
55
- version_requirements: &id003 !ruby/object:Gem::Requirement
69
+ version_requirements: &id004 !ruby/object:Gem::Requirement
56
70
  none: false
57
71
  requirements:
58
72
  - - ~>
@@ -64,11 +78,11 @@ dependencies:
64
78
  - 2
65
79
  version: 1.5.2
66
80
  name: jeweler
67
- requirement: *id003
81
+ requirement: *id004
68
82
  type: :development
69
83
  - !ruby/object:Gem::Dependency
70
84
  prerelease: false
71
- version_requirements: &id004 !ruby/object:Gem::Requirement
85
+ version_requirements: &id005 !ruby/object:Gem::Requirement
72
86
  none: false
73
87
  requirements:
74
88
  - - ">="
@@ -78,11 +92,11 @@ dependencies:
78
92
  - 0
79
93
  version: "0"
80
94
  name: rcov
81
- requirement: *id004
95
+ requirement: *id005
82
96
  type: :development
83
97
  - !ruby/object:Gem::Dependency
84
98
  prerelease: false
85
- version_requirements: &id005 !ruby/object:Gem::Requirement
99
+ version_requirements: &id006 !ruby/object:Gem::Requirement
86
100
  none: false
87
101
  requirements:
88
102
  - - ">="
@@ -92,7 +106,7 @@ dependencies:
92
106
  - 0
93
107
  version: "0"
94
108
  name: mocha
95
- requirement: *id005
109
+ requirement: *id006
96
110
  type: :development
97
111
  description: Ruler module implements a DSL that makes it easy to write a set of facts and rules. If you have some tricky conditional logic, Ruler can help clear it up.
98
112
  email: kognate@gmail.com