ruleby 0.1 → 0.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.
@@ -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