edge-state-machine 0.0.3 → 0.9.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/README.rdoc +128 -4
- data/edge-state-machine.gemspec +2 -2
- data/lib/edge-state-machine/version.rb +1 -1
- data/spec/active_record/active_record_helper.rb +2 -0
- data/spec/active_record/active_record_spec.rb +66 -64
- data/spec/active_record/double_machine_spec.rb +75 -0
- data/spec/active_record/migrations/create_double_machine.rb +8 -0
- data/spec/active_record/samples/double_machine.rb +59 -0
- data/spec/active_record/samples/traffic_light.rb +29 -0
- data/spec/event_spec.rb +6 -6
- data/spec/machine_spec.rb +2 -2
- data/spec/mongoid/double_machine_spec.rb +72 -0
- data/spec/mongoid/mongoid_helper.rb +1 -0
- data/spec/mongoid/mongoid_spec.rb +64 -62
- data/spec/mongoid/samples/double_machine.rb +61 -0
- data/spec/mongoid/samples/traffic_light.rb +31 -0
- data/spec/non_persistent/double_machine_spec.rb +44 -0
- data/spec/non_persistent/non_persistent_helper.rb +2 -0
- data/spec/non_persistent/non_persistent_spec.rb +41 -41
- data/spec/non_persistent/on_off_switch_spec.rb +51 -0
- data/spec/non_persistent/samples/double_machine.rb +48 -0
- data/spec/non_persistent/samples/microwave.rb +1 -0
- data/spec/non_persistent/samples/on_off_switch.rb +62 -0
- data/spec/state_spec.rb +11 -11
- data/spec/transition_spec.rb +25 -25
- metadata +27 -17
data/README.rdoc
CHANGED
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
= Edge State Machine
|
|
2
2
|
|
|
3
|
-
Edge
|
|
4
|
-
It offers support for ActiveRecord and Mongoid
|
|
3
|
+
Edge State Machine is a complete state machine solution.
|
|
4
|
+
It offers support for ActiveRecord and Mongoid for persistence.
|
|
5
5
|
|
|
6
6
|
{<img src="https://secure.travis-ci.org/danpersa/edge-state-machine.png"/>}[http://travis-ci.org/danpersa/edge-state-machine]
|
|
7
7
|
|
|
8
|
+
== Supported Features
|
|
9
|
+
|
|
10
|
+
* Multiple state machines per class each of them acting independently
|
|
11
|
+
* Find errors in state machine definitions as early as possible
|
|
12
|
+
* Transition guards
|
|
13
|
+
* Multiple actions executed on transitions
|
|
14
|
+
* Multiple actions executed on entering and exiting a state
|
|
15
|
+
* No other dependencies for non-persistent state machines
|
|
16
|
+
* Minimal dependencies for persistent ones
|
|
17
|
+
|
|
8
18
|
== Installation
|
|
9
19
|
|
|
10
20
|
If you're using Rails + ActiveRecord + Bundler
|
|
@@ -57,10 +67,124 @@ If you're using Rails + Mongoid + Bundler
|
|
|
57
67
|
end
|
|
58
68
|
end
|
|
59
69
|
|
|
60
|
-
|
|
70
|
+
== State Machine Examples
|
|
71
|
+
|
|
72
|
+
=== Microwave State Machine
|
|
73
|
+
|
|
74
|
+
class Microwave
|
|
75
|
+
state_machine :microwave do # name should be optional, if the name is not present, it should have a default name
|
|
76
|
+
# we give state machines names, so we can pun many machines inside a class
|
|
77
|
+
initial_state :unplugged # initial state should be optional, if the initial state is not present, the initial state will be the first defined state
|
|
78
|
+
|
|
79
|
+
state :unplugged
|
|
80
|
+
|
|
81
|
+
state :plugged
|
|
82
|
+
|
|
83
|
+
state :door_opened do
|
|
84
|
+
enter :light_on # enter should be executed on entering the state
|
|
85
|
+
exit :light_off # exit method should be executed on exiting the state
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
state :door_closed
|
|
89
|
+
|
|
90
|
+
state :started_in_grill_mode do
|
|
91
|
+
enter lambda { |t| p "Entering hate" } # should have support for Procs
|
|
92
|
+
exit :grill_off
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
state :started do
|
|
96
|
+
enter :microwaves_on
|
|
97
|
+
exit :microwaves_off
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
event :plug_in do
|
|
101
|
+
transition :from => :unplugged, :to => :plugged
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
event :open_door do
|
|
105
|
+
transition :from => :plugged, :to => :door_opened
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
event :close_door do
|
|
109
|
+
transition :from => :door_opened, :to => :door_closed,
|
|
110
|
+
:on_transition => :put_food_in_the_microwave # we can put many actions in an Array for the on_transition parameter
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
event :start do
|
|
114
|
+
transition :from => :door_closed, :to => [:started, :started_in_grill_mode],
|
|
115
|
+
:on_transition => :start_spinning_the_food,
|
|
116
|
+
:guard => :grill_button_pressed? # the method grill_button_pressed? should choose the next state
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
event :stop do
|
|
120
|
+
transition :from => [:started, :started_in_grill_mode], :to => :door_closed
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
=== Dice State Machine
|
|
126
|
+
|
|
127
|
+
class Dice
|
|
128
|
+
|
|
129
|
+
state_machine do
|
|
130
|
+
state :one
|
|
131
|
+
state :two
|
|
132
|
+
state :three
|
|
133
|
+
state :four
|
|
134
|
+
state :five
|
|
135
|
+
state :six
|
|
136
|
+
|
|
137
|
+
event :roll do
|
|
138
|
+
transition :from => [:one, :two, :three, :four, :five, :six],
|
|
139
|
+
:to => [:one, :two, :three, :four, :five, :six],
|
|
140
|
+
:guard => :roll_result,
|
|
141
|
+
:on_transition => :display_dice_rolling_animation
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def roll_result
|
|
146
|
+
# return one of the states
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def display_dice_rolling_animation
|
|
150
|
+
# draw the new position of the dice
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
class User
|
|
155
|
+
state_machine do
|
|
156
|
+
state :pending # first one is initial state
|
|
157
|
+
state :active
|
|
158
|
+
state :blocked # the user in this state can't sign in
|
|
159
|
+
|
|
160
|
+
event :activate do
|
|
161
|
+
transition :from => [:pending], :to => :active, :on_transition => :do_activate
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
event :block do
|
|
165
|
+
transition :from => [:pending, :active], :to => :blocked
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
=== Other Examples
|
|
171
|
+
|
|
172
|
+
For other (more complex) examples, please check the following links:
|
|
173
|
+
|
|
174
|
+
* {Examples without Persistence}[https://github.com/danpersa/edge-state-machine/tree/master/spec/non_persistent/samples]
|
|
175
|
+
* {Examples with ActiveRecord}[https://github.com/danpersa/edge-state-machine/tree/master/spec/active_record/samples]
|
|
176
|
+
* {Examples with Mongoid}[https://github.com/danpersa/edge-state-machine/tree/master/spec/mongoid/samples]
|
|
177
|
+
|
|
178
|
+
== Notes
|
|
179
|
+
|
|
180
|
+
For classes with multiple state machines, the state names, machine names must be unique per class.
|
|
181
|
+
|
|
182
|
+
The same thing with the event names.
|
|
183
|
+
|
|
184
|
+
== Credits
|
|
61
185
|
|
|
62
186
|
The gem is based on Rick Olson's code of ActiveModel::StateMachine,
|
|
63
187
|
axed from ActiveModel in {this
|
|
64
188
|
commit}[http://github.com/rails/rails/commit/db49c706b62e7ea2ab93f05399dbfddf5087ee0c].
|
|
65
189
|
|
|
66
|
-
And on Krzysiek Heród's gem, {Transitions}[https://github.com/netizer/transitions], which added Mongoid support.
|
|
190
|
+
And on Krzysiek Heród's gem, {Transitions}[https://github.com/netizer/transitions], which added Mongoid support.
|
data/edge-state-machine.gemspec
CHANGED
|
@@ -8,8 +8,8 @@ Gem::Specification.new do |s|
|
|
|
8
8
|
s.authors = ["Dan Persa"]
|
|
9
9
|
s.email = ["dan.persa@gmail.com"]
|
|
10
10
|
s.homepage = "http://github.com/danpersa/edge-state-machine"
|
|
11
|
-
s.summary = %q{State
|
|
12
|
-
s.description = %q{
|
|
11
|
+
s.summary = %q{Edge State Machine}
|
|
12
|
+
s.description = %q{Edge State Machine is a complete state machine solution. It offers support for ActiveRecord and Mongoid for persistence.}
|
|
13
13
|
|
|
14
14
|
s.rubyforge_project = "edge-state-machine"
|
|
15
15
|
|
|
@@ -3,8 +3,10 @@ require 'active_record'
|
|
|
3
3
|
require 'active_support/core_ext/module/aliasing'
|
|
4
4
|
require 'active_record/migrations/create_orders'
|
|
5
5
|
require 'active_record/migrations/create_traffic_lights'
|
|
6
|
+
require 'active_record/migrations/create_double_machine'
|
|
6
7
|
require 'active_record/samples/traffic_light'
|
|
7
8
|
require 'active_record/samples/order'
|
|
9
|
+
require 'active_record/samples/double_machine'
|
|
8
10
|
|
|
9
11
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", 'lib'))
|
|
10
12
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
|
@@ -1,127 +1,129 @@
|
|
|
1
1
|
require 'active_record/active_record_helper'
|
|
2
2
|
|
|
3
|
-
describe
|
|
3
|
+
describe 'active record state machine' do
|
|
4
4
|
|
|
5
|
-
context
|
|
5
|
+
context 'existing active record' do
|
|
6
6
|
before do
|
|
7
|
-
ActiveRecord::Base.establish_connection(:adapter =>
|
|
7
|
+
ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
|
|
8
8
|
ActiveRecord::Migration.verbose = false
|
|
9
9
|
CreateTrafficLights.migrate(:up)
|
|
10
|
-
@light = TrafficLight.create!
|
|
11
10
|
end
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
@light.current_state.should == :off
|
|
12
|
+
let :light do
|
|
13
|
+
TrafficLight.create!
|
|
16
14
|
end
|
|
17
15
|
|
|
18
|
-
it
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
it 'should have an initial state' do
|
|
17
|
+
light.off?.should == true
|
|
18
|
+
light.current_state.should == :off
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it 'should go to a valid state on transition' do
|
|
22
|
+
light.reset
|
|
23
|
+
light.red?.should == true
|
|
24
|
+
light.current_state.should == :red
|
|
22
25
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
light.green_on
|
|
27
|
+
light.green?.should == true
|
|
28
|
+
light.current_state.should == :green
|
|
26
29
|
end
|
|
27
30
|
|
|
28
|
-
it
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
it 'should not persist state on transition' do
|
|
32
|
+
light.reset
|
|
33
|
+
light.current_state.should == :red
|
|
34
|
+
light.reload
|
|
35
|
+
light.state.should == 'off'
|
|
33
36
|
end
|
|
34
37
|
|
|
35
|
-
it
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
it 'should persists state on transition' do
|
|
39
|
+
light.reset!
|
|
40
|
+
light.current_state.should == :red
|
|
41
|
+
light.reload
|
|
42
|
+
light.state.should == 'red'
|
|
40
43
|
end
|
|
41
44
|
|
|
42
|
-
it
|
|
43
|
-
|
|
44
|
-
loaded_light = TrafficLight.find_by_id(
|
|
45
|
+
it 'should initialize the current state when loaded from database' do
|
|
46
|
+
light.reset!
|
|
47
|
+
loaded_light = TrafficLight.find_by_id(light.id)
|
|
45
48
|
loaded_light.current_state.should == :red
|
|
46
49
|
end
|
|
47
50
|
|
|
48
|
-
it
|
|
49
|
-
expect {
|
|
50
|
-
|
|
51
|
+
it 'should raise error on transition to an invalid state' do
|
|
52
|
+
expect { light.yellow_on }.should raise_error EdgeStateMachine::NoTransitionFound
|
|
53
|
+
light.current_state.should == :off
|
|
51
54
|
end
|
|
52
55
|
|
|
53
|
-
it
|
|
56
|
+
it 'should persist state when state is protected on transition' do
|
|
54
57
|
protected_light = ProtectedTrafficLight.create!
|
|
55
58
|
protected_light.reset!
|
|
56
59
|
protected_light.current_state.should == :red
|
|
57
60
|
protected_light.reload
|
|
58
|
-
protected_light.state.should ==
|
|
61
|
+
protected_light.state.should == 'red'
|
|
59
62
|
end
|
|
60
63
|
|
|
61
|
-
it
|
|
62
|
-
for s in
|
|
63
|
-
|
|
64
|
-
|
|
64
|
+
it 'should not validate when try transition with wrong state' do
|
|
65
|
+
for s in light.class.state_machines[:default].states.keys
|
|
66
|
+
light.state = s
|
|
67
|
+
light.valid?.should == true
|
|
65
68
|
end
|
|
66
|
-
|
|
67
|
-
|
|
69
|
+
light.state = 'invalid_one'
|
|
70
|
+
light.valid?.should_not == true
|
|
68
71
|
end
|
|
69
72
|
|
|
70
|
-
it
|
|
73
|
+
it 'should raise exception when model validation fails on transition' do
|
|
71
74
|
validating_light = ValidatingTrafficLight.create!
|
|
72
75
|
expect {validating_light.reset!}.should raise_error ActiveRecord::RecordInvalid
|
|
73
76
|
validating_light.red?.should == false
|
|
74
77
|
validating_light.off?.should == true
|
|
75
78
|
end
|
|
76
79
|
|
|
77
|
-
it
|
|
80
|
+
it 'should state query method used in a validation condition' do
|
|
78
81
|
validating_light = ConditionalValidatingTrafficLight.create!
|
|
79
82
|
#expect {validating_light.reset!}.should raise_error ActiveRecord::RecordInvalid
|
|
80
83
|
validating_light.off?.should == true
|
|
81
84
|
validating_light.red?.should == false
|
|
82
85
|
end
|
|
83
86
|
|
|
84
|
-
it
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
it 'should reload the model when current state resets' do
|
|
88
|
+
light.reset
|
|
89
|
+
light.red?.should == true
|
|
90
|
+
light.update_attribute(:state, 'green')
|
|
91
|
+
light.reload.green?.should == true # reloaded state should come from database
|
|
89
92
|
end
|
|
90
93
|
|
|
91
|
-
describe
|
|
92
|
-
it
|
|
94
|
+
describe 'scopes' do
|
|
95
|
+
it 'should be added for each state' do
|
|
93
96
|
TrafficLight.should respond_to(:off)
|
|
94
97
|
TrafficLight.should respond_to(:red)
|
|
95
98
|
end
|
|
96
99
|
|
|
97
|
-
it
|
|
98
|
-
|
|
99
|
-
|
|
100
|
+
it 'should not be added for each state' do
|
|
101
|
+
TrafficLightNoScope.should_not respond_to(:off)
|
|
102
|
+
TrafficLightNoScope.should_not respond_to(:red)
|
|
100
103
|
end
|
|
101
104
|
|
|
102
|
-
it
|
|
103
|
-
3.times { TrafficLight.create(:state =>
|
|
104
|
-
3.times { TrafficLight.create(:state =>
|
|
105
|
-
|
|
106
|
-
TrafficLight.off.count.should == 4
|
|
105
|
+
it 'should behave like scopes' do
|
|
106
|
+
3.times { TrafficLight.create(:state => 'off') }
|
|
107
|
+
3.times { TrafficLight.create(:state => 'red') }
|
|
108
|
+
TrafficLight.off.count.should == 3
|
|
107
109
|
TrafficLight.red.count.should == 3
|
|
108
110
|
end
|
|
109
111
|
end
|
|
110
112
|
end
|
|
111
113
|
|
|
112
|
-
context
|
|
113
|
-
|
|
114
|
-
|
|
114
|
+
context 'new active record' do
|
|
115
|
+
let :light do
|
|
116
|
+
TrafficLight.new
|
|
115
117
|
end
|
|
116
118
|
|
|
117
|
-
it
|
|
118
|
-
|
|
119
|
+
it 'should have the initial state set' do
|
|
120
|
+
light.current_state.should == :off
|
|
119
121
|
end
|
|
120
122
|
end
|
|
121
123
|
|
|
122
|
-
context
|
|
124
|
+
context 'timestamp' do
|
|
123
125
|
before do
|
|
124
|
-
ActiveRecord::Base.establish_connection(:adapter =>
|
|
126
|
+
ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
|
|
125
127
|
ActiveRecord::Migration.verbose = false
|
|
126
128
|
CreateOrders.migrate(:up)
|
|
127
129
|
end
|
|
@@ -131,10 +133,10 @@ describe "active record state machine" do
|
|
|
131
133
|
end
|
|
132
134
|
|
|
133
135
|
# control case, no timestamp has been set so we should expect default behaviour
|
|
134
|
-
it
|
|
136
|
+
it 'should not raise any exceptions when moving to placed' do
|
|
135
137
|
@order = create_order
|
|
136
138
|
expect { @order.place! }.should_not raise_error
|
|
137
|
-
@order.state.should ==
|
|
139
|
+
@order.state.should == 'placed'
|
|
138
140
|
end
|
|
139
141
|
end
|
|
140
142
|
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
require 'active_record/active_record_helper'
|
|
2
|
+
|
|
3
|
+
describe DoubleMachineActiveRecord do
|
|
4
|
+
|
|
5
|
+
before do
|
|
6
|
+
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
|
|
7
|
+
ActiveRecord::Migration.verbose = false
|
|
8
|
+
CreateDoubleMachine.migrate(:up)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
let :double_machine do
|
|
12
|
+
DoubleMachineActiveRecord.create!
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it 'should have a current state equals with the initial state for each machine' do
|
|
16
|
+
double_machine.current_state.should == :first_state
|
|
17
|
+
double_machine.current_state(:second).should == :red
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it 'should have the corresponding methods for verifying the states' do
|
|
21
|
+
double_machine.first_state?.should == true
|
|
22
|
+
double_machine.second_state?.should == false
|
|
23
|
+
double_machine.red?.should == true
|
|
24
|
+
double_machine.green?.should == false
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it 'should trigger events from the state machines' do
|
|
28
|
+
double_machine.first_move
|
|
29
|
+
double_machine.current_state.should == :second_state
|
|
30
|
+
double_machine.second_state?.should == true
|
|
31
|
+
|
|
32
|
+
double_machine.go_green
|
|
33
|
+
double_machine.current_state(:second).should == :green
|
|
34
|
+
double_machine.green?.should == true
|
|
35
|
+
|
|
36
|
+
double_machine.second_move
|
|
37
|
+
double_machine.current_state.should == :third_state
|
|
38
|
+
double_machine.third_state?.should == true
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it 'should execute the on_transition method' do
|
|
42
|
+
double_machine.should_receive :do_move
|
|
43
|
+
double_machine.first_move
|
|
44
|
+
double_machine.go_green
|
|
45
|
+
|
|
46
|
+
double_machine.should_receive :turn_off
|
|
47
|
+
double_machine.should_receive :color_in_red
|
|
48
|
+
double_machine.go_red
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
context 'persistence ' do
|
|
52
|
+
it 'should create scopes for each state machine' do
|
|
53
|
+
3.times { DoubleMachineActiveRecord.create(:state => 'second_state', :second_state => 'blue') }
|
|
54
|
+
3.times { DoubleMachineActiveRecord.create(:state => 'first_state', :second_state => 'blue') }
|
|
55
|
+
DoubleMachineActiveRecord.first_state.count.should == 3
|
|
56
|
+
DoubleMachineActiveRecord.second_state.count.should == 3
|
|
57
|
+
DoubleMachineActiveRecord.blue.count.should == 6
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it 'should save the state machines in the database' do
|
|
61
|
+
machine = DoubleMachineActiveRecord.create(:state => 'second_state', :second_state => 'blue')
|
|
62
|
+
machine.go_red!
|
|
63
|
+
machine.current_state(:second).should == :red
|
|
64
|
+
machine.red?.should == true
|
|
65
|
+
|
|
66
|
+
machine.second_move!
|
|
67
|
+
machine.third_state?.should == true
|
|
68
|
+
machine.current_state.should == :third_state
|
|
69
|
+
|
|
70
|
+
loaded_machine = DoubleMachineActiveRecord.find_by_id(machine.id)
|
|
71
|
+
loaded_machine.third_state?.should == true
|
|
72
|
+
loaded_machine.current_state.should == :third_state
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
require 'active_record'
|
|
2
|
+
require 'active_record/edge-state-machine'
|
|
3
|
+
|
|
4
|
+
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
|
|
5
|
+
|
|
6
|
+
class DoubleMachineActiveRecord < ActiveRecord::Base
|
|
7
|
+
include ActiveRecord::EdgeStateMachine
|
|
8
|
+
|
|
9
|
+
state_machine do
|
|
10
|
+
# the machine is automatically named :default
|
|
11
|
+
# the scopes are not created by default
|
|
12
|
+
create_scopes true
|
|
13
|
+
# the persistence instance variable is the default one (:state)
|
|
14
|
+
state :first_state # first one is initial state
|
|
15
|
+
state :second_state
|
|
16
|
+
state :third_state # the user in this state can't sign in
|
|
17
|
+
|
|
18
|
+
event :first_move do
|
|
19
|
+
transition :from => :first_state, :to => :second_state, :on_transition => :do_move
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
event :second_move do
|
|
23
|
+
transition :from => :second_state, :to => :third_state
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
state_machine :second do
|
|
28
|
+
# the scopes are not created by default
|
|
29
|
+
create_scopes true
|
|
30
|
+
# for the second machine we must specify the name of the persistence instance variable
|
|
31
|
+
# so there are no conflicts between the state machines
|
|
32
|
+
persisted_to :second_state
|
|
33
|
+
initial_state :red
|
|
34
|
+
state :blue
|
|
35
|
+
state :green
|
|
36
|
+
state :red
|
|
37
|
+
|
|
38
|
+
event :go_blue do
|
|
39
|
+
transition :from => [:red, :green], :to => :blue
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
event :go_red do
|
|
43
|
+
transition :from => [:blue, :green], :to => :red, :on_transition => [:turn_off, :color_in_red]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
event :go_green do
|
|
47
|
+
transition :from => [:blue, :red], :to => :green
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def do_move
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def turn_off
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def color_in_red
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -43,4 +43,33 @@ end
|
|
|
43
43
|
|
|
44
44
|
class ConditionalValidatingTrafficLight < TrafficLight
|
|
45
45
|
validate :name, :presence => true, :length => { :within => 20..40 }, :confirmation => true, :if => :red?
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
class TrafficLightNoScope < ActiveRecord::Base
|
|
49
|
+
include ActiveRecord::EdgeStateMachine
|
|
50
|
+
|
|
51
|
+
state_machine do
|
|
52
|
+
persisted_to :state
|
|
53
|
+
state :off
|
|
54
|
+
|
|
55
|
+
state :red
|
|
56
|
+
state :green
|
|
57
|
+
state :yellow
|
|
58
|
+
|
|
59
|
+
event :red_on do
|
|
60
|
+
transition :to => :red, :from => [:yellow]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
event :green_on do
|
|
64
|
+
transition :to => :green, :from => [:red]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
event :yellow_on do
|
|
68
|
+
transition :to => :yellow, :from => [:green]
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
event :reset do
|
|
72
|
+
transition :to => :red, :from => [:off]
|
|
73
|
+
end
|
|
74
|
+
end
|
|
46
75
|
end
|
data/spec/event_spec.rb
CHANGED
|
@@ -34,17 +34,17 @@ describe EdgeStateMachine::Event do
|
|
|
34
34
|
end
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
-
it
|
|
37
|
+
it 'should set the name' do
|
|
38
38
|
@state_name.should == @event.name
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
it
|
|
41
|
+
it 'should create Transitions' do
|
|
42
42
|
EdgeStateMachine::Transition.should_receive(:new).with(:to => :closed, :from => [:open, :received])
|
|
43
43
|
new_event
|
|
44
44
|
end
|
|
45
45
|
|
|
46
|
-
describe
|
|
47
|
-
it
|
|
46
|
+
describe 'event arguments' do
|
|
47
|
+
it 'should pass arguments to transition method' do
|
|
48
48
|
subject = ArgumentsTestSubject.new
|
|
49
49
|
subject.current_state.should == :initial
|
|
50
50
|
subject.open!
|
|
@@ -52,13 +52,13 @@ describe EdgeStateMachine::Event do
|
|
|
52
52
|
end
|
|
53
53
|
end
|
|
54
54
|
|
|
55
|
-
describe
|
|
55
|
+
describe 'events being fired' do
|
|
56
56
|
before do
|
|
57
57
|
@machine = mock
|
|
58
58
|
@machine.stub!(:name).and_return(:default)
|
|
59
59
|
end
|
|
60
60
|
|
|
61
|
-
it
|
|
61
|
+
it 'should raise an EdgeStateMachine::NoTransitionFound error if the transitions are empty' do
|
|
62
62
|
event = EdgeStateMachine::Event.new(:event, @machine)
|
|
63
63
|
obj = mock
|
|
64
64
|
obj.stub!(:current_state).and_return(:open)
|
data/spec/machine_spec.rb
CHANGED
|
@@ -31,11 +31,11 @@ describe EdgeStateMachine::Machine do
|
|
|
31
31
|
MachineTestSubject.state_machines.size.should == 2
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
-
it
|
|
34
|
+
it 'should set #initial_state_name from initial_state method' do
|
|
35
35
|
MachineTestSubject.state_machines[:extra].initial_state_name.should == :bar
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
-
it
|
|
38
|
+
it 'should access non-default state machine' do
|
|
39
39
|
MachineTestSubject.state_machines[:extra].class.should == EdgeStateMachine::Machine
|
|
40
40
|
end
|
|
41
41
|
end
|