simple_state_machine 0.5.2 → 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.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/build.yml +46 -0
  3. data/.gitignore +3 -0
  4. data/.travis.yml +17 -0
  5. data/Changelog.rdoc +16 -0
  6. data/Gemfile +7 -0
  7. data/README.rdoc +154 -99
  8. data/examples/conversation.rb +5 -5
  9. data/examples/lamp.rb +5 -5
  10. data/examples/relationship.rb +8 -8
  11. data/examples/traffic_light.rb +3 -3
  12. data/examples/user.rb +8 -4
  13. data/gemfiles/Gemfile.activerecord-5.2.x +9 -0
  14. data/gemfiles/Gemfile.activerecord-6.0.x +9 -0
  15. data/gemfiles/Gemfile.activerecord-6.1.x +8 -0
  16. data/gemfiles/Gemfile.activerecord-main.x +9 -0
  17. data/gemfiles/Gemfile.basic +6 -0
  18. data/lib/simple_state_machine/.DS_Store +0 -0
  19. data/lib/simple_state_machine/active_record.rb +2 -64
  20. data/lib/simple_state_machine/decorator/active_record.rb +68 -0
  21. data/lib/simple_state_machine/decorator/default.rb +91 -0
  22. data/lib/simple_state_machine/railtie.rb +1 -1
  23. data/lib/simple_state_machine/simple_state_machine.rb +8 -251
  24. data/lib/simple_state_machine/state_machine.rb +88 -0
  25. data/lib/simple_state_machine/state_machine_definition.rb +72 -0
  26. data/lib/simple_state_machine/tools/graphviz.rb +21 -0
  27. data/lib/simple_state_machine/tools/inspector.rb +44 -0
  28. data/lib/simple_state_machine/transition.rb +40 -0
  29. data/lib/simple_state_machine/version.rb +1 -1
  30. data/lib/simple_state_machine.rb +13 -3
  31. data/lib/tasks/graphviz.rake +31 -0
  32. data/simple_state_machine.gemspec +14 -24
  33. data/spec/.DS_Store +0 -0
  34. data/spec/active_record_spec.rb +216 -179
  35. data/spec/{decorator_spec.rb → decorator/default_spec.rb} +32 -32
  36. data/spec/examples_spec.rb +17 -17
  37. data/spec/mountable_spec.rb +26 -14
  38. data/spec/simple_state_machine_spec.rb +128 -92
  39. data/spec/spec_helper.rb +18 -5
  40. data/spec/state_machine_definition_spec.rb +48 -34
  41. data/spec/state_machine_spec.rb +36 -2
  42. data/spec/tools/graphviz_spec.rb +30 -0
  43. data/spec/tools/inspector_spec.rb +70 -0
  44. metadata +54 -128
  45. data/autotest/discover.rb +0 -1
  46. data/lib/tasks/graphiz.rake +0 -13
  47. data/rails/graphiz.rake +0 -16
@@ -1,6 +1,6 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
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 = described_class.new klass
14
14
  decorator.decorate SimpleStateMachine::Transition.new(:event, :state1, :state2)
15
15
  @instance = klass.new
16
16
  @instance.state = 'state1'
@@ -18,26 +18,26 @@ describe SimpleStateMachine::Decorator do
18
18
 
19
19
  describe "#initialize" do
20
20
  it "defines a state_machine method" do
21
- @instance.state_machine.should be_an(SimpleStateMachine::StateMachine)
21
+ expect(@instance.state_machine).to be_an(SimpleStateMachine::StateMachine)
22
22
  end
23
23
 
24
24
  it "defines a state getter method" do
25
- @instance.should respond_to(:state)
25
+ expect(@instance).to respond_to(:state)
26
26
  end
27
27
 
28
28
  it "defines a state setter method" do
29
- @instance.should respond_to(:state=)
29
+ expect(@instance).to respond_to(:state=)
30
30
  end
31
31
  end
32
32
 
33
33
  describe "#decorate" do
34
34
  it "defines state_helper_methods for both states" do
35
- @instance.state1?.should == true
36
- @instance.state2?.should == false
35
+ expect(@instance.state1?).to eq(true)
36
+ expect(@instance.state2?).to eq(false)
37
37
  end
38
-
38
+
39
39
  it "defines an event method" do
40
- @instance.should respond_to(:event)
40
+ expect(@instance).to respond_to(:event)
41
41
  end
42
42
  end
43
43
  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 = described_class.new klass
59
59
  decorator.decorate transition
60
60
  klass.state_machine_definition.transitions << transition
61
61
  @instance = klass.new
@@ -64,30 +64,30 @@ describe SimpleStateMachine::Decorator do
64
64
 
65
65
  describe "#initialize" do
66
66
  it "defines a state_machine method" do
67
- @instance.state_machine.should be_an(SimpleStateMachine::StateMachine)
67
+ expect(@instance.state_machine).to be_an(SimpleStateMachine::StateMachine)
68
68
  end
69
69
 
70
70
  it "defines a state getter method" do
71
- @instance.should respond_to(:state)
71
+ expect(@instance).to respond_to(:state)
72
72
  end
73
73
 
74
74
  it "defines a state setter method" do
75
- @instance.should respond_to(:state=)
75
+ expect(@instance).to respond_to(:state=)
76
76
  end
77
77
  end
78
78
 
79
79
  describe "#decorate" do
80
80
  it "does not overwrite predefined state_helper_methods" do
81
- @instance.state1?.should == "state1"
82
- @instance.state2?.should == "state2"
81
+ expect(@instance.state1?).to eq("state1")
82
+ expect(@instance.state2?).to eq("state2")
83
83
  end
84
84
 
85
85
  it "does not overwrite predefined event method" do
86
- @instance.event.should == "predefined method"
86
+ expect(@instance.event).to eq("predefined method")
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 = described_class.new klass
106
106
  decorator.decorate transition
107
107
  klass.state_machine_definition.transitions << transition
108
108
  @instance = klass.new
@@ -111,22 +111,22 @@ describe SimpleStateMachine::Decorator do
111
111
 
112
112
  describe "#initialize" do
113
113
  it "defines a state_machine method" do
114
- @instance.state_machine.should be_an(SimpleStateMachine::StateMachine)
114
+ expect(@instance.state_machine).to be_an(SimpleStateMachine::StateMachine)
115
115
  end
116
116
 
117
117
  it "defines a state getter method" do
118
- @instance.should respond_to(:state)
118
+ expect(@instance).to respond_to(:state)
119
119
  end
120
120
 
121
121
  it "defines a state setter method" do
122
- @instance.should respond_to(:state=)
122
+ expect(@instance).to respond_to(:state=)
123
123
  end
124
124
  end
125
125
 
126
126
  describe "#decorate" do
127
127
  it "does not overwrite predefined protected state_helper_methods" do
128
- @instance.send(:state1?).should == "state1"
129
- @instance.send(:state2?).should == "state2"
128
+ expect(@instance.send(:state1?)).to eq("state1")
129
+ expect(@instance.send(:state2?)).to eq("state2")
130
130
  end
131
131
 
132
132
  it "keeps predefined protected state_helper_methods protected" do
@@ -135,7 +135,7 @@ describe SimpleStateMachine::Decorator do
135
135
  end
136
136
 
137
137
  it "does not overwrite predefined protected event method" do
138
- @instance.event.should == "predefined method"
138
+ expect(@instance.event).to eq("predefined method")
139
139
  end
140
140
  end
141
141
  end
@@ -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 = described_class.new klass
158
158
  decorator.decorate transition
159
159
  klass.state_machine_definition.transitions << transition
160
160
  @instance = klass.new
@@ -163,22 +163,22 @@ describe SimpleStateMachine::Decorator do
163
163
 
164
164
  describe "#initialize" do
165
165
  it "defines a state_machine method" do
166
- @instance.state_machine.should be_an(SimpleStateMachine::StateMachine)
166
+ expect(@instance.state_machine).to be_an(SimpleStateMachine::StateMachine)
167
167
  end
168
168
 
169
169
  it "defines a state getter method" do
170
- @instance.should respond_to(:state)
170
+ expect(@instance).to respond_to(:state)
171
171
  end
172
172
 
173
173
  it "defines a state setter method" do
174
- @instance.should respond_to(:state=)
174
+ expect(@instance).to respond_to(:state=)
175
175
  end
176
176
  end
177
177
 
178
178
  describe "#decorate" do
179
179
  it "does not overwrite predefined private state_helper_methods" do
180
- @instance.send(:state1?).should == "state1"
181
- @instance.send(:state2?).should == "state2"
180
+ expect(@instance.send(:state1?)).to eq("state1")
181
+ expect(@instance.send(:state2?)).to eq("state2")
182
182
  end
183
183
 
184
184
  it "keeps predefined private state_helper_methods private" do
@@ -187,7 +187,7 @@ describe SimpleStateMachine::Decorator do
187
187
  end
188
188
 
189
189
  it "does not overwrite predefined protected event method" do
190
- @instance.event.should == "predefined method"
190
+ expect(@instance.event).to eq("predefined method")
191
191
  end
192
192
  end
193
193
  end
@@ -1,57 +1,57 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
1
+ require 'spec_helper'
2
2
 
3
3
  describe "Examples" do
4
4
  describe "TrafficLight" do
5
5
  it "changes to the next state" do
6
6
  tl = TrafficLight.new
7
- tl.should be_green
7
+ expect(tl).to be_green
8
8
  tl.change_state
9
- tl.should be_orange
9
+ expect(tl).to be_orange
10
10
  tl.change_state
11
- tl.should be_red
11
+ expect(tl).to be_red
12
12
  tl.change_state
13
- tl.should be_green
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.should be_off
20
+ expect(lamp).to be_off
21
21
  lamp.push_button1
22
- lamp.should be_on
22
+ expect(lamp).to be_on
23
23
  lamp.push_button2
24
- lamp.should be_off
24
+ expect(lamp).to be_off
25
25
  lamp.push_button2
26
- lamp.should be_on
26
+ expect(lamp).to be_on
27
27
  lamp.push_button1
28
- lamp.should be_off
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.should be_unread
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.should be_read
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.should be_closed
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.should be_closed
54
+ expect(conversation).to be_closed
55
55
  end
56
56
 
57
57
  end
@@ -1,24 +1,36 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
1
+ require 'spec_helper'
2
2
 
3
- describe "Mountable" do
4
- before do
5
- mountable_class = Class.new(SimpleStateMachine::StateMachineDefinition) do
6
- def initialize(subject)
7
- self.lazy_decorator = lambda { SimpleStateMachine::Decorator.new(subject) }
8
- add_transition(:event, :state1, :state2)
9
- end
3
+ describe SimpleStateMachine::Mountable do
4
+ class MountableExample < SimpleStateMachine::StateMachineDefinition
5
+ event(:event, :state1 => :state2)
6
+
7
+ def decorator_class
8
+ SimpleStateMachine::Decorator::Default
10
9
  end
11
- klass = Class.new do
10
+ end
11
+
12
+ let(:klass) do
13
+ Class.new do
14
+ attr_accessor :event_called
12
15
  extend SimpleStateMachine::Mountable
13
- self.state_machine_definition = mountable_class.new self
16
+ mount_state_machine MountableExample
17
+ def event_without_managed_state
18
+ @event_called = true
19
+ end
14
20
  end
15
- @instance = klass.new
16
- @instance.state = 'state1'
21
+ end
22
+ subject do
23
+ klass.new.tap{|i| i.state = 'state1' }
17
24
  end
18
25
 
19
26
  it "has state_helper methods" do
20
- @instance.should be_state1
21
- @instance.should_not be_state2
27
+ expect(subject).to be_state1
28
+ expect(subject).not_to be_state2
22
29
  end
23
30
 
31
+ it "calls existing methods" do
32
+ subject.event
33
+ expect(subject).to be_state2
34
+ expect(subject.event_called).to eq(true)
35
+ end
24
36
  end
@@ -1,126 +1,162 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
- require 'cgi'
1
+ require 'spec_helper'
3
2
 
4
3
  describe SimpleStateMachine do
5
-
4
+
6
5
  it "has an error that extends RuntimeError" do
7
- SimpleStateMachine::IllegalStateTransitionError.superclass.should == RuntimeError
6
+ expect(SimpleStateMachine::IllegalStateTransitionError.superclass).to eq(RuntimeError)
7
+ end
8
+
9
+ let(:klass) do
10
+ Class.new do
11
+ extend SimpleStateMachine
12
+ def initialize(state = 'state1')
13
+ @state = state
14
+ end
15
+ end
8
16
  end
9
17
 
10
18
  describe ".event" do
11
19
 
12
- before do
13
- @klass = Class.new do
14
- extend SimpleStateMachine
15
- def initialize(state = 'state1')
16
- @state = state
20
+ it "returns what the decorated method returns" do
21
+ klass.instance_eval do
22
+ event :event1, :state1 => :state2
23
+ define_method :event2 do
24
+ 'event2'
17
25
  end
26
+ event :event2, :state2 => :state3
18
27
  end
28
+ subject = klass.new
29
+ expect(subject.event1).to eq(nil)
30
+ expect(subject.event2).to eq('event2')
19
31
  end
20
32
 
21
- it "changes state if event has multiple transitions" do
22
- klass = Class.new(@klass)
33
+ it "calls existing methods" do
23
34
  klass.instance_eval do
24
- event :event, :state1 => :state2, :state2 => :state3
35
+ attr_accessor :event_called
36
+ define_method :event do
37
+ @event_called = true
38
+ end
39
+ event :event, :state1 => :state2
25
40
  end
26
- example = klass.new
27
- example.should be_state1
28
- example.event
29
- example.should be_state2
30
- example.event
31
- example.should be_state3
41
+ subject = klass.new
42
+ subject.event
43
+ expect(subject.event_called).to eq(true)
32
44
  end
33
45
 
34
- it "changes state if event has multiple froms" do
35
- klass = Class.new(@klass)
36
- klass.instance_eval do
37
- event :event, [:state1, :state2] => :state3
46
+ context "given an event has multiple transitions" do
47
+ before do
48
+ klass.instance_eval do
49
+ event :event, :state1 => :state2, :state2 => :state3
50
+ end
38
51
  end
39
- example = klass.new
40
- example.event
41
- example.should be_state3
42
- example = klass.new 'state2'
43
- example.should be_state2
44
- example.event
45
- example.should be_state3
46
- end
47
52
 
48
- it "changes state if event has all as from" do
49
- klass = Class.new(@klass)
50
- klass.instance_eval do
51
- event :other_event, :state1 => :state2
52
- event :event, :all => :state3
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
53
60
  end
54
- example = klass.new
55
- example.event
56
- example.should be_state3
57
- example = klass.new 'state2'
58
- example.should be_state2
59
- example.event
60
- example.should be_state3
61
61
  end
62
62
 
63
- it "changes state when state is a symbol instead of a string" do
64
- klass = Class.new(@klass)
65
- klass.instance_eval do
66
- event :event, :state1 => :state2
67
- end
68
- example = klass.new :state1
69
- example.state.should == :state1
70
- example.send(:event)
71
- example.should be_state2
63
+ context "given an event has multiple from states" do
64
+ before do
65
+ klass.instance_eval do
66
+ event :event, [:state1, :state2] => :state3
67
+ end
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
78
+ end
72
79
  end
73
80
 
74
- it "changes state to error_state when error can be caught" do
75
- class_with_error = Class.new(@klass)
76
- class_with_error.instance_eval do
77
- define_method :raise_error do
78
- raise RuntimeError.new
81
+ context "given an event has :all as from state" do
82
+ before do
83
+ klass.instance_eval do
84
+ event :other_event, :state1 => :state2
85
+ event :event, :all => :state3
79
86
  end
80
- event :raise_error, :state1 => :state2, RuntimeError => :failed
81
87
  end
82
- example = class_with_error.new
83
- example.should be_state1
84
- example.raise_error
85
- example.should be_failed
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
97
+ end
86
98
  end
87
-
88
- it "raises an error if an invalid state_transition is called" do
89
- klass = Class.new(@klass)
90
- klass.instance_eval do
91
- event :event, :state1 => :state2
92
- event :event2, :state2 => :state3
93
- end
94
- example = klass.new
95
- lambda { example.event2 }.should raise_error(SimpleStateMachine::IllegalStateTransitionError, "You cannot 'event2' when state is 'state1'")
99
+
100
+ context "given state is a symbol instead of a string" do
101
+ before do
102
+ klass.instance_eval do
103
+ event :event, :state1 => :state2
104
+ end
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
112
+ end
96
113
  end
97
114
 
98
- it "returns what the decorated method returns" do
99
- klass = Class.new(@klass)
100
- klass.instance_eval do
101
- event :event1, :state1 => :state2
102
- define_method :event2 do
103
- 'event2'
115
+ context "given an RuntimeError begin state" do
116
+ it "changes state to error_state when error can be caught" do
117
+ class_with_error = Class.new(klass)
118
+ class_with_error.instance_eval do
119
+ define_method :raise_error do
120
+ raise RuntimeError.new
121
+ end
122
+ event :raise_error, :state1 => :state2, RuntimeError => :failed
104
123
  end
105
- event :event2, :state2 => :state3
106
- end
107
- example = klass.new
108
- example.event1.should == nil
109
- example.event2.should == 'event2'
124
+ subject = class_with_error.new
125
+ expect(subject).to be_state1
126
+ subject.raise_error
127
+ expect(subject).to be_failed
128
+ end
129
+
130
+ it "changes state to error_state when error superclass can be caught" do
131
+ error_subclass = Class.new(RuntimeError)
132
+ class_with_error = Class.new(klass)
133
+ class_with_error.instance_eval do
134
+ define_method :raise_error do
135
+ raise error_subclass.new
136
+ end
137
+ event :raise_error, :state1 => :state2, RuntimeError => :failed
138
+ end
139
+ subject = class_with_error.new
140
+ expect(subject).to be_state1
141
+ subject.raise_error
142
+ expect(subject).to be_failed
143
+ end
110
144
  end
111
145
 
112
- it "calls existing methods" do
113
- klass = Class.new(@klass)
114
- klass.instance_eval do
115
- attr_accessor :event_called
116
- define_method :event do
117
- @event_called = true
146
+ context "given an invalid state_transition is called" do
147
+ before do
148
+ klass.instance_eval do
149
+ event :event, :state1 => :state2
150
+ event :event2, :state2 => :state3
118
151
  end
119
- event :event, :state1 => :state2
120
- end
121
- example = klass.new
122
- example.event
123
- example.event_called.should == true
152
+ end
153
+
154
+ it "raises an IllegalStateTransitionError" do
155
+ subject = klass.new
156
+ expect { subject.event2 }.to raise_error(
157
+ SimpleStateMachine::IllegalStateTransitionError,
158
+ "You cannot 'event2' when state is 'state1'")
159
+ end
124
160
  end
125
161
 
126
162
  end
data/spec/spec_helper.rb CHANGED
@@ -1,7 +1,20 @@
1
- $LOAD_PATH.unshift(File.dirname(__FILE__))
2
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
1
+ require "rubygems"
2
+ require "bundler"
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
+
3
15
  require 'simple_state_machine'
4
- require 'examples/conversation'
5
- require 'examples/lamp'
6
- require 'examples/traffic_light'
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
7
20