simple_state_machine 0.6.0.pre → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Changelog.rdoc +16 -0
- data/README.rdoc +79 -51
- data/lib/simple_state_machine.rb +10 -1
- data/lib/simple_state_machine/.DS_Store +0 -0
- data/lib/simple_state_machine/active_record.rb +1 -75
- data/lib/simple_state_machine/decorator/active_record.rb +74 -0
- data/lib/simple_state_machine/decorator/default.rb +91 -0
- data/lib/simple_state_machine/simple_state_machine.rb +0 -342
- data/lib/simple_state_machine/state_machine.rb +88 -0
- data/lib/simple_state_machine/state_machine_definition.rb +72 -0
- data/lib/simple_state_machine/tools/graphviz.rb +17 -0
- data/lib/simple_state_machine/tools/inspector.rb +44 -0
- data/lib/simple_state_machine/transition.rb +40 -0
- data/lib/simple_state_machine/version.rb +1 -1
- data/simple_state_machine.gemspec +1 -1
- data/spec/.DS_Store +0 -0
- data/spec/active_record_spec.rb +7 -14
- data/spec/{decorator_spec.rb → decorator/default_spec.rb} +8 -8
- data/spec/examples_spec.rb +4 -4
- data/spec/mountable_spec.rb +3 -3
- data/spec/simple_state_machine_spec.rb +1 -2
- data/spec/spec_helper.rb +6 -0
- data/spec/state_machine_definition_spec.rb +1 -76
- data/spec/tools/graphviz_spec.rb +29 -0
- data/spec/tools/inspector_spec.rb +70 -0
- metadata +25 -14
@@ -0,0 +1,17 @@
|
|
1
|
+
module SimpleStateMachine
|
2
|
+
module Tools
|
3
|
+
require 'cgi'
|
4
|
+
module Graphviz
|
5
|
+
# Graphviz dot format for rendering as a directional graph
|
6
|
+
def to_graphviz_dot
|
7
|
+
transitions.map { |t| t.to_graphviz_dot }.sort.join(";")
|
8
|
+
end
|
9
|
+
|
10
|
+
# Generates a url that renders states and events as a directional graph.
|
11
|
+
# See http://code.google.com/apis/chart/docs/gallery/graphviz.html
|
12
|
+
def google_chart_url
|
13
|
+
"http://chart.googleapis.com/chart?cht=gv&chl=digraph{#{::CGI.escape to_graphviz_dot}}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module SimpleStateMachine
|
2
|
+
module Tools
|
3
|
+
module Inspector
|
4
|
+
def begin_states
|
5
|
+
from_states - to_states
|
6
|
+
end
|
7
|
+
|
8
|
+
def end_states
|
9
|
+
to_states - from_states
|
10
|
+
end
|
11
|
+
|
12
|
+
def states
|
13
|
+
(to_states + from_states).uniq
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def from_states
|
19
|
+
to_uniq_sym(sample_transitions.map(&:from))
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_states
|
23
|
+
to_uniq_sym(sample_transitions.map(&:to))
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_uniq_sym(array)
|
27
|
+
array.map { |state| state.is_a?(String) ? state.to_sym : state }.uniq
|
28
|
+
end
|
29
|
+
|
30
|
+
def sample_transitions
|
31
|
+
(@subject || sample_subject).state_machine_definition.send :transitions
|
32
|
+
end
|
33
|
+
|
34
|
+
def sample_subject
|
35
|
+
self_class = self.class
|
36
|
+
sample = Class.new do
|
37
|
+
extend SimpleStateMachine::Mountable
|
38
|
+
mount_state_machine self_class
|
39
|
+
end
|
40
|
+
sample
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module SimpleStateMachine
|
2
|
+
# Defines transitions for events
|
3
|
+
class Transition
|
4
|
+
attr_reader :event_name, :from, :to
|
5
|
+
def initialize(event_name, from, to)
|
6
|
+
@event_name = event_name.to_s
|
7
|
+
@from = from.is_a?(Class) ? from : from.to_s
|
8
|
+
@to = to.to_s
|
9
|
+
end
|
10
|
+
|
11
|
+
# returns true if it's a transition for event_name and subject_state
|
12
|
+
def is_transition_for?(event_name, subject_state)
|
13
|
+
is_same_event?(event_name) && is_same_from?(subject_state)
|
14
|
+
end
|
15
|
+
|
16
|
+
# returns true if it's a error transition for event_name and error
|
17
|
+
def is_error_transition_for?(event_name, error)
|
18
|
+
is_same_event?(event_name) && from.is_a?(Class) && error.is_a?(from)
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
"#{from}.#{event_name}! => #{to}"
|
23
|
+
end
|
24
|
+
|
25
|
+
# TODO move to Graphiz module
|
26
|
+
def to_graphviz_dot
|
27
|
+
%("#{from}"->"#{to}"[label=#{event_name}])
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def is_same_event?(event_name)
|
33
|
+
self.event_name == event_name.to_s
|
34
|
+
end
|
35
|
+
|
36
|
+
def is_same_from?(subject_from)
|
37
|
+
from.to_s == 'all' || subject_from.to_s == from.to_s
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -8,7 +8,7 @@ Gem::Specification.new do |s|
|
|
8
8
|
s.platform = Gem::Platform::RUBY
|
9
9
|
|
10
10
|
s.authors = ["Marek de Heus", "Petrik de Heus"]
|
11
|
-
s.description = %q{
|
11
|
+
s.description = %q{A simple DSL to decorate existing methods with state transition guards.}
|
12
12
|
s.email = ["FIX@example.com"]
|
13
13
|
s.homepage = %q{http://github.com/mdh/ssm}
|
14
14
|
s.extra_rdoc_files = [
|
data/spec/.DS_Store
ADDED
Binary file
|
data/spec/active_record_spec.rb
CHANGED
@@ -1,11 +1,4 @@
|
|
1
|
-
require
|
2
|
-
|
3
|
-
require "rubygems"
|
4
|
-
require "bundler"
|
5
|
-
Bundler.require
|
6
|
-
#Bundler.setup(:test)#, :activerecord)
|
7
|
-
require 'active_record'
|
8
|
-
require 'examples/user'
|
1
|
+
require 'spec_helper'
|
9
2
|
|
10
3
|
ActiveRecord::Base.logger = Logger.new "test.log"
|
11
4
|
ActiveRecord::Base.establish_connection(:adapter => "sqlite3",
|
@@ -114,7 +107,7 @@ describe ActiveRecord do
|
|
114
107
|
it "rollsback if an exception is raised" do
|
115
108
|
user_class = Class.new(User)
|
116
109
|
user_class.instance_eval do
|
117
|
-
define_method :
|
110
|
+
define_method :invite_without_managed_state do
|
118
111
|
User.create!(:name => 'name2') #this shouldn't be persisted
|
119
112
|
User.create! #this should raise an error
|
120
113
|
end
|
@@ -182,7 +175,7 @@ describe ActiveRecord do
|
|
182
175
|
it "rollsback if an exception is raised" do
|
183
176
|
user_class = Class.new(User)
|
184
177
|
user_class.instance_eval do
|
185
|
-
define_method :
|
178
|
+
define_method :invite_without_managed_state do
|
186
179
|
User.create!(:name => 'name2') #this shouldn't be persisted
|
187
180
|
User.create! #this should raise an error
|
188
181
|
end
|
@@ -242,16 +235,16 @@ describe ActiveRecord do
|
|
242
235
|
|
243
236
|
it "persists transitions" do
|
244
237
|
ticket = Ticket.create!
|
245
|
-
ticket.should
|
238
|
+
ticket.ssm_state.should == 'open'
|
246
239
|
ticket.close!.should == true
|
247
|
-
ticket.should
|
240
|
+
ticket.ssm_state.should == 'closed'
|
248
241
|
end
|
249
242
|
|
250
243
|
it "persists transitions with !" do
|
251
244
|
ticket = Ticket.create!
|
252
|
-
ticket.should
|
245
|
+
ticket.ssm_state.should == 'open'
|
253
246
|
ticket.close!
|
254
|
-
ticket.should
|
247
|
+
ticket.ssm_state.should == 'closed'
|
255
248
|
end
|
256
249
|
|
257
250
|
end
|
@@ -1,6 +1,6 @@
|
|
1
|
-
require
|
1
|
+
require 'spec_helper'
|
2
2
|
|
3
|
-
describe SimpleStateMachine::Decorator do
|
3
|
+
describe SimpleStateMachine::Decorator::Default do
|
4
4
|
|
5
5
|
context "given a class" do
|
6
6
|
before do
|
@@ -10,7 +10,7 @@ describe SimpleStateMachine::Decorator do
|
|
10
10
|
@state_machine_definition ||= SimpleStateMachine::StateMachineDefinition.new
|
11
11
|
end
|
12
12
|
end
|
13
|
-
decorator = SimpleStateMachine::Decorator.new klass
|
13
|
+
decorator = SimpleStateMachine::Decorator::Default.new klass
|
14
14
|
decorator.decorate SimpleStateMachine::Transition.new(:event, :state1, :state2)
|
15
15
|
@instance = klass.new
|
16
16
|
@instance.state = 'state1'
|
@@ -35,7 +35,7 @@ describe SimpleStateMachine::Decorator do
|
|
35
35
|
@instance.state1?.should == true
|
36
36
|
@instance.state2?.should == false
|
37
37
|
end
|
38
|
-
|
38
|
+
|
39
39
|
it "defines an event method" do
|
40
40
|
@instance.should respond_to(:event)
|
41
41
|
end
|
@@ -55,7 +55,7 @@ describe SimpleStateMachine::Decorator do
|
|
55
55
|
def event() "predefined method" end
|
56
56
|
end
|
57
57
|
transition = SimpleStateMachine::Transition.new(:event, :state1, :state2)
|
58
|
-
decorator = SimpleStateMachine::Decorator.new klass
|
58
|
+
decorator = SimpleStateMachine::Decorator::Default.new klass
|
59
59
|
decorator.decorate transition
|
60
60
|
klass.state_machine_definition.transitions << transition
|
61
61
|
@instance = klass.new
|
@@ -87,7 +87,7 @@ describe SimpleStateMachine::Decorator do
|
|
87
87
|
end
|
88
88
|
end
|
89
89
|
end
|
90
|
-
|
90
|
+
|
91
91
|
context "given a class with predefined protected methods" do
|
92
92
|
before do
|
93
93
|
klass = Class.new do
|
@@ -102,7 +102,7 @@ describe SimpleStateMachine::Decorator do
|
|
102
102
|
def event() "predefined method" end
|
103
103
|
end
|
104
104
|
transition = SimpleStateMachine::Transition.new(:event, :state1, :state2)
|
105
|
-
decorator = SimpleStateMachine::Decorator.new klass
|
105
|
+
decorator = SimpleStateMachine::Decorator::Default.new klass
|
106
106
|
decorator.decorate transition
|
107
107
|
klass.state_machine_definition.transitions << transition
|
108
108
|
@instance = klass.new
|
@@ -154,7 +154,7 @@ describe SimpleStateMachine::Decorator do
|
|
154
154
|
def event() "predefined method" end
|
155
155
|
end
|
156
156
|
transition = SimpleStateMachine::Transition.new(:event, :state1, :state2)
|
157
|
-
decorator = SimpleStateMachine::Decorator.new klass
|
157
|
+
decorator = SimpleStateMachine::Decorator::Default.new klass
|
158
158
|
decorator.decorate transition
|
159
159
|
klass.state_machine_definition.transitions << transition
|
160
160
|
@instance = klass.new
|
data/spec/examples_spec.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require 'spec_helper'
|
2
2
|
|
3
3
|
describe "Examples" do
|
4
4
|
describe "TrafficLight" do
|
@@ -13,7 +13,7 @@ describe "Examples" do
|
|
13
13
|
tl.should be_green
|
14
14
|
end
|
15
15
|
end
|
16
|
-
|
16
|
+
|
17
17
|
describe "Lamp" do
|
18
18
|
it "changes between :on and :off" do
|
19
19
|
lamp = Lamp.new
|
@@ -28,13 +28,13 @@ describe "Examples" do
|
|
28
28
|
lamp.should be_off
|
29
29
|
end
|
30
30
|
end
|
31
|
-
|
31
|
+
|
32
32
|
describe "Conversation" do
|
33
33
|
it "is :unread by default" do
|
34
34
|
conversation = Conversation.new
|
35
35
|
conversation.should be_unread
|
36
36
|
end
|
37
|
-
|
37
|
+
|
38
38
|
it "changes to read on view" do
|
39
39
|
conversation = Conversation.new
|
40
40
|
conversation.view
|
data/spec/mountable_spec.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require 'spec_helper'
|
2
2
|
|
3
3
|
describe "Mountable" do
|
4
4
|
before do
|
@@ -6,14 +6,14 @@ describe "Mountable" do
|
|
6
6
|
event(:event, :state1 => :state2)
|
7
7
|
|
8
8
|
def decorator_class
|
9
|
-
SimpleStateMachine::Decorator
|
9
|
+
SimpleStateMachine::Decorator::Default
|
10
10
|
end
|
11
11
|
end
|
12
12
|
klass = Class.new do
|
13
13
|
attr_accessor :event_called
|
14
14
|
extend SimpleStateMachine::Mountable
|
15
15
|
mount_state_machine mountable_class
|
16
|
-
def
|
16
|
+
def event_without_managed_state
|
17
17
|
@event_called = true
|
18
18
|
end
|
19
19
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,7 +1,13 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "bundler"
|
3
|
+
Bundler.require
|
4
|
+
#Bundler.setup(:test)#, :activerecord)
|
5
|
+
require 'active_record'
|
1
6
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
7
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
3
8
|
require 'simple_state_machine'
|
4
9
|
require 'examples/conversation'
|
5
10
|
require 'examples/lamp'
|
6
11
|
require 'examples/traffic_light'
|
12
|
+
require 'examples/user'
|
7
13
|
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require 'spec_helper'
|
2
2
|
|
3
3
|
describe SimpleStateMachine::StateMachineDefinition do
|
4
4
|
|
@@ -86,70 +86,6 @@ describe SimpleStateMachine::StateMachineDefinition do
|
|
86
86
|
end
|
87
87
|
end
|
88
88
|
|
89
|
-
describe "#begin_states" do
|
90
|
-
before do
|
91
|
-
@klass = Class.new(SimpleStateMachine::StateMachineDefinition) do
|
92
|
-
def add_events
|
93
|
-
define_event(:event_a, :state1 => :state2)
|
94
|
-
define_event(:event_b, :state2 => :state3)
|
95
|
-
define_event(:event_c, :state1 => :state3)
|
96
|
-
define_event(:event_c, RuntimeError => :state3)
|
97
|
-
end
|
98
|
-
|
99
|
-
def decorator_class
|
100
|
-
SimpleStateMachine::Decorator
|
101
|
-
end
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
it "returns all 'from' states that aren't 'to' states" do
|
106
|
-
@klass.new.begin_states.should == [:state1, RuntimeError]
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
describe "#end_states" do
|
111
|
-
before do
|
112
|
-
@klass = Class.new(SimpleStateMachine::StateMachineDefinition) do
|
113
|
-
def add_events
|
114
|
-
define_event(:event_a, :state1 => :state2)
|
115
|
-
define_event(:event_b, :state2 => :state3)
|
116
|
-
define_event(:event_c, :state1 => :state3)
|
117
|
-
end
|
118
|
-
|
119
|
-
def decorator_class
|
120
|
-
SimpleStateMachine::Decorator
|
121
|
-
end
|
122
|
-
|
123
|
-
end
|
124
|
-
end
|
125
|
-
|
126
|
-
it "returns all 'to' states that aren't 'from' states" do
|
127
|
-
@klass.new.end_states.should == [:state3]
|
128
|
-
end
|
129
|
-
end
|
130
|
-
|
131
|
-
describe "#states" do
|
132
|
-
before do
|
133
|
-
@klass = Class.new(SimpleStateMachine::StateMachineDefinition) do
|
134
|
-
def add_events
|
135
|
-
define_event(:event_a, :state1 => :state2)
|
136
|
-
define_event(:event_b, :state2 => :state3)
|
137
|
-
define_event(:event_c, :state1 => :state3)
|
138
|
-
end
|
139
|
-
|
140
|
-
def decorator_class
|
141
|
-
SimpleStateMachine::Decorator
|
142
|
-
end
|
143
|
-
|
144
|
-
end
|
145
|
-
end
|
146
|
-
|
147
|
-
it "returns all states" do
|
148
|
-
@klass.new.states.map(&:to_s).sort.should == %w{state1 state2 state3}
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
|
-
|
153
89
|
describe "#transitions" do
|
154
90
|
it "has a list of transitions" do
|
155
91
|
@smd.transitions.should be_a(Array)
|
@@ -163,16 +99,5 @@ describe SimpleStateMachine::StateMachineDefinition do
|
|
163
99
|
end
|
164
100
|
end
|
165
101
|
|
166
|
-
describe "#to_graphviz_dot" do
|
167
|
-
it "converts to graphviz dot format" do
|
168
|
-
@smd.to_graphviz_dot.should == %("state1"->"state2"[label=event1];"state2"->"state3"[label=event1])
|
169
|
-
end
|
170
|
-
end
|
171
|
-
|
172
|
-
describe "#google_chart_url" do
|
173
|
-
it "shows the state and event dependencies as a Google chart" do
|
174
|
-
@smd.google_chart_url.should == "http://chart.googleapis.com/chart?cht=gv&chl=digraph{#{::CGI.escape @smd.to_graphviz_dot}}"
|
175
|
-
end
|
176
|
-
end
|
177
102
|
end
|
178
103
|
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe SimpleStateMachine::Tools::Graphviz do
|
4
|
+
|
5
|
+
before do
|
6
|
+
@klass = Class.new do
|
7
|
+
extend SimpleStateMachine
|
8
|
+
def initialize(state = 'state1')
|
9
|
+
@state = state
|
10
|
+
end
|
11
|
+
event :event1, :state1 => :state2, :state2 => :state3
|
12
|
+
end
|
13
|
+
@smd = @klass.state_machine_definition
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "#to_graphviz_dot" do
|
17
|
+
it "converts to graphviz dot format" do
|
18
|
+
@smd.to_graphviz_dot.should == %("state1"->"state2"[label=event1];"state2"->"state3"[label=event1])
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "#google_chart_url" do
|
23
|
+
it "shows the state and event dependencies as a Google chart" do
|
24
|
+
@smd.google_chart_url.should == "http://chart.googleapis.com/chart?cht=gv&chl=digraph{#{::CGI.escape @smd.to_graphviz_dot}}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe SimpleStateMachine::Tools::Inspector do
|
4
|
+
|
5
|
+
describe "#begin_states" do
|
6
|
+
before do
|
7
|
+
@klass = Class.new(SimpleStateMachine::StateMachineDefinition) do
|
8
|
+
def add_events
|
9
|
+
define_event(:event_a, :state1 => :state2)
|
10
|
+
define_event(:event_b, :state2 => :state3)
|
11
|
+
define_event(:event_c, :state1 => :state3)
|
12
|
+
define_event(:event_c, RuntimeError => :state3)
|
13
|
+
end
|
14
|
+
|
15
|
+
def decorator_class
|
16
|
+
SimpleStateMachine::Decorator::Default
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
it "returns all 'from' states that aren't 'to' states" do
|
22
|
+
@klass.new.begin_states.should == [:state1, RuntimeError]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "#end_states" do
|
27
|
+
before do
|
28
|
+
@klass = Class.new(SimpleStateMachine::StateMachineDefinition) do
|
29
|
+
def add_events
|
30
|
+
define_event(:event_a, :state1 => :state2)
|
31
|
+
define_event(:event_b, :state2 => :state3)
|
32
|
+
define_event(:event_c, :state1 => :state3)
|
33
|
+
end
|
34
|
+
|
35
|
+
def decorator_class
|
36
|
+
SimpleStateMachine::Decorator::Default
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
it "returns all 'to' states that aren't 'from' states" do
|
43
|
+
@klass.new.end_states.should == [:state3]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "#states" do
|
48
|
+
before do
|
49
|
+
@klass = Class.new(SimpleStateMachine::StateMachineDefinition) do
|
50
|
+
def add_events
|
51
|
+
define_event(:event_a, :state1 => :state2)
|
52
|
+
define_event(:event_b, :state2 => :state3)
|
53
|
+
define_event(:event_c, :state1 => :state3)
|
54
|
+
end
|
55
|
+
|
56
|
+
def decorator_class
|
57
|
+
SimpleStateMachine::Decorator::Default
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
it "returns all states" do
|
64
|
+
@klass.new.states.map(&:to_s).sort.should == %w{state1 state2 state3}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
|