simple_state_machine 0.6.0.pre → 0.6.0
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.
- 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
|
+
|