simple_state_machine 0.6.0 → 0.6.1
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.
- checksums.yaml +7 -0
- data/.github/workflows/build.yml +46 -0
- data/.gitignore +3 -0
- data/.travis.yml +17 -0
- data/Gemfile +7 -0
- data/README.rdoc +31 -29
- data/examples/conversation.rb +5 -5
- data/examples/lamp.rb +5 -5
- data/examples/relationship.rb +8 -8
- data/examples/traffic_light.rb +3 -3
- data/examples/user.rb +1 -0
- data/gemfiles/Gemfile.activerecord-5.2.x +9 -0
- data/gemfiles/Gemfile.activerecord-6.0.x +9 -0
- data/gemfiles/Gemfile.activerecord-6.1.x +8 -0
- data/gemfiles/Gemfile.activerecord-main.x +9 -0
- data/gemfiles/Gemfile.basic +6 -0
- data/lib/simple_state_machine/decorator/active_record.rb +12 -18
- data/lib/simple_state_machine/tools/graphviz.rb +6 -2
- data/lib/simple_state_machine/version.rb +1 -1
- data/lib/simple_state_machine.rb +5 -4
- data/lib/tasks/graphviz.rake +10 -0
- data/simple_state_machine.gemspec +14 -25
- data/spec/active_record_spec.rb +196 -199
- data/spec/decorator/default_spec.rb +28 -28
- data/spec/examples_spec.rb +13 -13
- data/spec/mountable_spec.rb +18 -16
- data/spec/simple_state_machine_spec.rb +71 -63
- data/spec/spec_helper.rb +16 -9
- data/spec/state_machine_definition_spec.rb +21 -21
- data/spec/state_machine_spec.rb +12 -12
- data/spec/tools/graphviz_spec.rb +3 -2
- data/spec/tools/inspector_spec.rb +3 -3
- metadata +36 -137
- data/autotest/discover.rb +0 -1
data/spec/examples_spec.rb
CHANGED
@@ -4,54 +4,54 @@ describe "Examples" do
|
|
4
4
|
describe "TrafficLight" do
|
5
5
|
it "changes to the next state" do
|
6
6
|
tl = TrafficLight.new
|
7
|
-
tl.
|
7
|
+
expect(tl).to be_green
|
8
8
|
tl.change_state
|
9
|
-
tl.
|
9
|
+
expect(tl).to be_orange
|
10
10
|
tl.change_state
|
11
|
-
tl.
|
11
|
+
expect(tl).to be_red
|
12
12
|
tl.change_state
|
13
|
-
tl.
|
13
|
+
expect(tl).to 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
|
20
|
-
lamp.
|
20
|
+
expect(lamp).to be_off
|
21
21
|
lamp.push_button1
|
22
|
-
lamp.
|
22
|
+
expect(lamp).to be_on
|
23
23
|
lamp.push_button2
|
24
|
-
lamp.
|
24
|
+
expect(lamp).to be_off
|
25
25
|
lamp.push_button2
|
26
|
-
lamp.
|
26
|
+
expect(lamp).to be_on
|
27
27
|
lamp.push_button1
|
28
|
-
lamp.
|
28
|
+
expect(lamp).to 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
|
-
conversation.
|
35
|
+
expect(conversation).to 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
|
41
|
-
conversation.
|
41
|
+
expect(conversation).to be_read
|
42
42
|
end
|
43
43
|
|
44
44
|
it "changes to closed on close" do
|
45
45
|
conversation = Conversation.new
|
46
46
|
conversation.close
|
47
|
-
conversation.
|
47
|
+
expect(conversation).to be_closed
|
48
48
|
end
|
49
49
|
|
50
50
|
it "changes to closed on close if :read" do
|
51
51
|
conversation = Conversation.new
|
52
52
|
conversation.view
|
53
53
|
conversation.close
|
54
|
-
conversation.
|
54
|
+
expect(conversation).to be_closed
|
55
55
|
end
|
56
56
|
|
57
57
|
end
|
data/spec/mountable_spec.rb
CHANGED
@@ -1,34 +1,36 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
describe
|
4
|
-
|
5
|
-
|
6
|
-
event(:event, :state1 => :state2)
|
3
|
+
describe SimpleStateMachine::Mountable do
|
4
|
+
class MountableExample < SimpleStateMachine::StateMachineDefinition
|
5
|
+
event(:event, :state1 => :state2)
|
7
6
|
|
8
|
-
|
9
|
-
|
10
|
-
end
|
7
|
+
def decorator_class
|
8
|
+
SimpleStateMachine::Decorator::Default
|
11
9
|
end
|
12
|
-
|
10
|
+
end
|
11
|
+
|
12
|
+
let(:klass) do
|
13
|
+
Class.new do
|
13
14
|
attr_accessor :event_called
|
14
15
|
extend SimpleStateMachine::Mountable
|
15
|
-
mount_state_machine
|
16
|
+
mount_state_machine MountableExample
|
16
17
|
def event_without_managed_state
|
17
18
|
@event_called = true
|
18
19
|
end
|
19
20
|
end
|
20
|
-
|
21
|
-
|
21
|
+
end
|
22
|
+
subject do
|
23
|
+
klass.new.tap{|i| i.state = 'state1' }
|
22
24
|
end
|
23
25
|
|
24
26
|
it "has state_helper methods" do
|
25
|
-
|
26
|
-
|
27
|
+
expect(subject).to be_state1
|
28
|
+
expect(subject).not_to be_state2
|
27
29
|
end
|
28
30
|
|
29
31
|
it "calls existing methods" do
|
30
|
-
|
31
|
-
|
32
|
-
|
32
|
+
subject.event
|
33
|
+
expect(subject).to be_state2
|
34
|
+
expect(subject.event_called).to eq(true)
|
33
35
|
end
|
34
36
|
end
|
@@ -3,22 +3,21 @@ require 'spec_helper'
|
|
3
3
|
describe SimpleStateMachine do
|
4
4
|
|
5
5
|
it "has an error that extends RuntimeError" do
|
6
|
-
SimpleStateMachine::IllegalStateTransitionError.superclass.
|
6
|
+
expect(SimpleStateMachine::IllegalStateTransitionError.superclass).to eq(RuntimeError)
|
7
7
|
end
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
def initialize(state = 'state1')
|
15
|
-
@state = state
|
16
|
-
end
|
9
|
+
let(:klass) do
|
10
|
+
Class.new do
|
11
|
+
extend SimpleStateMachine
|
12
|
+
def initialize(state = 'state1')
|
13
|
+
@state = state
|
17
14
|
end
|
18
15
|
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe ".event" do
|
19
19
|
|
20
20
|
it "returns what the decorated method returns" do
|
21
|
-
klass = Class.new(@klass)
|
22
21
|
klass.instance_eval do
|
23
22
|
event :event1, :state1 => :state2
|
24
23
|
define_method :event2 do
|
@@ -26,13 +25,12 @@ describe SimpleStateMachine do
|
|
26
25
|
end
|
27
26
|
event :event2, :state2 => :state3
|
28
27
|
end
|
29
|
-
|
30
|
-
|
31
|
-
|
28
|
+
subject = klass.new
|
29
|
+
expect(subject.event1).to eq(nil)
|
30
|
+
expect(subject.event2).to eq('event2')
|
32
31
|
end
|
33
32
|
|
34
33
|
it "calls existing methods" do
|
35
|
-
klass = Class.new(@klass)
|
36
34
|
klass.instance_eval do
|
37
35
|
attr_accessor :event_called
|
38
36
|
define_method :event do
|
@@ -40,112 +38,122 @@ describe SimpleStateMachine do
|
|
40
38
|
end
|
41
39
|
event :event, :state1 => :state2
|
42
40
|
end
|
43
|
-
|
44
|
-
|
45
|
-
|
41
|
+
subject = klass.new
|
42
|
+
subject.event
|
43
|
+
expect(subject.event_called).to eq(true)
|
46
44
|
end
|
47
45
|
|
48
46
|
context "given an event has multiple transitions" do
|
49
|
-
|
50
|
-
klass = Class.new(@klass)
|
47
|
+
before do
|
51
48
|
klass.instance_eval do
|
52
49
|
event :event, :state1 => :state2, :state2 => :state3
|
53
50
|
end
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
51
|
+
end
|
52
|
+
|
53
|
+
it "changes state for all transitions" do
|
54
|
+
subject = klass.new
|
55
|
+
expect(subject).to be_state1
|
56
|
+
subject.event
|
57
|
+
expect(subject).to be_state2
|
58
|
+
subject.event
|
59
|
+
expect(subject).to be_state3
|
60
60
|
end
|
61
61
|
end
|
62
62
|
|
63
63
|
context "given an event has multiple from states" do
|
64
|
-
|
65
|
-
klass = Class.new(@klass)
|
64
|
+
before do
|
66
65
|
klass.instance_eval do
|
67
66
|
event :event, [:state1, :state2] => :state3
|
68
67
|
end
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
68
|
+
end
|
69
|
+
|
70
|
+
it "changes state for all from states" do
|
71
|
+
subject = klass.new
|
72
|
+
subject.event
|
73
|
+
expect(subject).to be_state3
|
74
|
+
subject = klass.new 'state2'
|
75
|
+
expect(subject).to be_state2
|
76
|
+
subject.event
|
77
|
+
expect(subject).to be_state3
|
76
78
|
end
|
77
79
|
end
|
78
80
|
|
79
81
|
context "given an event has :all as from state" do
|
80
|
-
|
81
|
-
klass = Class.new(@klass)
|
82
|
+
before do
|
82
83
|
klass.instance_eval do
|
83
84
|
event :other_event, :state1 => :state2
|
84
85
|
event :event, :all => :state3
|
85
86
|
end
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
87
|
+
end
|
88
|
+
|
89
|
+
it "changes state from all states" do
|
90
|
+
subject = klass.new
|
91
|
+
subject.event
|
92
|
+
expect(subject).to be_state3
|
93
|
+
subject = klass.new 'state2'
|
94
|
+
expect(subject).to be_state2
|
95
|
+
subject.event
|
96
|
+
expect(subject).to be_state3
|
93
97
|
end
|
94
98
|
end
|
95
99
|
|
96
100
|
context "given state is a symbol instead of a string" do
|
97
|
-
|
98
|
-
klass = Class.new(@klass)
|
101
|
+
before do
|
99
102
|
klass.instance_eval do
|
100
103
|
event :event, :state1 => :state2
|
101
104
|
end
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
105
|
+
end
|
106
|
+
|
107
|
+
it "changes state" do
|
108
|
+
subject = klass.new :state1
|
109
|
+
expect(subject.state).to eq(:state1)
|
110
|
+
subject.send(:event)
|
111
|
+
expect(subject).to be_state2
|
106
112
|
end
|
107
113
|
end
|
108
114
|
|
109
115
|
context "given an RuntimeError begin state" do
|
110
116
|
it "changes state to error_state when error can be caught" do
|
111
|
-
class_with_error = Class.new(
|
117
|
+
class_with_error = Class.new(klass)
|
112
118
|
class_with_error.instance_eval do
|
113
119
|
define_method :raise_error do
|
114
120
|
raise RuntimeError.new
|
115
121
|
end
|
116
122
|
event :raise_error, :state1 => :state2, RuntimeError => :failed
|
117
123
|
end
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
124
|
+
subject = class_with_error.new
|
125
|
+
expect(subject).to be_state1
|
126
|
+
subject.raise_error
|
127
|
+
expect(subject).to be_failed
|
122
128
|
end
|
123
129
|
|
124
130
|
it "changes state to error_state when error superclass can be caught" do
|
125
131
|
error_subclass = Class.new(RuntimeError)
|
126
|
-
class_with_error = Class.new(
|
132
|
+
class_with_error = Class.new(klass)
|
127
133
|
class_with_error.instance_eval do
|
128
134
|
define_method :raise_error do
|
129
135
|
raise error_subclass.new
|
130
136
|
end
|
131
137
|
event :raise_error, :state1 => :state2, RuntimeError => :failed
|
132
138
|
end
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
139
|
+
subject = class_with_error.new
|
140
|
+
expect(subject).to be_state1
|
141
|
+
subject.raise_error
|
142
|
+
expect(subject).to be_failed
|
137
143
|
end
|
138
144
|
end
|
139
145
|
|
140
146
|
context "given an invalid state_transition is called" do
|
141
|
-
|
142
|
-
klass = Class.new(@klass)
|
147
|
+
before do
|
143
148
|
klass.instance_eval do
|
144
149
|
event :event, :state1 => :state2
|
145
150
|
event :event2, :state2 => :state3
|
146
151
|
end
|
147
|
-
|
148
|
-
|
152
|
+
end
|
153
|
+
|
154
|
+
it "raises an IllegalStateTransitionError" do
|
155
|
+
subject = klass.new
|
156
|
+
expect { subject.event2 }.to raise_error(
|
149
157
|
SimpleStateMachine::IllegalStateTransitionError,
|
150
158
|
"You cannot 'event2' when state is 'state1'")
|
151
159
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,13 +1,20 @@
|
|
1
1
|
require "rubygems"
|
2
2
|
require "bundler"
|
3
|
-
Bundler.require
|
4
|
-
|
5
|
-
require 'active_record'
|
6
|
-
|
7
|
-
|
3
|
+
Bundler.require :test
|
4
|
+
begin
|
5
|
+
require 'active_record'
|
6
|
+
rescue LoadError
|
7
|
+
puts "Skipping ActiveRecord specs"
|
8
|
+
end
|
9
|
+
|
10
|
+
ROOT = Pathname(File.expand_path(File.join(File.dirname(__FILE__), '..')))
|
11
|
+
$LOAD_PATH << File.join(ROOT, 'lib')
|
12
|
+
$LOAD_PATH << File.join(ROOT, 'spec')
|
13
|
+
$LOAD_PATH << File.join(ROOT, 'examples')
|
14
|
+
|
8
15
|
require 'simple_state_machine'
|
9
|
-
require 'examples
|
10
|
-
require 'examples
|
11
|
-
require 'examples
|
12
|
-
require 'examples
|
16
|
+
require File.join(ROOT, 'examples', 'conversation.rb')
|
17
|
+
require File.join(ROOT, 'examples', 'lamp.rb')
|
18
|
+
require File.join(ROOT, 'examples', 'traffic_light.rb')
|
19
|
+
require File.join(ROOT, 'examples', 'user.rb') if defined? ActiveRecord
|
13
20
|
|
@@ -2,24 +2,24 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe SimpleStateMachine::StateMachineDefinition do
|
4
4
|
|
5
|
-
|
6
|
-
|
5
|
+
let(:klass) do
|
6
|
+
Class.new do
|
7
7
|
extend SimpleStateMachine
|
8
8
|
def initialize(state = 'state1')
|
9
9
|
@state = state
|
10
10
|
end
|
11
11
|
event :event1, :state1 => :state2, :state2 => :state3
|
12
12
|
end
|
13
|
-
@smd = @klass.state_machine_definition
|
14
13
|
end
|
14
|
+
let(:smd) { klass.state_machine_definition }
|
15
15
|
|
16
16
|
it "is inherited by subclasses" do
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
17
|
+
subject = Class.new(klass).new
|
18
|
+
expect(subject).to be_state1
|
19
|
+
subject.event1
|
20
|
+
expect(subject).to be_state2
|
21
|
+
subject.event1
|
22
|
+
expect(subject).to be_state3
|
23
23
|
end
|
24
24
|
|
25
25
|
describe '#state_method' do
|
@@ -39,23 +39,23 @@ describe SimpleStateMachine::StateMachineDefinition do
|
|
39
39
|
end
|
40
40
|
|
41
41
|
it "is used when changing state" do
|
42
|
-
subject.ssm_state.
|
42
|
+
expect(subject.ssm_state).to eq('state1')
|
43
43
|
subject.event1
|
44
|
-
subject.ssm_state.
|
44
|
+
expect(subject.ssm_state).to eq('state2')
|
45
45
|
subject.event2
|
46
|
-
subject.ssm_state.
|
46
|
+
expect(subject.ssm_state).to eq('state3')
|
47
47
|
end
|
48
48
|
|
49
49
|
it "works with state helper methods" do
|
50
|
-
subject.
|
50
|
+
expect(subject).to be_state1
|
51
51
|
subject.event1
|
52
|
-
subject.
|
52
|
+
expect(subject).to be_state2
|
53
53
|
subject.event2
|
54
|
-
subject.
|
54
|
+
expect(subject).to be_state3
|
55
55
|
end
|
56
56
|
|
57
57
|
it "raise an error if an invalid state_transition is called" do
|
58
|
-
|
58
|
+
expect { subject.event2 }.to raise_error(SimpleStateMachine::IllegalStateTransitionError, "You cannot 'event2' when state is 'state1'")
|
59
59
|
end
|
60
60
|
|
61
61
|
end
|
@@ -80,22 +80,22 @@ describe SimpleStateMachine::StateMachineDefinition do
|
|
80
80
|
end
|
81
81
|
|
82
82
|
it "is set when an error occurs during an event" do
|
83
|
-
subject.state.
|
83
|
+
expect(subject.state).to eq('state1')
|
84
84
|
subject.event1
|
85
|
-
subject.state.
|
85
|
+
expect(subject.state).to eq('failed')
|
86
86
|
end
|
87
87
|
end
|
88
88
|
|
89
89
|
describe "#transitions" do
|
90
90
|
it "has a list of transitions" do
|
91
|
-
|
92
|
-
|
91
|
+
expect(smd.transitions).to be_a(Array)
|
92
|
+
expect(smd.transitions.first).to be_a(SimpleStateMachine::Transition)
|
93
93
|
end
|
94
94
|
end
|
95
95
|
|
96
96
|
describe "#to_s" do
|
97
97
|
it "converts to readable string format" do
|
98
|
-
|
98
|
+
expect(smd.to_s).to match(Regexp.new("state1.event1! => state2"))
|
99
99
|
end
|
100
100
|
end
|
101
101
|
|
data/spec/state_machine_spec.rb
CHANGED
@@ -14,11 +14,11 @@ describe SimpleStateMachine::StateMachine do
|
|
14
14
|
end
|
15
15
|
|
16
16
|
it "returns the next state for the event and current state" do
|
17
|
-
subject.next_state('event1').
|
17
|
+
expect(subject.next_state('event1')).to eq('state2')
|
18
18
|
end
|
19
19
|
|
20
20
|
it "returns nil if no next state for the event and current state exists" do
|
21
|
-
subject.next_state('event2').
|
21
|
+
expect(subject.next_state('event2')).to be_nil
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
@@ -37,20 +37,20 @@ describe SimpleStateMachine::StateMachine do
|
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
40
|
+
subject do
|
41
|
+
class_with_error.new.tap{|i| i.raise_error }
|
42
|
+
end
|
43
|
+
|
40
44
|
it "the raised error is accessible" do
|
41
|
-
|
42
|
-
|
43
|
-
raised_error
|
44
|
-
raised_error.should be_a(RuntimeError)
|
45
|
-
raised_error.message.should == "Something went wrong"
|
45
|
+
raised_error = subject.state_machine.raised_error
|
46
|
+
expect(raised_error).to be_a(RuntimeError)
|
47
|
+
expect(raised_error.message).to eq("Something went wrong")
|
46
48
|
end
|
47
49
|
|
48
50
|
it "the raised error is set to nil on the next transition" do
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
example.retry
|
53
|
-
example.state_machine.raised_error.should_not be
|
51
|
+
expect(subject.state_machine.raised_error).to be
|
52
|
+
subject.retry
|
53
|
+
expect(subject.state_machine.raised_error).not_to be
|
54
54
|
end
|
55
55
|
|
56
56
|
end
|
data/spec/tools/graphviz_spec.rb
CHANGED
@@ -15,13 +15,14 @@ describe SimpleStateMachine::Tools::Graphviz do
|
|
15
15
|
|
16
16
|
describe "#to_graphviz_dot" do
|
17
17
|
it "converts to graphviz dot format" do
|
18
|
-
@smd.to_graphviz_dot.
|
18
|
+
expect(@smd.to_graphviz_dot).to eq(%(digraph G {\n"state1"->"state2"[label=event1];\n"state2"->"state3"[label=event1]\n}))
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
22
|
describe "#google_chart_url" do
|
23
23
|
it "shows the state and event dependencies as a Google chart" do
|
24
|
-
@smd.
|
24
|
+
graph = @smd.transitions.map { |t| t.to_graphviz_dot }.sort.join(";")
|
25
|
+
expect(@smd.google_chart_url).to eq("http://chart.googleapis.com/chart?cht=gv&chl=digraph{#{::CGI.escape graph}}")
|
25
26
|
end
|
26
27
|
end
|
27
28
|
end
|
@@ -19,7 +19,7 @@ describe SimpleStateMachine::Tools::Inspector do
|
|
19
19
|
end
|
20
20
|
|
21
21
|
it "returns all 'from' states that aren't 'to' states" do
|
22
|
-
@klass.new.begin_states.
|
22
|
+
expect(@klass.new.begin_states).to eq([:state1, RuntimeError])
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
@@ -40,7 +40,7 @@ describe SimpleStateMachine::Tools::Inspector do
|
|
40
40
|
end
|
41
41
|
|
42
42
|
it "returns all 'to' states that aren't 'from' states" do
|
43
|
-
@klass.new.end_states.
|
43
|
+
expect(@klass.new.end_states).to eq([:state3])
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
@@ -61,7 +61,7 @@ describe SimpleStateMachine::Tools::Inspector do
|
|
61
61
|
end
|
62
62
|
|
63
63
|
it "returns all states" do
|
64
|
-
@klass.new.states.map(&:to_s).sort.
|
64
|
+
expect(@klass.new.states.map(&:to_s).sort).to eq(%w{state1 state2 state3})
|
65
65
|
end
|
66
66
|
end
|
67
67
|
|