statemachine 1.2.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +12 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +12 -0
- data/Rakefile +21 -20
- data/doc/website/README +6 -0
- data/doc/website/config.yaml +2 -0
- data/doc/website/index.html +39 -0
- data/doc/website/src/default.css +110 -0
- data/doc/website/src/default.template +41 -0
- data/doc/website/src/documentation.page +45 -0
- data/doc/website/src/example.page +13 -0
- data/doc/website/src/example1.page +59 -0
- data/doc/website/src/example2.page +96 -0
- data/doc/website/src/example3.page +76 -0
- data/doc/website/src/example4.page +65 -0
- data/doc/website/src/images/8l_star.png +0 -0
- data/doc/website/src/images/bg.png +0 -0
- data/doc/website/src/images/bottom_border.png +0 -0
- data/doc/website/src/images/bottom_edge.png +0 -0
- data/doc/website/src/images/examples/vending_machine.png +0 -0
- data/doc/website/src/images/examples/vending_machine2.png +0 -0
- data/doc/website/src/images/examples/vending_machine3.png +0 -0
- data/doc/website/src/images/examples/vending_machine4a.png +0 -0
- data/doc/website/src/images/examples/vending_machine4b.png +0 -0
- data/doc/website/src/images/left_edge.png +0 -0
- data/doc/website/src/images/logo.png +0 -0
- data/doc/website/src/images/right_edge.png +0 -0
- data/doc/website/src/images/side_borders.png +0 -0
- data/doc/website/src/index.page +20 -0
- data/generate_tests/dot_graph/turnstile.rb +29 -0
- data/generate_tests/dot_graph/turnstile2.rb +48 -0
- data/generate_tests/java/turnstile.rb +54 -0
- data/generate_tests/java/turnstile2.rb +72 -0
- data/lib/statemachine/action_invokation.rb +0 -19
- data/lib/statemachine/version.rb +2 -2
- data/rails_plugin/README +183 -0
- data/rails_plugin/Rakefile +11 -0
- data/rails_plugin/app/controllers/admin_controller.rb +43 -0
- data/rails_plugin/app/controllers/application.rb +7 -0
- data/rails_plugin/app/controllers/main_controller.rb +34 -0
- data/rails_plugin/app/helpers/admin_helper.rb +2 -0
- data/rails_plugin/app/helpers/application_helper.rb +3 -0
- data/rails_plugin/app/helpers/main_helper.rb +7 -0
- data/rails_plugin/app/models/product.rb +21 -0
- data/rails_plugin/app/models/vending_machine.rb +36 -0
- data/rails_plugin/app/models/vending_machine_interface.rb +101 -0
- data/rails_plugin/app/models/vending_statemachine.rb +41 -0
- data/rails_plugin/app/views/admin/index.rhtml +45 -0
- data/rails_plugin/app/views/layouts/main.rhtml +12 -0
- data/rails_plugin/app/views/main/_info.rhtml +13 -0
- data/rails_plugin/app/views/main/event.rjs +27 -0
- data/rails_plugin/app/views/main/index.rhtml +38 -0
- data/rails_plugin/config/boot.rb +45 -0
- data/rails_plugin/config/database.yml +21 -0
- data/rails_plugin/config/environment.rb +60 -0
- data/rails_plugin/config/environments/development.rb +21 -0
- data/rails_plugin/config/environments/production.rb +18 -0
- data/rails_plugin/config/environments/test.rb +19 -0
- data/rails_plugin/config/routes.rb +23 -0
- data/rails_plugin/db/migrate/001_create_vending_machines.rb +12 -0
- data/rails_plugin/db/migrate/002_create_products.rb +14 -0
- data/rails_plugin/db/migrate/003_add_position_to_products.rb +9 -0
- data/rails_plugin/db/schema.rb +20 -0
- data/rails_plugin/doc/README_FOR_APP +2 -0
- data/rails_plugin/lib/tasks/base.rake +5 -0
- data/rails_plugin/public/.htaccess +40 -0
- data/rails_plugin/public/404.html +30 -0
- data/rails_plugin/public/500.html +30 -0
- data/rails_plugin/public/dispatch.cgi +10 -0
- data/rails_plugin/public/dispatch.fcgi +24 -0
- data/rails_plugin/public/dispatch.rb +10 -0
- data/rails_plugin/public/favicon.ico +0 -0
- data/rails_plugin/public/images/bricks.jpg +0 -0
- data/rails_plugin/public/images/cash_release_button.png +0 -0
- data/rails_plugin/public/images/change.png +0 -0
- data/rails_plugin/public/images/dime.png +0 -0
- data/rails_plugin/public/images/dollar.png +0 -0
- data/rails_plugin/public/images/money_panel.png +0 -0
- data/rails_plugin/public/images/nickel.png +0 -0
- data/rails_plugin/public/images/quarter.png +0 -0
- data/rails_plugin/public/images/rails.png +0 -0
- data/rails_plugin/public/images/vending_machine_body.png +0 -0
- data/rails_plugin/public/index.html +277 -0
- data/rails_plugin/public/javascripts/application.js +2 -0
- data/rails_plugin/public/javascripts/controls.js +833 -0
- data/rails_plugin/public/javascripts/dragdrop.js +942 -0
- data/rails_plugin/public/javascripts/effects.js +1088 -0
- data/rails_plugin/public/javascripts/prototype.js +2515 -0
- data/rails_plugin/public/robots.txt +1 -0
- data/rails_plugin/public/stylesheets/layout.css +163 -0
- data/rails_plugin/script/about +3 -0
- data/rails_plugin/script/breakpointer +3 -0
- data/rails_plugin/script/console +3 -0
- data/rails_plugin/script/destroy +3 -0
- data/rails_plugin/script/generate +3 -0
- data/rails_plugin/script/performance/benchmarker +3 -0
- data/rails_plugin/script/performance/profiler +3 -0
- data/rails_plugin/script/plugin +3 -0
- data/rails_plugin/script/process/inspector +3 -0
- data/rails_plugin/script/process/reaper +3 -0
- data/rails_plugin/script/process/spawner +3 -0
- data/rails_plugin/script/rails_spec +11 -0
- data/rails_plugin/script/rails_spec_server +43 -0
- data/rails_plugin/script/runner +3 -0
- data/rails_plugin/script/server +3 -0
- data/rails_plugin/spec/controllers/admin_controller_spec.rb +7 -0
- data/rails_plugin/spec/controllers/main_controller_spec.rb +38 -0
- data/rails_plugin/spec/fixtures/products.yml +2 -0
- data/rails_plugin/spec/fixtures/vending_machines.yml +2 -0
- data/rails_plugin/spec/helpers/admin_helper_spec.rb +5 -0
- data/rails_plugin/spec/helpers/main_helper_spec.rb +5 -0
- data/rails_plugin/spec/models/product_spec.rb +16 -0
- data/rails_plugin/spec/models/vending_machine_interface_spec.rb +168 -0
- data/rails_plugin/spec/models/vending_machine_spec.rb +30 -0
- data/rails_plugin/spec/models/vending_statemachine_spec.rb +130 -0
- data/rails_plugin/spec/plugins/context_support_spec.rb +28 -0
- data/rails_plugin/spec/plugins/controller_support_spec.rb +98 -0
- data/rails_plugin/spec/spec.opts +1 -0
- data/rails_plugin/spec/spec_helper.rb +28 -0
- data/rails_plugin/spec/views/main/event_spec.rb +61 -0
- data/rails_plugin/vendor/plugins/statemachine/README +4 -0
- data/rails_plugin/vendor/plugins/statemachine/Rakefile +22 -0
- data/rails_plugin/vendor/plugins/statemachine/init.rb +2 -0
- data/rails_plugin/vendor/plugins/statemachine/install.rb +1 -0
- data/rails_plugin/vendor/plugins/statemachine/lib/context_support.rb +22 -0
- data/rails_plugin/vendor/plugins/statemachine/lib/controller_support.rb +85 -0
- data/rails_plugin/vendor/plugins/statemachine/lib/statemachine_on_rails.rb +11 -0
- data/rails_plugin/vendor/plugins/statemachine/tasks/statemachine_tasks.rake +4 -0
- data/spec/action_invokation_spec.rb +1 -1
- data/spec/builder_spec.rb +1 -1
- data/spec/default_transition_spec.rb +1 -1
- data/spec/generate/dot_graph/dot_graph_stagemachine_spec.rb +1 -1
- data/spec/history_spec.rb +1 -1
- data/spec/sm_action_parameterization_spec.rb +1 -1
- data/spec/sm_entry_exit_actions_spec.rb +1 -1
- data/spec/sm_odds_n_ends_spec.rb +1 -1
- data/spec/sm_simple_spec.rb +1 -1
- data/spec/sm_super_state_spec.rb +1 -1
- data/spec/sm_turnstile_spec.rb +1 -1
- data/spec/transition_spec.rb +1 -1
- metadata +162 -46
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
data/Rakefile
CHANGED
@@ -5,6 +5,7 @@ require 'rake/clean'
|
|
5
5
|
require 'rake/rdoctask'
|
6
6
|
require 'spec/rake/spectask'
|
7
7
|
require 'statemachine'
|
8
|
+
require "bundler/gem_tasks"
|
8
9
|
|
9
10
|
PKG_NAME = "statemachine"
|
10
11
|
PKG_VERSION = Statemachine::VERSION::STRING
|
@@ -33,26 +34,26 @@ rd = Rake::RDocTask.new do |rdoc|
|
|
33
34
|
end
|
34
35
|
task :rdoc
|
35
36
|
|
36
|
-
spec = Gem::Specification.new do |s|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
end
|
51
|
-
|
52
|
-
Rake::GemPackageTask.new(spec) do |pkg|
|
53
|
-
|
54
|
-
|
55
|
-
end
|
37
|
+
# spec = Gem::Specification.new do |s|
|
38
|
+
# s.name = PKG_NAME
|
39
|
+
# s.version = PKG_VERSION
|
40
|
+
# s.summary = Statemachine::VERSION::DESCRIPTION
|
41
|
+
# s.description = "Statemachine is a ruby library for building Finite State Machines (FSM), also known as Finite State Automata (FSA)."
|
42
|
+
# s.files = PKG_FILES.to_a
|
43
|
+
# s.require_path = 'lib'
|
44
|
+
# s.test_files = Dir.glob('spec/*_spec.rb')
|
45
|
+
# s.require_path = 'lib'
|
46
|
+
# s.autorequire = 'statemachine'
|
47
|
+
# s.author = "Micah Martin"
|
48
|
+
# s.email = "statemachine-devel@rubyforge.org"
|
49
|
+
# s.homepage = "http://statemachine.rubyforge.org"
|
50
|
+
# s.rubyforge_project = "statemachine"
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
# Rake::GemPackageTask.new(spec) do |pkg|
|
54
|
+
# pkg.need_zip = true
|
55
|
+
# pkg.need_tar = true
|
56
|
+
# end
|
56
57
|
|
57
58
|
def egrep(pattern)
|
58
59
|
Dir['**/*.rb'].each do |fn|
|
data/doc/website/README
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
<html>
|
2
|
+
<head>
|
3
|
+
<title>Statemachine</title>
|
4
|
+
<link href="css/sm.css" media="all" rel="Stylesheet" type="text/css" />
|
5
|
+
</head>
|
6
|
+
<body>
|
7
|
+
<div id="frame">
|
8
|
+
<div id="left_edge"> </div>
|
9
|
+
<div id="right_edge"> </div>
|
10
|
+
<div id="canvas">
|
11
|
+
<div id="title_box">
|
12
|
+
<img src="images/logo.png"><br/>
|
13
|
+
<div class="tag_line">A Ruby Library, Gem, and Rails Plugin</div>
|
14
|
+
</div>
|
15
|
+
<div id="menu">
|
16
|
+
|
17
|
+
</div>
|
18
|
+
|
19
|
+
asdf<br>
|
20
|
+
asdf<br>
|
21
|
+
asdf<br>
|
22
|
+
asdf<br>
|
23
|
+
asdf<br>
|
24
|
+
asdf<br>
|
25
|
+
asdf<br>
|
26
|
+
|
27
|
+
</div>
|
28
|
+
<div id="footer">
|
29
|
+
<table border="0" cellpadding="0" cellspacing="0" align="center">
|
30
|
+
<tr>
|
31
|
+
<td align="right"> Written by Micah Martin </td>
|
32
|
+
<td width="20"></td>
|
33
|
+
<td align="left"><a href="8thlight.com"><img src="images/8l_star.png"></a></td>
|
34
|
+
</tr>
|
35
|
+
</table>
|
36
|
+
</div>
|
37
|
+
</div>
|
38
|
+
</body>
|
39
|
+
</html>
|
@@ -0,0 +1,110 @@
|
|
1
|
+
body {
|
2
|
+
background: url(images/bg.png);
|
3
|
+
margin: 0px;
|
4
|
+
padding: 0px;
|
5
|
+
font-family: sans-serif;
|
6
|
+
find
|
7
|
+
}
|
8
|
+
|
9
|
+
#frame {
|
10
|
+
width: 816px;
|
11
|
+
margin-left: auto;
|
12
|
+
margin-right: auto;
|
13
|
+
background: url(images/side_borders.png);
|
14
|
+
}
|
15
|
+
|
16
|
+
#bottom_frame {
|
17
|
+
width: 816px;
|
18
|
+
margin-left: auto;
|
19
|
+
margin-right: auto;
|
20
|
+
height: 10px;
|
21
|
+
background: url(images/bottom_border.png);
|
22
|
+
}
|
23
|
+
|
24
|
+
#canvas {
|
25
|
+
width: 95%;
|
26
|
+
margin-left: auto;
|
27
|
+
margin-right: auto;
|
28
|
+
}
|
29
|
+
|
30
|
+
#footer {
|
31
|
+
padding-left: 20px;
|
32
|
+
padding-right: 20px;
|
33
|
+
border-top: 2px dotted #9E5454;
|
34
|
+
margin-top: -2px;
|
35
|
+
overflow: none;
|
36
|
+
}
|
37
|
+
|
38
|
+
#footer td {
|
39
|
+
font-size: 0.6em;
|
40
|
+
letter-spacing: 5px;
|
41
|
+
}
|
42
|
+
|
43
|
+
#title_box {
|
44
|
+
text-align: center;
|
45
|
+
}
|
46
|
+
|
47
|
+
.tag_line {
|
48
|
+
padding-bottom: 10px;
|
49
|
+
font-style: italic;
|
50
|
+
}
|
51
|
+
|
52
|
+
a, a:link, a:visited {
|
53
|
+
text-decoration: none;
|
54
|
+
color: #9E5454;
|
55
|
+
}
|
56
|
+
|
57
|
+
a:hover, a:active {
|
58
|
+
text-shadow: 0px 0px 4px #777;
|
59
|
+
}
|
60
|
+
|
61
|
+
a img {
|
62
|
+
border: none;
|
63
|
+
margin: 0px;
|
64
|
+
padding: 0px;
|
65
|
+
}
|
66
|
+
|
67
|
+
#menu {
|
68
|
+
width: 100%;
|
69
|
+
padding-top: 5px;
|
70
|
+
padding-bottom: 5px;
|
71
|
+
border-top: 2px dotted #9E5454;
|
72
|
+
border-bottom: 2px dotted #9E5454;
|
73
|
+
text-align: center;
|
74
|
+
}
|
75
|
+
|
76
|
+
#menu ul {
|
77
|
+
margin-left: auto;
|
78
|
+
margin-right: auto;
|
79
|
+
padding: 0px;
|
80
|
+
margin: 0px;
|
81
|
+
list-style-type: none;
|
82
|
+
display: block;
|
83
|
+
}
|
84
|
+
|
85
|
+
#menu ul li {
|
86
|
+
padding-right: 10px;
|
87
|
+
padding-left: 10px;
|
88
|
+
position: relative;
|
89
|
+
display: inline;
|
90
|
+
}
|
91
|
+
|
92
|
+
#menu a, #menu a:link, #menu a:visited {
|
93
|
+
text-decoration: none;
|
94
|
+
color: #777;
|
95
|
+
}
|
96
|
+
|
97
|
+
#menu a:hover, #menu a:active {
|
98
|
+
text-shadow: 0px 0px 4px #9E5454;
|
99
|
+
}
|
100
|
+
|
101
|
+
#body {
|
102
|
+
margin: 10px;
|
103
|
+
}
|
104
|
+
|
105
|
+
pre {
|
106
|
+
border: 1px solid black;
|
107
|
+
background: grey;
|
108
|
+
color: lightgreen;
|
109
|
+
padding: 5px;
|
110
|
+
}
|
@@ -0,0 +1,41 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
3
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="{lang:}">
|
4
|
+
<head>
|
5
|
+
<title>{title: }</title>
|
6
|
+
<link href="{relocatable: default.css}" media="all" rel="Stylesheet" type="text/css" />
|
7
|
+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
8
|
+
</head>
|
9
|
+
<body>
|
10
|
+
<div id="frame">
|
11
|
+
<div id="canvas">
|
12
|
+
<div id="title_box">
|
13
|
+
<img src="images/logo.png"><br/>
|
14
|
+
<div class="tag_line">A Ruby Library, Gem, and Rails Plugin</div>
|
15
|
+
</div>
|
16
|
+
<div id="menu">
|
17
|
+
<ul>
|
18
|
+
<li><a href="index.html">Overview</a></li>
|
19
|
+
<li><a href="documentation.html">Documentation</a></li>
|
20
|
+
<li><a href="example.html">Examples</a></li>
|
21
|
+
<li><a href="rdoc/index.html">RDoc</a></li>
|
22
|
+
<li><a href="http://github.com/slagyr/statemachine/">Project</a></li>
|
23
|
+
</ul>
|
24
|
+
</div>
|
25
|
+
<div id="body">
|
26
|
+
<webgen:block name='content' />
|
27
|
+
</div>
|
28
|
+
<div id="footer">
|
29
|
+
<table border="0" cellpadding="0" cellspacing="0" align="center">
|
30
|
+
<tr>
|
31
|
+
<td align="right"> Written by Micah Martin </td>
|
32
|
+
<td width="20"></td>
|
33
|
+
<td align="left"><a href="8thlight.com"><img src="images/8l_star.png"></a></td>
|
34
|
+
</tr>
|
35
|
+
</table>
|
36
|
+
</div>
|
37
|
+
</div>
|
38
|
+
</div>
|
39
|
+
<div id="bottom_frame"> </div>
|
40
|
+
</body>
|
41
|
+
</html>
|
@@ -0,0 +1,45 @@
|
|
1
|
+
---
|
2
|
+
title: Statemachine Documentation
|
3
|
+
inMenu: true
|
4
|
+
directoryName:
|
5
|
+
---
|
6
|
+
<h2>What is a statemachine?</h2>
|
7
|
+
|
8
|
+
A statemachine keeps track of the status(state) of an application or device and responds to different inputs, which alter the state of the machine.
|
9
|
+
|
10
|
+
<h2>States, Transitions, and Events</h2>
|
11
|
+
|
12
|
+
* State: This is the status of the device or application the statemachine is being used for. At any given time, the statemachine is in one of its predefined states.
|
13
|
+
* Transition: Moving from one state to another is called a transition. Transitions are invoked by Events.
|
14
|
+
* Event: Events are the inputs to a statemachine.
|
15
|
+
|
16
|
+
In the Statemachine project, a statemachine is defined by its transitions.
|
17
|
+
|
18
|
+
Take a look at <a href="example1.html">Example 1</a> to see States, Transitions, and Events being used.
|
19
|
+
|
20
|
+
<h2>Actions</h2>
|
21
|
+
|
22
|
+
Actions allow statemachines to perform operations at various point during execution. There are two models for incorporating actions into statemachines.
|
23
|
+
|
24
|
+
* Mealy: A Mealy machine performs actions on transitions. Each transition in a statemachine may invoke a unique action.
|
25
|
+
* Moore: A Moore machine performs actions when entering a state. Each state may have it’s own entry action.
|
26
|
+
|
27
|
+
Mealy and Moore machines each have advantages and disadvantages. But one great advantage of both it that they are not mutually exclusive. If we use both models, and toss in some exit actions, we’ve got it made!
|
28
|
+
|
29
|
+
Take a look at <a href="example2.html">Example 2</a> to see Actions being used.
|
30
|
+
|
31
|
+
<h2>Conditional Logic</h2>
|
32
|
+
|
33
|
+
If you’re doing any significant amount of work with statemachines, you will most certainly encounter some conditional logic in your statemachines. You may need the state machine to go to one state if a certain condition is true and a different state if it is false. To accomplish this, you can have a state check for the condition and invoke the appropriate transition in an entry action.
|
34
|
+
|
35
|
+
Take a look at <a href="example3.html">Example 3</a> to see Conditional Logic being used.
|
36
|
+
|
37
|
+
<h2>Superstates</h2>
|
38
|
+
|
39
|
+
Oftentimes duplication can arise within a statemachine. One way to solve this problem is through the use of superstates. A <b>superstate</b> is a state that contains other states. One statemachine may have multiple superstates. And every superstate may contain other superstates. ie. Superstates can be nested.
|
40
|
+
|
41
|
+
<h3>History State</h3>
|
42
|
+
|
43
|
+
One problem with superstates is that they may not know which state to return to when coming into that state. To solve this problem, superstates come with the <b>history state</b>. Every superstate will remember which state it is in before the superstate is exited. This memory is stored in a pseudo state called the history state. Transitions that end in the history state will recall the last active state of the superstate and enter it.
|
44
|
+
|
45
|
+
Take a look at <a href="example4.html">Example 4</a> to see Superstates and History States being used.
|
@@ -0,0 +1,13 @@
|
|
1
|
+
---
|
2
|
+
title: Statemachine Examples
|
3
|
+
inMenu: true
|
4
|
+
directoryName:
|
5
|
+
---
|
6
|
+
<h2>Examples</h2>
|
7
|
+
|
8
|
+
Please take a look at the Documentation tab for details explaining the terms used in each of the following examples.
|
9
|
+
|
10
|
+
* <a href="example1.html">Example 1</a>: States, Transitions, and Events
|
11
|
+
* <a href="example2.html">Example 2</a>: Actions
|
12
|
+
* <a href="example3.html">Example 3</a>: Conditional Logic
|
13
|
+
* <a href="example4.html">Example 4</a>: Superstates
|
@@ -0,0 +1,59 @@
|
|
1
|
+
---
|
2
|
+
title: Statemachine Example 1
|
3
|
+
inMenu: true
|
4
|
+
directoryName:
|
5
|
+
---
|
6
|
+
<h2>States, Transitions, and Events</h2>
|
7
|
+
|
8
|
+
<h3>This is a simple statemachine showing use of states and transitions.</h3>
|
9
|
+
|
10
|
+
<img style="border: 1px solid black" src="images/examples/vending_machine.png">
|
11
|
+
<br><b>The Vending Machine Statemachine Diagram</b>
|
12
|
+
|
13
|
+
Above is a UML diagram of the statemachine the runs a simple vending machine. We can see that there are two rectangles with rounded corners. These are States. The vending machine has two possible states, Waiting and Paid. At any given time during execution, the vending machine will be in one of these states.
|
14
|
+
|
15
|
+
Note the arrows going from one state to another. These arrows represent the transitions of the statemachine. Also note that each transition is labeled with an Event. They invoke transitions. For example, when the vending machine is in the Waiting state and the dollar event is received, the statemachine will transition into the Paid state. When in the paid state and the selection event is received, the statemachine will transition back into the Waiting state.
|
16
|
+
|
17
|
+
This should seem reasonable. Imagine a real vending machine. When you walk up to it it’s waiting for you to put money in. You pay by sticking a dollar in and then you make your selection. After this happy transaction, the vending machine waits for the next client.
|
18
|
+
|
19
|
+
This scenario is not the only possibility though. Statemachine are very helpful in examining all possible flows through the system. Take the Waiting state. We don’t normally expect users to make selections if they haven’t paid but it’s a possibility. As you can see this unexpected event is handled by our vending machine. It will simply continue to wait for your dollar. And it would be foolish for someone to put more money in the the vending machine if they’ve already paid. Foolish or not, you and I know it happens. Our vending machine handles this graciously by taking the money and allowing the user to make a selection for the fist dollar they supplied. Effectively the client loses the extra money they put in. (grin)
|
20
|
+
|
21
|
+
Implementing the Statemachine:
|
22
|
+
|
23
|
+
We have identified 3 fundamental components to a statemachine: States, Transitions, and Events. It turns out that the simplest way to define a statemachine is to define its transitions. Each transition can be defined by identifying the state where it begins, the event by which is invoked, and the state where it ends. Using this scheme we can define out vending machine like so…
|
24
|
+
|
25
|
+
<table style="border: 1px solid black; margin: 10px;">
|
26
|
+
<tr><th>Origin State</th><th>Event</th><th>Destination State</th></tr>
|
27
|
+
<tr><td>Waiting</td><td>dollar</td><td>Paid</td></tr>
|
28
|
+
<tr><td>Paid</td><td>selection</td><td>Waiting</td></tr>
|
29
|
+
|
30
|
+
<tr><td>Waiting</td><td>selection</td><td>Waiting</td></tr>
|
31
|
+
<tr><td>Paid</td><td>dollar</td><td>Paid</td></tr>
|
32
|
+
</table>
|
33
|
+
|
34
|
+
Defining it in ruby is not much harder:
|
35
|
+
<pre>require 'rubygems'
|
36
|
+
require 'statemachine'
|
37
|
+
|
38
|
+
vending_machine = Statemachine.build do
|
39
|
+
trans :waiting, :dollar, :paid
|
40
|
+
trans :paid, :selection, :waiting
|
41
|
+
trans :waiting, :selection, :waiting
|
42
|
+
trans :paid, :dollar, :paid
|
43
|
+
end</pre>
|
44
|
+
|
45
|
+
The above snippet assumes you have the statemachine gem installed. (See <a href="overview.html">Overview</a> for installation instructions).
|
46
|
+
|
47
|
+
The outcome of this code an instance of Statemachine stored in the variable named vending_machine. To use our statemachine we need to send events to it. This is done by calling methods that correspond to events.
|
48
|
+
|
49
|
+
<pre>puts vending_machine.state
|
50
|
+
vending_machine.dollar
|
51
|
+
puts vending_machine.state
|
52
|
+
vending_machine.selection
|
53
|
+
puts vending_machine.state</pre>
|
54
|
+
That sequence of events will produce the following ouput:
|
55
|
+
<pre>waiting
|
56
|
+
paid
|
57
|
+
waiting</pre>
|
58
|
+
|
59
|
+
That’s it for the basics. <a href="example2.html">Example 2</a> shows how to make our statemachine more functional by adding actions.
|
@@ -0,0 +1,96 @@
|
|
1
|
+
---
|
2
|
+
title: Statemachine Example 2
|
3
|
+
inMenu: true
|
4
|
+
directoryName:
|
5
|
+
---
|
6
|
+
<h2>Actions</h2>
|
7
|
+
|
8
|
+
<h3>This example shows the addition of actions to our statemachine from <a href="example1.html">Example 1</a>.</h3>
|
9
|
+
|
10
|
+
The vending machine statemachine had some problems. Adding some actions will solve many of them. Here’s the same statemachine with actions.
|
11
|
+
|
12
|
+
<img style="border: 1px solid black" src="images/examples/vending_machine2.png">
|
13
|
+
<br><b>The Vending Machine Statemachine Diagram, Version 2</b>
|
14
|
+
|
15
|
+
You can see I’ve added three transition actions (the Mealy type). Check out the transition from <i>Waiting</i> to <i>Paid</i>. When this transition is triggered the <i>activate</i> action will be called which will activate the hardware that dispenses goodies. Also, when a selection is made, transitioning from <i>Paid</i> to <i>Waiting</i>, the <i>release</i> action will cause the hardware to release the selected product. Finally, this version of the vending machine won’t steal your money any more. When an extra dollar is inserted, the <i>refund</i> event is invoked and the dollar is refunded.
|
16
|
+
|
17
|
+
Notice that the <i>Waiting state</i> has an entry action (Moore type) and an exit action. When ever the <i>Waiting</i> states is entered, <i>the sales_mode action</i> is invoked. The intent of this action is to make the vending machine blink or flash or scroll text; whatever it takes to attract customers. When the <i>Waiting</i> state is exited, the vending will go into operation_mode where all the blinking stops so the customer do business.
|
18
|
+
|
19
|
+
Implementation:
|
20
|
+
|
21
|
+
Here’s how the new vending machine can be implemented in Ruby:
|
22
|
+
|
23
|
+
<pre>vending_machine = Statemachine.build do
|
24
|
+
state :waiting do
|
25
|
+
event :dollar, :paid, :activate
|
26
|
+
event :selection, :waiting
|
27
|
+
on_entry :sales_mode
|
28
|
+
on_exit :operation_mode
|
29
|
+
end
|
30
|
+
trans :paid, :selection, :waiting, :release
|
31
|
+
trans :paid, :dollar, :paid, :refund
|
32
|
+
context VendingMachineContext.new
|
33
|
+
end</pre>
|
34
|
+
|
35
|
+
There are several new tricks to learn here. First is the state method. This is the formal syntax for declaring a state. The informal syntax is the trans method which we’ve already seen. The state method requires the state id and an option block. Every method invoked within the block is applied to the state being declared.
|
36
|
+
|
37
|
+
With a state block you may declare events, entry actions, and exit actions. The event method is used to declare transition out of the current state. Its parameters are the event, destination state, and an optional action. The on_entry and on_exit methods are straight forward. They take one parameter: an action. (See below for more on action syntax)
|
38
|
+
|
39
|
+
After the waiting state declaration we see the familiar calls to trans. The trans method takes an option 4th action parameter. You can see that the release and refund actions were added this way.
|
40
|
+
Context:
|
41
|
+
|
42
|
+
The final line sets the context of the statemachine. This is an interesting aspect. Every statemachine may have a context and if your statemachine has actions, you should definitely give it a context. Every action of a statemachine will be executed within its context object. We’ll discuss this more later.
|
43
|
+
|
44
|
+
Here is a simple context for the vending machine statemachine.
|
45
|
+
|
46
|
+
<pre>class VendingMachineContext
|
47
|
+
|
48
|
+
def activate
|
49
|
+
puts "activating"
|
50
|
+
end
|
51
|
+
|
52
|
+
def release(product)
|
53
|
+
puts "releasing product: #{product}"
|
54
|
+
end
|
55
|
+
|
56
|
+
def refund
|
57
|
+
puts "refuding dollar"
|
58
|
+
end
|
59
|
+
|
60
|
+
def sales_mode
|
61
|
+
puts "going into sales mode"
|
62
|
+
end
|
63
|
+
|
64
|
+
def operation_mode
|
65
|
+
puts "going into operation mode"
|
66
|
+
end
|
67
|
+
|
68
|
+
end</pre>
|
69
|
+
|
70
|
+
<h3>Action Declarations:</h3>
|
71
|
+
|
72
|
+
With the statemachine gem, actions can be declared in any of three forms: Symbol, String, or Block.
|
73
|
+
|
74
|
+
When the action is a <b>Symbol</b>, (on_entry :sales_mode) it is assumes that there is a method by the same name on the context class. This method will be invoked. Any parameters in with the event will be passed along to the invoked method.
|
75
|
+
|
76
|
+
<b>String</b> actions should contains ruby code (on_entry "puts 'entering sales mode'"). The string will use invoked with in the context object using instance_eval. Strings allow quick and dirty actions without the overhead of defining methods on your context class. The disadvantage of String actions is that they cannot accept parameters.
|
77
|
+
|
78
|
+
If the action is a <b>Proc</b> (on_entry Proc.new {puts 'entering sales mode'}), it will be called within the context of the context. Proc actions are also nice for quick and dirty actions. They can accept parameters and are preferred to String actions, unless you want to marshal your statemachine. Using one Proc actions will prevent the entire statemachine from being marhsal-able.
|
79
|
+
|
80
|
+
<h3>Execution</h3>
|
81
|
+
|
82
|
+
For kicks let’s put this statemachine thought a few events.
|
83
|
+
|
84
|
+
<pre>vending_machine.dollar
|
85
|
+
vending_machine.dollar
|
86
|
+
vending_machine.selection "Peanuts"</pre>
|
87
|
+
|
88
|
+
Here’s the output:
|
89
|
+
|
90
|
+
<pre>going into operation mode
|
91
|
+
activating
|
92
|
+
refuding dollar
|
93
|
+
releasing product: Peanuts
|
94
|
+
going into sales mode</pre>
|
95
|
+
|
96
|
+
That sums it up for actions. In <a href="example3.html">Example 3</a>, we’ll talk about how do deal with conditional logic in your statemachine.
|