ruleby 0.1 → 0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,158 @@
1
+ $LOAD_PATH << File.join(File.dirname(__FILE__), '../lib/')
2
+ require 'ruleby'
3
+ require 'rulebook'
4
+
5
+ # NOTE : before we can get this example working we need to figure out exactly
6
+ # what the conflict resolution strategy should be. When we implement the
7
+ # default strategy as we understand it (from various literature) this example
8
+ # does not work (nor should it). But this example works in drools as is. Why?
9
+
10
+ class Customer
11
+ def initialize(name,subscription)
12
+ @name = name
13
+ @subscription = subscription
14
+ end
15
+ attr_reader :name,:subscription
16
+ def to_s
17
+ return '[Customer ' + @name.to_s + ' : ' + @subscription.to_s + ']';
18
+ end
19
+ end
20
+
21
+ class Ticket
22
+ def initialize(customer)
23
+ @customer = customer
24
+ @status = 'New'
25
+ end
26
+ attr :status, true
27
+ attr_reader :customer
28
+ def to_s
29
+ return '[Ticket ' + @customer.to_s + ' : ' + @status.to_s + ']';
30
+ end
31
+ end
32
+
33
+ # This example is used in JBoss-Rules to demonstrate durations and the use of
34
+ # custom DSL. We are simply using here to demonstrate another example.
35
+ class TroubleTicketRulebook < Rulebook
36
+ def rules
37
+ rule 'New Ticket' do |r|
38
+ r.when do |has|
39
+ has.customer Customer
40
+ has.ticket Ticket
41
+ has.ticket.customer = :customer, :%
42
+ has.ticket.status = 'New'
43
+ end
44
+
45
+ r.then = action do |e,vars|
46
+ puts 'New : ' + vars[:ticket].to_s
47
+ end
48
+
49
+ r.priority = 10
50
+ #r.duration = 10
51
+ end
52
+
53
+ rule 'Silver Priority' do |r|
54
+ r.when do |has|
55
+ has.customer Customer
56
+ has.customer.subscription = 'Silver'
57
+
58
+ has.ticket Ticket
59
+ has.ticket.customer = :customer, :%
60
+ has.ticket.status = 'New'
61
+ end
62
+
63
+ r.then = action do |e,vars|
64
+ vars[:ticket].status = 'Escalate'
65
+ e.modify vars[:ticket]
66
+ end
67
+ #r.duration = 3000
68
+ end
69
+
70
+ rule 'Gold Priority' do |r|
71
+ r.when do |has|
72
+ has.customer Customer
73
+ has.customer.subscription = 'Gold'
74
+
75
+ has.ticket Ticket
76
+ has.ticket.customer = :customer, :%
77
+ has.ticket.status = 'New'
78
+ end
79
+
80
+ r.then = action do |e,vars|
81
+ vars[:ticket].status = 'Escalate'
82
+ e.modify vars[:ticket]
83
+ end
84
+ #r.duration = 1000
85
+ end
86
+
87
+ rule 'Platinum Priority' do |r|
88
+ r.when do |has|
89
+ has.customer Customer
90
+ has.customer.subscription = 'Platinum'
91
+
92
+ has.ticket Ticket
93
+ has.ticket.customer = :customer, :%
94
+ has.ticket.status = 'New'
95
+ end
96
+
97
+ r.then = action do |e,vars|
98
+ vars[:ticket].status = 'Escalate'
99
+ e.modify vars[:ticket]
100
+ end
101
+ end
102
+
103
+ rule 'Escalate' do |r|
104
+ r.when do |has|
105
+ has.customer Customer
106
+
107
+ has.ticket Ticket
108
+ has.ticket.customer = :customer, :%
109
+ has.ticket.status = 'Escalate'
110
+ end
111
+
112
+ r.then = action do |e,vars|
113
+ puts 'Email : ' + vars[:ticket].to_s
114
+ end
115
+ end
116
+
117
+ rule 'Done' do |r|
118
+ r.when do |has|
119
+ has.customer Customer
120
+
121
+ has.ticket Ticket
122
+ has.ticket.customer = :customer, :%
123
+ has.ticket.status = 'Done'
124
+ end
125
+
126
+ r.then = action do |e,vars|
127
+ puts 'Done : ' + vars[:ticket].to_s
128
+ end
129
+ end
130
+ end
131
+ end
132
+
133
+ include Ruleby
134
+
135
+ # FACTS
136
+
137
+ a = Customer.new('A', 'Gold')
138
+ b = Customer.new('B', 'Platinum')
139
+ c = Customer.new('C', 'Silver')
140
+ d = Customer.new('D', 'Silver')
141
+
142
+ t1 = Ticket.new(a)
143
+ t2 = Ticket.new(b)
144
+ t3 = Ticket.new(c)
145
+ t4 = Ticket.new(d)
146
+
147
+ engine :engine do |e|
148
+ TroubleTicketRulebook.new(e).rules
149
+ e.assert a
150
+ e.assert b
151
+ e.assert c
152
+ e.assert d
153
+ e.assert t1
154
+ e.assert t2
155
+ e.assert t3
156
+ e.assert t4
157
+ e.match
158
+ end
@@ -0,0 +1,33 @@
1
+ $LOAD_PATH << File.join(File.dirname(__FILE__), '../lib/')
2
+ require 'ruleby'
3
+ require 'fibonacci_rulebook'
4
+ class Fibonacci
5
+ def initialize(sequence,value=-1)
6
+ @sequence = sequence
7
+ @value = value
8
+ end
9
+
10
+ attr_reader :sequence
11
+ attr :value, true
12
+
13
+ def to_s
14
+ return '['+super + " sequence=" + @sequence.to_s + ",value=" + @value.to_s + ']'
15
+ end
16
+ end
17
+
18
+ include Ruleby
19
+
20
+ # This example is borrowed from the JBoss-Rule project.
21
+
22
+ # FACTS
23
+ fib1 = Fibonacci.new(8)
24
+
25
+ t1 = Time.new
26
+ engine :engine do |e|
27
+ FibonacciRulebook1.new(e).rules
28
+ e.assert fib1
29
+ e.match
30
+ end
31
+ t2 = Time.new
32
+ diff = t2.to_f - t1.to_f
33
+ puts diff.to_s
@@ -0,0 +1,29 @@
1
+ $LOAD_PATH << File.join(File.dirname(__FILE__), '../lib/')
2
+ require 'ruleby'
3
+ require 'fibonacci_rulebook'
4
+ class Fibonacci
5
+ def initialize(sequence,value=-1)
6
+ @sequence = sequence
7
+ @value = value
8
+ end
9
+
10
+ attr_reader :sequence
11
+ attr :value, true
12
+
13
+ def to_s
14
+ return super + "::sequence=" + @sequence.to_s + ",value=" + @value.to_s
15
+ end
16
+ end
17
+
18
+ include Ruleby
19
+
20
+ # FACTS
21
+ fib1 = Fibonacci.new(1,1)
22
+ fib2 = Fibonacci.new(2,1)
23
+
24
+ engine :engine do |e|
25
+ FibonacciRulebook2.new(e).rules
26
+ e.assert fib1
27
+ e.assert fib2
28
+ e.match
29
+ end
@@ -0,0 +1,137 @@
1
+
2
+ require 'rulebook'
3
+ class FibonacciRulebook2 < Rulebook
4
+ MAX_SEQUENCE = 100
5
+ def rules
6
+ rule 'Calculate' do |r|
7
+
8
+ r.when do |has|
9
+ has.f1 Fibonacci
10
+ has.f1.value.not = -1
11
+ has.f1.sequence :s1
12
+
13
+ has.f2 Fibonacci
14
+ has.f2.value.not = -1
15
+ has.f2.sequence :s2
16
+ has.f2.sequence :s1 do |s, s1| s == (s1 + 1) end
17
+
18
+ has.f3 Fibonacci
19
+ has.f3.value = -1
20
+ has.f3.sequence :s2 do |s, s2| s == (s2 + 1) end
21
+ end
22
+
23
+ r.then do |e, vars|
24
+ e.retract vars[:f1]
25
+ e.retract vars[:f3]
26
+ if(vars[:f2].sequence == MAX_SEQUENCE)
27
+ e.retract vars[:f2]
28
+ else
29
+ f3 = Fibonacci.new(vars[:f2].sequence + 1, vars[:f1].value + vars[:f2].value)
30
+ e.assert f3
31
+ puts "#{f3.sequence} == #{f3.value}"
32
+ end
33
+ end
34
+
35
+ r.priority = 2
36
+ end
37
+
38
+ rule 'Build' do |r|
39
+
40
+ r.when do |has|
41
+ has.f Fibonacci
42
+ has.f.value.not = -1
43
+ has.f.sequence :s1
44
+
45
+ has.f2 Fibonacci
46
+ has.f2.value.not = -1
47
+ has.f2.sequence :s1 do |s, s1| s == (s1 + 1) end
48
+ end
49
+
50
+ r.then do |e, vars|
51
+ f3 = Fibonacci.new(vars[:f2].sequence + 1, -1)
52
+ e.assert f3
53
+ end
54
+
55
+ r.priority = 1
56
+ end
57
+ end
58
+ end
59
+
60
+ class FibonacciRulebook1 < Rulebook
61
+
62
+ def rules
63
+ # Bootstrap1
64
+ rule 'Bootstrap1' do |r|
65
+ r.when do |has|
66
+ has.f Fibonacci
67
+ has.f.value = -1
68
+ has.f.sequence = 1
69
+ end
70
+
71
+ r.then do |e,vars|
72
+ vars[:f].value = 1
73
+ e.modify vars[:f]
74
+ puts vars[:f].sequence.to_s + ' == ' + vars[:f].value.to_s
75
+ end
76
+
77
+ r.priority = 4
78
+ end
79
+
80
+ # Recurse
81
+ rule 'Recurse' do |r|
82
+ r.when do |has|
83
+ has.f Fibonacci
84
+ has.f.value = -1
85
+ end
86
+
87
+ r.then do |e,vars|
88
+ f2 = Fibonacci.new(vars[:f].sequence - 1)
89
+ e.assert f2
90
+ puts 'recurse for ' + f2.sequence.to_s
91
+ end
92
+ r.priority = 3
93
+ end
94
+
95
+ # Bootstrap2
96
+ rule 'Bootstrap2' do |r|
97
+ r.when do |has|
98
+ has.f9 Fibonacci
99
+ has.f9.value = -1
100
+ has.f9.sequence = 2
101
+ end
102
+ r.then do |e,vars|
103
+ vars[:f9].value = 1
104
+ e.modify vars[:f9]
105
+ puts vars[:f9].sequence.to_s + ' == ' + vars[:f9].value.to_s
106
+ end
107
+ r.priority = 0
108
+ end
109
+
110
+ # Calculate
111
+ rule 'Calculate' do |r|
112
+ r.when do |has|
113
+ has.f1 Fibonacci
114
+ has.f1.value.not = -1
115
+ has.f1.sequence :s1
116
+
117
+ has.f2 Fibonacci
118
+ has.f2.value.not = -1
119
+ has.f2.sequence :s2
120
+ has.f2.sequence.references :s1
121
+ has.f2.sequence do |s2, s1| s2 == (s1 + 1) end
122
+
123
+ has.f3 Fibonacci
124
+ has.f3.value = -1
125
+ has.f3.sequence.references :s2
126
+ has.f3.sequence do |sequence, s2| sequence == (s2 + 1) end
127
+ end
128
+ r.then do |e,vars|
129
+ vars[:f3].value = vars[:f1].value + vars[:f2].value
130
+ e.modify vars[:f3]
131
+ e.retract vars[:f1]
132
+ puts vars[:f3].sequence.to_s + ' == ' + vars[:f3].value.to_s
133
+ end
134
+ r.priority = 0
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,35 @@
1
+ $LOAD_PATH << File.join(File.dirname(__FILE__), '../lib/')
2
+ require 'ruleby'
3
+ require 'rulebook'
4
+
5
+ class Message
6
+ def initialize(status,message)
7
+ @status = status
8
+ @message = message
9
+ end
10
+ attr :status, true
11
+ attr :message, true
12
+ end
13
+
14
+ class SelfRefRulebook < Rulebook
15
+ def rules
16
+ rule 'HelloWorld' do |r|
17
+ r.when do |has|
18
+ has.m2 Message
19
+ has.m2.message :x
20
+ has.m2.status = :x, :%
21
+ end
22
+ r.then = action do |e,vars|
23
+ puts 'Success'
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ include Ruleby
30
+
31
+ engine :engine do |e|
32
+ SelfRefRulebook.new(e).rules
33
+ e.assert Message.new(:HELLO, :HELLO)
34
+ e.match
35
+ end
@@ -11,14 +11,11 @@ module Ruleby
11
11
  @tag = tag
12
12
  @name = name
13
13
  @proc = Proc.new(&block) if block_given?
14
- @parent = nil # TODO move to AtomNode
15
14
  end
16
15
 
17
- attr :parent, true
18
16
  attr_reader :name
19
17
  attr_reader :tag
20
18
  attr_reader :proc
21
-
22
19
  end
23
20
 
24
21
  # This kind of atom is used to match just a single, hard coded value.
@@ -27,28 +24,15 @@ module Ruleby
27
24
  # a.name do |n| n == 'John' end
28
25
  #
29
26
  # So there are no references to other atoms.
30
- class PropertyAtom < Atom
31
-
32
- def match?(fact)
33
- mr = MatchResult.new
34
- temp = fact.object.send("#{@name}")
35
- if @proc.call(temp)
36
- mr.is_match = true
37
- mr.fact_hash[@tag] = fact.id
38
- mr.recency.push fact.recency
39
- mr[@tag] = temp
40
- end
41
- return [mr]
42
- end
43
-
44
- def matches
45
- @parent.obj_results
46
- end
47
-
27
+ class PropertyAtom < Atom
48
28
  def ==(atom)
49
- return atom != nil && @tag == atom.tag && @name == atom.name && @proc == atom.proc
29
+ return shareable?(atom) && @tag == atom.tag
50
30
  end
51
31
 
32
+ def shareable?(atom)
33
+ return atom && @name == atom.name && @proc == atom.proc
34
+ end
35
+
52
36
  def to_s
53
37
  return 'tag='+@tag.to_s + '[' + matches.join(',') + ']'
54
38
  end
@@ -60,17 +44,7 @@ module Ruleby
60
44
  #
61
45
  # It is only used at the start of a pattern.
62
46
  class TypeAtom < PropertyAtom
63
- def match?(fact)
64
- mr = MatchResult.new
65
- temp = fact.object.send("#{@name}")
66
- if @proc.call(temp)
67
- mr.is_match = true
68
- mr.recency.push fact.recency
69
- mr.fact_hash[@tag] = fact.id
70
- mr[@tag] = fact.object
71
- end
72
- return [mr]
73
- end
47
+
74
48
  end
75
49
 
76
50
  # This kind of atom is used for matching a value that is a variable.
@@ -84,46 +58,9 @@ module Ruleby
84
58
  def initialize(tag, name, vars, &block)
85
59
  super(tag, name, &block)
86
60
  @vars = vars # list of referenced variable names
87
- @var_atoms = Hash.new # will be set when rule is asserted
88
61
  end
89
62
 
90
63
  attr_reader :vars
91
- attr :var_atoms, true
92
-
93
- def match?(fact)
94
- local_matches = Array.new
95
- val = fact.object.send("#{@name}")
96
- @vars.each do |v|
97
- # TODO we don't really need to iterate over all the @var_atoms, just
98
- # the one that is at the top of the reference chain (i.e. the one that
99
- # has references to all of this atoms other references.
100
- @var_atoms[v].matches.each do |match|
101
- args = [val]
102
- args.push match.variables[v]
103
- i = 0; while i < @vars.length
104
- if match.key? @vars[i]
105
- args.push match.variables[@vars[i]]
106
- i+=1
107
- else
108
- i = @vars.length + 1
109
- end
110
- end
111
- break if i > @vars.length
112
- if @proc.call(args)
113
- m = MatchResult.new(match.variables.clone, true, match.fact_hash.clone, match.recency)
114
- m.recency.push fact.recency
115
- m.fact_hash[@tag] = fact.id
116
- m.variables[@tag] = val
117
- local_matches.push(m)
118
- end
119
- end
120
- end
121
- return local_matches.uniq
122
- end
123
-
124
- def matches
125
- return @parent.ref_results(@tag)
126
- end
127
64
 
128
65
  def ==(atom)
129
66
  return atom.kind_of?(ReferenceAtom) && @proc == atom.proc && @tag == atom.tag && @vars == atom.vars
@@ -134,6 +71,11 @@ module Ruleby
134
71
  end
135
72
  end
136
73
 
74
+ # This is an atom that references another atom that is in the same pattern.
75
+ class SelfReferenceAtom < ReferenceAtom
76
+
77
+ end
78
+
137
79
  class MatchResult
138
80
 
139
81
  # TODO this class needs to be cleaned up for that we don't have a bunch of
@@ -184,7 +126,7 @@ module Ruleby
184
126
  @is_match = mr.is_match
185
127
  @variables = @variables.update mr.variables
186
128
 
187
- # QUESTION why the heck is does this statement work instead of the
129
+ # QUESTION why the heck does this statement work instead of the
188
130
  # commented one below it??
189
131
  @fact_hash = mr.fact_hash.update @fact_hash
190
132
  #@fact_hash = @fact_hash.update mr.fact_hash