simple_state_machine 0.5.2 → 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
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