ruler 1.2.0 → 1.4.2

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.
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