volute 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -10,11 +10,11 @@ require 'rake'
10
10
  #
11
11
  # SPEC
12
12
 
13
- task :spec do
14
- sh "spec -cfs spec/"
13
+ task :rspec do
14
+ sh 'time rspec -cfs spec/'
15
15
  end
16
16
 
17
- task :default => :spec
17
+ task :default => :rspec
18
18
 
19
19
 
20
20
  #
@@ -47,7 +47,7 @@ placing some [business] logic outside of classes
47
47
 
48
48
  #gem.add_dependency 'mime-types', '>= 1.16'
49
49
  gem.add_development_dependency 'rake'
50
- gem.add_development_dependency 'rspec'
50
+ gem.add_development_dependency 'rspec', '>= 2.0.0'
51
51
  gem.add_development_dependency 'jeweler'
52
52
 
53
53
  # gemspec spec : http://www.rubygems.org/read/chapter/20
data/TODO.txt CHANGED
@@ -1,24 +1,38 @@
1
1
 
2
2
  [o] volute 'prev' => 'new' { ... }
3
3
  [o] volute Module { ... }
4
- [o] obj.volute_do_set(:a => 0, :b => 1)
4
+ [o] obj.vset(:a => 0, :b => 1)
5
5
  [o] volute { over }
6
+ [o] volute Class { volute :attr { } } --> Class AND attr
7
+ volute Class, :attr { } --> Class OR attr
8
+ [o] volute :not, Light { ... }
9
+ [o] volute /^att/ { ... }
10
+ [o] not including Volute, but triggering Volute.xxx on some changes
11
+ [o] volute :att => :not_nil { ... } (not a nil value)
12
+ [o] volute :att => [ a, b ] { ... } (a or b)
13
+ [o] volute previous_value => [ val0, val1 ] { ... } (OR)
14
+ [o] volute [ pval0, pval1 ] => value { ... } (OR)
15
+ [o] volute :att => /regex/ { ... }
16
+ [o] volute /regex/ => /regex/ { ... }
17
+ [o] rewrite README head
18
+ [o] readme : list alternatives (aspects/hooks/callbacks)
6
19
 
7
- [ ] volute /^att/ { ... }
8
- [ ] volute /regex/ => /regex/ { ... }
9
-
10
- [ ] volute do
11
- volute Invoice do
12
- volute :paid do
13
-
14
- if is(true)
15
- object.comment = 'got paid'
16
- elsif was(nil)
17
- object.comment = 'still not paid'
18
- end
20
+ [ ] volute Invoice do
21
+ volute :paid do
22
+ if is(true)
23
+ object.comment = 'got paid'
24
+ elsif was(nil)
25
+ object.comment = 'still not paid'
19
26
  end
20
27
  end
21
28
  end
22
29
 
23
- [ ] multi-entity state machine example
30
+ [x] multi-entity state machine example
31
+ not happy with the traffic light example
32
+
33
+ [ ] volutes
34
+ `-- volute x, y
35
+ `-- volute a, b
36
+
37
+ [ ] pass the object as block binding ?
24
38
 
@@ -0,0 +1,82 @@
1
+
2
+ # license is MIT
3
+
4
+ # this example is strongly inspired by the diagnosis example
5
+ # for Ruleby ( http://github.com/codeaspects/ruleby )
6
+ # but please remember that Ruleby is a Rete implementation and is thus
7
+ # vastly superior to what is implemented here
8
+
9
+ $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
10
+
11
+ require 'volute'
12
+
13
+
14
+ #
15
+ # some classes
16
+
17
+ class Patient
18
+
19
+ attr_reader :name
20
+
21
+ attr_accessor :fever
22
+ attr_accessor :rash
23
+ attr_accessor :spots
24
+ attr_accessor :sore_throat
25
+ attr_accessor :innoculated
26
+
27
+ attr_accessor :diagnosis
28
+
29
+ def initialize(name)
30
+
31
+ @name = name
32
+ @symptoms = {}
33
+ @innoculated = false
34
+ end
35
+
36
+ def diagnose!
37
+
38
+ Volute.apply(self)
39
+
40
+ @diagnosis
41
+ end
42
+ end
43
+
44
+ #
45
+ # the volutes
46
+
47
+ volute Patient do
48
+
49
+ # These volutes are 'state' volutes, they trigger if all the attributes
50
+ # mentioned as keys yield the given value.
51
+ # unlike classical volutes that act as OR, these states volutes have an
52
+ # AND behaviour.
53
+
54
+ volute :fever => :high, :spots => :true, :innoculated => true do
55
+ object.diagnosis = 'measles'
56
+ over # prevent further evals
57
+ end
58
+ volute :spots => true do
59
+ object.diagnosis = 'allergy (spots but no measles)'
60
+ end
61
+ volute :rash => true do
62
+ object.diagnosis = 'allergy (rash)'
63
+ end
64
+ volute :sore_throat => true, :fever => :mild do
65
+ object.diagnosis = 'flu'
66
+ end
67
+ volute :sore_throat => true, :fever => :high do
68
+ object.diagnosis = 'flu'
69
+ end
70
+ end
71
+
72
+ pat = Patient.new('alice')
73
+ pat.rash = true
74
+
75
+ puts "#{pat.name} : diagnosed with #{pat.diagnose!}"
76
+
77
+ pat = Patient.new('bob')
78
+ pat.sore_throat = true
79
+ pat.fever = :high
80
+
81
+ puts "#{pat.name} : diagnosed with #{pat.diagnose!}"
82
+
@@ -26,7 +26,7 @@ class Equation
26
26
  end
27
27
 
28
28
  #
29
- # a volute triggered for any 'set' operation on an attribute of book
29
+ # a volute per attribute
30
30
 
31
31
  volute Equation do
32
32
 
@@ -0,0 +1,27 @@
1
+
2
+ # license is MIT
3
+
4
+ $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
5
+
6
+ require 'volute' # gem install volute
7
+
8
+ class Light
9
+ include Volute
10
+
11
+ attr_accessor :colour
12
+ attr_accessor :changed_at
13
+ end
14
+
15
+ volute Light do
16
+ volute :colour do
17
+ object.changed_at = Time.now
18
+ end
19
+ end
20
+
21
+ l = Light.new
22
+ p l # => #<Light:0x10014c480>
23
+
24
+ l.colour = :blue
25
+ l.colour = :red
26
+ p l # => #<Light:0x10014c480 @changed_at=Fri Oct 08 20:01:52 +0900 2010, @colour=:blue>
27
+
@@ -9,50 +9,52 @@ $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
9
9
  require 'volute'
10
10
 
11
11
  #
12
- # our class
12
+ # our classes
13
13
 
14
- class Book
15
- include Volute
14
+ module Bookshop
16
15
 
17
- attr_accessor :stock
18
- attr_accessor :discontinued
16
+ class Book
17
+ include Volute
19
18
 
20
- attr_reader :state
19
+ attr_accessor :stock
20
+ attr_accessor :discontinued
21
+ attr_accessor :state
21
22
 
22
- def initialize (stock)
23
- @stock = stock
24
- @discontinued = false
25
- @state = :in_stock
23
+ def initialize (stock)
24
+ @stock = stock
25
+ @discontinued = false
26
+ @state = :in_stock
27
+ end
26
28
  end
27
29
  end
28
30
 
29
31
  #
30
- # a volute triggered for any 'set' operation on an attribute of book
32
+ # volutes
31
33
 
32
- volute Book do
34
+ volute Bookshop do
33
35
 
34
- # object.volute_do_set(:state, x)
35
- # is equivalent to
36
- # object.instance_variable_set(:@state, x)
36
+ volute :stock, :discontinued do
37
37
 
38
- if object.stock <= 0
39
- object.volute_do_set(
40
- :state, object.discontinued ? :discontinued : :out_of_stock)
41
- else
42
- object.volute_do_set(
43
- :state, :in_stock)
38
+ # anything in the module Bookshop that has an attribute :stock
39
+ # or :discontinued
40
+
41
+ if object.stock <= 0
42
+ object.state = object.discontinued ? :discontinued : :out_of_stock
43
+ else
44
+ object.state = :in_stock
45
+ end
44
46
  end
45
47
  end
46
48
 
47
49
  #
48
50
  # trying
49
51
 
50
- emma = Book.new(10)
52
+ emma = Bookshop::Book.new(10)
51
53
 
52
- emma.stock = 2
54
+ emma.stock -= 8
53
55
  p emma.state # => :in_stock
54
56
 
55
- emma.stock = 0
57
+ emma.stock -= 2
56
58
  p emma.state # => :out_of_stock
57
59
 
58
60
  emma.discontinued = true
@@ -0,0 +1,64 @@
1
+
2
+ # license is MIT
3
+ #
4
+ # a state-machine-ish example, inspired by the example at
5
+ # http://github.com/qoobaa/transitions
6
+
7
+ $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
8
+
9
+ require 'volute'
10
+
11
+ #
12
+ # our classes
13
+
14
+ module Bookshop
15
+
16
+ class Book
17
+ include Volute
18
+
19
+ attr_accessor :stock
20
+ attr_accessor :discontinued
21
+ attr_accessor :state
22
+
23
+ def initialize (stock)
24
+ @stock = stock
25
+ @discontinued = false
26
+ @state = :in_stock
27
+ end
28
+ end
29
+ end
30
+
31
+ #
32
+ # volutes
33
+
34
+ volute Bookshop do
35
+
36
+ volute :stock, :discontinued do
37
+
38
+ # anything in the module Bookshop that has an attribute :stock
39
+ # or :discontinued
40
+
41
+ volute :any => [ 0, true ] do
42
+ object.state = object.discontinued ? :discontinued : :out_of_stock
43
+ over
44
+ end
45
+ volute do
46
+ object.state = :in_stock
47
+ end
48
+ end
49
+ end
50
+
51
+ #
52
+ # trying
53
+
54
+ emma = Bookshop::Book.new(10)
55
+
56
+ emma.stock -= 8
57
+ p emma.state # => :in_stock
58
+
59
+ emma.stock -= 2
60
+ p emma.state # => :out_of_stock
61
+
62
+ emma.discontinued = true
63
+ p emma.state # => :discontinued
64
+
@@ -0,0 +1,115 @@
1
+
2
+ # license is MIT
3
+
4
+ $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
5
+
6
+ require 'volute'
7
+
8
+ #
9
+ # our classes
10
+
11
+ class Light
12
+ include Volute
13
+
14
+ attr_accessor :colour
15
+ attr_accessor :changed_at
16
+
17
+ def initialize(colour)
18
+ self.colour = colour
19
+ end
20
+
21
+ def green?
22
+ self.colour == :green
23
+ end
24
+
25
+ def red!
26
+ self.colour = :red
27
+ end
28
+ def green!
29
+ self.colour = :green
30
+ end
31
+
32
+ def seconds
33
+ Time.now - @changed_at
34
+ end
35
+ end
36
+
37
+ class Line
38
+ include Volute
39
+
40
+ attr_accessor :name
41
+ attr_accessor :cars
42
+ attr_accessor :light
43
+ attr_accessor :complement
44
+
45
+ def initialize(name, colour)
46
+ @name = name
47
+ @cars = 0
48
+ @light = Light.new(colour)
49
+ end
50
+
51
+ def one_car
52
+ self.cars = self.cars + 1
53
+ end
54
+
55
+ def log(message)
56
+ puts "#{@name} : #{message} (#{@cars} cars)"
57
+ end
58
+ end
59
+
60
+
61
+ #
62
+ # 'business logic'
63
+
64
+ volute Light do
65
+
66
+ volute :colour do
67
+ object.changed_at = Time.now
68
+ end
69
+ end
70
+
71
+ volute Line do
72
+
73
+ volute :cars do
74
+
75
+ delta = value - object.complement.cars
76
+
77
+ if object.light.green?
78
+ object.vset(:cars, value - 1)
79
+ object.log('cars pass')
80
+ else
81
+ if delta > 4 || object.light.seconds > 5
82
+ object.complement.light.red!
83
+ object.light.green!
84
+ object.log('now green')
85
+ object.vset(:cars, 0)
86
+ else
87
+ object.log('car stops')
88
+ end
89
+ end
90
+ end
91
+ end
92
+
93
+ #
94
+ # initialize system
95
+
96
+ north_south = Line.new('north-south', :green)
97
+ east_west = Line.new('east-west', :red)
98
+
99
+ north_south.complement = east_west
100
+ east_west.complement = north_south
101
+
102
+ #
103
+ # play
104
+
105
+ north_south.one_car
106
+ east_west.one_car
107
+ east_west.one_car
108
+ north_south.one_car
109
+ east_west.one_car
110
+ east_west.one_car
111
+ north_south.one_car
112
+ east_west.one_car
113
+ east_west.one_car
114
+ north_south.one_car
115
+