very_tiny_state_machine 2.1.0 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitignore +50 -0
- data/.rubocop.yml +10 -0
- data/.travis.yml +3 -4
- data/Gemfile +4 -8
- data/README.md +3 -3
- data/Rakefile +11 -44
- data/lib/very_tiny_state_machine.rb +67 -43
- data/spec/spec_helper.rb +2 -0
- data/spec/very_tiny_state_machine_spec.rb +96 -68
- data/very_tiny_state_machine.gemspec +26 -55
- metadata +47 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 47c8e65c48168e9aba7851fb1b26946a3e518c607f3df930bed9e0128d0b6c7e
|
4
|
+
data.tar.gz: 7d42d1daf4ac34a2c699df150b9a845321d3e853cf9e998183f75e3551077e2e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e9370fa9aa20c27e9469b84e9960a5f2a9f6c35973dbc8dd51b38020fee1a0c8fd729b6fa3b8c70b4b066304451849c92ed9cb9e795bc8dea653b1950fcd775b
|
7
|
+
data.tar.gz: bd669ece20c891094f82eed1fcf511897809ed0b7dc94de36108482f15e08e042e1374e99e29d81d32e3489163ac83e61cb86ecf4f20693b3ddbbac631cd3a9d
|
data/.gitignore
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# rcov generated
|
2
|
+
coverage
|
3
|
+
coverage.data
|
4
|
+
|
5
|
+
# rdoc generated
|
6
|
+
rdoc
|
7
|
+
|
8
|
+
# yard generated
|
9
|
+
doc
|
10
|
+
.yardoc
|
11
|
+
|
12
|
+
# bundler
|
13
|
+
.bundle
|
14
|
+
Gemfile.lock
|
15
|
+
|
16
|
+
# jeweler generated
|
17
|
+
pkg
|
18
|
+
|
19
|
+
# Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore:
|
20
|
+
#
|
21
|
+
# * Create a file at ~/.gitignore
|
22
|
+
# * Include files you want ignored
|
23
|
+
# * Run: git config --global core.excludesfile ~/.gitignore
|
24
|
+
#
|
25
|
+
# After doing this, these files will be ignored in all your git projects,
|
26
|
+
# saving you from having to 'pollute' every project you touch with them
|
27
|
+
#
|
28
|
+
# Not sure what to needs to be ignored for particular editors/OSes? Here's some ideas to get you started. (Remember, remove the leading # of the line)
|
29
|
+
#
|
30
|
+
# For MacOS:
|
31
|
+
#
|
32
|
+
#.DS_Store
|
33
|
+
|
34
|
+
# For TextMate
|
35
|
+
#*.tmproj
|
36
|
+
#tmtags
|
37
|
+
|
38
|
+
# For emacs:
|
39
|
+
#*~
|
40
|
+
#\#*
|
41
|
+
#.\#*
|
42
|
+
|
43
|
+
# For vim:
|
44
|
+
#*.swp
|
45
|
+
|
46
|
+
# For redcar:
|
47
|
+
#.redcar
|
48
|
+
|
49
|
+
# For rubinius:
|
50
|
+
#*.rbc
|
data/.rubocop.yml
ADDED
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
@@ -1,9 +1,5 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
gem "bundler", "~> 1.0"
|
7
|
-
gem "jeweler", "~> 2.0.1"
|
8
|
-
gem 'simplecov', '~> 0.10'
|
9
|
-
end
|
3
|
+
source 'http://rubygems.org'
|
4
|
+
|
5
|
+
gemspec
|
data/README.md
CHANGED
@@ -9,9 +9,9 @@ The state machine has the ability to dispatch callbacks when states are switched
|
|
9
9
|
are dispatched to the given object as messages.
|
10
10
|
|
11
11
|
@automaton = VeryTinyStateMachine.new(:initialized, self)
|
12
|
-
@automaton.
|
13
|
-
@automaton.
|
14
|
-
@automaton.permit_transition :closing => :
|
12
|
+
@automaton.permit_states_and_transitions(:initialized => :processing, :processing => :closing, :closing => [:closed])
|
13
|
+
@automaton.permit_state :failed
|
14
|
+
@automaton.permit_transition :closing => :failed
|
15
15
|
|
16
16
|
# Then, lower down the code
|
17
17
|
@automaton.transition! :processing
|
data/Rakefile
CHANGED
@@ -1,51 +1,18 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require '
|
4
|
-
require 'bundler'
|
5
|
-
begin
|
6
|
-
Bundler.setup(:default, :development)
|
7
|
-
rescue Bundler::BundlerError => e
|
8
|
-
$stderr.puts e.message
|
9
|
-
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
-
exit e.status_code
|
11
|
-
end
|
12
|
-
require 'rake'
|
13
|
-
require_relative 'lib/very_tiny_state_machine'
|
14
|
-
require 'jeweler'
|
15
|
-
Jeweler::Tasks.new do |gem|
|
16
|
-
# gem is a Gem::Specification... see http://guides.rubygems.org/specification-reference/ for more options
|
17
|
-
gem.version = VeryTinyStateMachine::VERSION
|
18
|
-
gem.name = "very_tiny_state_machine"
|
19
|
-
gem.homepage = "http://github.com/WeTransfer/very_tiny_state_machine"
|
20
|
-
gem.license = "MIT"
|
21
|
-
gem.description = %Q{You wouldn't beleive how tiny it is}
|
22
|
-
gem.summary = %Q{A minuscule state machine for storing state of interesting objects}
|
23
|
-
gem.email = "me@julik.nl"
|
24
|
-
gem.authors = ["Julik Tarkhanov"]
|
25
|
-
# dependencies defined in Gemfile
|
26
|
-
end
|
27
|
-
Jeweler::RubygemsDotOrgTasks.new
|
28
|
-
|
29
|
-
require 'rspec/core'
|
3
|
+
require 'bundler/gem_tasks'
|
30
4
|
require 'rspec/core/rake_task'
|
31
|
-
|
32
|
-
|
33
|
-
end
|
5
|
+
require 'yard'
|
6
|
+
require 'rubocop/rake_task'
|
34
7
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
8
|
+
YARD::Rake::YardocTask.new(:doc) do |t|
|
9
|
+
# The dash has to be between the two to "divide" the source files and
|
10
|
+
# miscellaneous documentation files that contain no code
|
11
|
+
t.files = ['lib/**/*.rb', '-', 'LICENSE.txt']
|
39
12
|
end
|
40
13
|
|
41
|
-
|
14
|
+
RSpec::Core::RakeTask.new(:spec)
|
42
15
|
|
43
|
-
|
44
|
-
Rake::RDocTask.new do |rdoc|
|
45
|
-
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
16
|
+
RuboCop::RakeTask.new(:rubocop)
|
46
17
|
|
47
|
-
|
48
|
-
rdoc.title = "very_tiny_state_machine #{version}"
|
49
|
-
rdoc.rdoc_files.include('README*')
|
50
|
-
rdoc.rdoc_files.include('lib/**/*.rb')
|
51
|
-
end
|
18
|
+
task default: %i[spec rubocop]
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'set'
|
2
4
|
|
3
5
|
# A mini state machine object that can be used to track a state flow.
|
@@ -11,13 +13,13 @@ require 'set'
|
|
11
13
|
# @automaton.permit_state :processing, :closing, :closed
|
12
14
|
# @automaton.permit_transition :initialized => :processing, :processing => :closing
|
13
15
|
# @automaton.permit_transition :closing => :closed
|
14
|
-
#
|
16
|
+
#
|
15
17
|
# # Then, lower down the code
|
16
18
|
# @automaton.transition! :processing
|
17
|
-
#
|
19
|
+
#
|
18
20
|
# # This switches the internal state of the machine, and dispatches the following method
|
19
21
|
# # calls on the object given as the second argument to the constructor, in the following order:
|
20
|
-
#
|
22
|
+
#
|
21
23
|
# # self.leaving_initialized_state
|
22
24
|
# # self.entering_processing_state
|
23
25
|
# # self.transitioning_from_initialized_to_processing_state
|
@@ -28,15 +30,15 @@ require 'set'
|
|
28
30
|
#
|
29
31
|
# @automaton.transition :initialized # Will raise TinyStateMachine::InvalidFlow
|
30
32
|
# @automaton.transition :something_odd # Will raise TinyStateMachine::UnknownState
|
31
|
-
#
|
33
|
+
#
|
32
34
|
# @automaton.in_state?(:processing) #=> true
|
33
35
|
# @automaton.in_state?(:initialized) #=> false
|
34
36
|
class VeryTinyStateMachine
|
35
|
-
VERSION = '2.
|
36
|
-
|
37
|
+
VERSION = '2.2.0'
|
38
|
+
INCLUDE_PRIVATES = true
|
37
39
|
InvalidFlow = Class.new(StandardError) # Gets raised when an impossible transition gets requested
|
38
40
|
UnknownState = Class.new(StandardError) # Gets raised when an unknown state gets requested
|
39
|
-
|
41
|
+
|
40
42
|
# Initialize a new TinyStateMachine, with the initial state and the object that will receive callbacks.
|
41
43
|
#
|
42
44
|
# @param initial_state[#to_sym] the initial state of the state machine
|
@@ -48,7 +50,7 @@ class VeryTinyStateMachine
|
|
48
50
|
@permitted_transitions = Set.new
|
49
51
|
@callbacks_via = object_handling_callbacks
|
50
52
|
end
|
51
|
-
|
53
|
+
|
52
54
|
# Permit a single state or multiple states
|
53
55
|
#
|
54
56
|
# @param states [Array] states to permit
|
@@ -59,7 +61,25 @@ class VeryTinyStateMachine
|
|
59
61
|
@permitted_states += states_to_permit
|
60
62
|
will_be_added
|
61
63
|
end
|
62
|
-
|
64
|
+
|
65
|
+
# Permit states and transitions between them, all in one call
|
66
|
+
#
|
67
|
+
# @param **states_to_states [Hash] a mapping from one state the machine may go into and one or multiple states that can be reached from that state
|
68
|
+
# @return self
|
69
|
+
def permit_states_and_transitions(**initial_states_to_destination_states)
|
70
|
+
initial_states_to_destination_states.each_pair do |one_or_more_source_states, one_or_more_destination_states|
|
71
|
+
sources = Array(one_or_more_source_states)
|
72
|
+
destinations = Array(one_or_more_destination_states)
|
73
|
+
sources.each do |src|
|
74
|
+
destinations.each do |dest|
|
75
|
+
permit_state(src, dest)
|
76
|
+
permit_transition(src => dest)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
self
|
81
|
+
end
|
82
|
+
|
63
83
|
# Permit a transition from one state to another. If you need to add multiple transitions
|
64
84
|
# from the same state, just call the method multiple times:
|
65
85
|
#
|
@@ -70,16 +90,17 @@ class VeryTinyStateMachine
|
|
70
90
|
# @return [Array] the list of states added to permitted states
|
71
91
|
def permit_transition(from_to_hash)
|
72
92
|
transitions_to_permit = Set.new
|
73
|
-
from_to_hash.each_pair do |
|
93
|
+
from_to_hash.each_pair do |from_state, to_state|
|
74
94
|
raise UnknownState, from_state unless @permitted_states.include?(from_state.to_sym)
|
75
95
|
raise UnknownState, to_state unless @permitted_states.include?(to_state.to_sym)
|
76
|
-
|
96
|
+
|
97
|
+
transitions_to_permit << { from_state.to_sym => to_state.to_sym }
|
77
98
|
end
|
78
99
|
additions = transitions_to_permit - @permitted_transitions
|
79
100
|
@permitted_transitions += transitions_to_permit
|
80
101
|
additions
|
81
102
|
end
|
82
|
-
|
103
|
+
|
83
104
|
# Tells whether the state is known to this state machine
|
84
105
|
#
|
85
106
|
# @param state[Symbol,String] the state to check for
|
@@ -87,17 +108,17 @@ class VeryTinyStateMachine
|
|
87
108
|
def known?(state)
|
88
109
|
@permitted_states.include?(state.to_sym)
|
89
110
|
end
|
90
|
-
|
111
|
+
|
91
112
|
# Tells whether a transition is permitted to the given state.
|
92
113
|
#
|
93
114
|
# @param to_state[Symbol,String] state to transition to
|
94
115
|
# @return [Boolean] whether the state can be transitioned to
|
95
116
|
def may_transition_to?(to_state)
|
96
117
|
to_state = to_state.to_sym
|
97
|
-
transition = {@state => to_state.to_sym}
|
118
|
+
transition = { @state => to_state.to_sym }
|
98
119
|
@permitted_states.include?(to_state) && @permitted_transitions.include?(transition)
|
99
120
|
end
|
100
|
-
|
121
|
+
|
101
122
|
# Tells whether the state machine is in a given state at the moment
|
102
123
|
#
|
103
124
|
# @param requisite_state [Symbol,String] name of the state to check for
|
@@ -105,7 +126,7 @@ class VeryTinyStateMachine
|
|
105
126
|
def in_state?(requisite_state)
|
106
127
|
@state == requisite_state.to_sym
|
107
128
|
end
|
108
|
-
|
129
|
+
|
109
130
|
# Ensure the machine is in a given state, and if it isn't raise an InvalidFlow
|
110
131
|
#
|
111
132
|
# @param requisite_state[#to_sym] the state to verify
|
@@ -115,6 +136,7 @@ class VeryTinyStateMachine
|
|
115
136
|
unless requisite_state.to_sym == @state
|
116
137
|
raise InvalidFlow, "Must be in #{requisite_state.inspect} state, but was in #{@state.inspect}"
|
117
138
|
end
|
139
|
+
|
118
140
|
true
|
119
141
|
end
|
120
142
|
|
@@ -123,7 +145,7 @@ class VeryTinyStateMachine
|
|
123
145
|
# be raised if you did not permit this transition explicitly. If you want to transition to a state OR
|
124
146
|
# stay in it if it is already active use {TinyStateMachine#transition_or_maintain!}
|
125
147
|
#
|
126
|
-
#
|
148
|
+
#
|
127
149
|
# During transitions the before callbacks will be called on the @callbacks_via instance variable. If you are
|
128
150
|
# transitioning from "initialized" to "processing" for instance, the following callbacks will be dispatched:
|
129
151
|
#
|
@@ -142,23 +164,24 @@ class VeryTinyStateMachine
|
|
142
164
|
# @raise InvalidFlow
|
143
165
|
def transition!(new_state)
|
144
166
|
new_state = new_state.to_sym
|
145
|
-
|
167
|
+
|
146
168
|
raise UnknownState, new_state.inspect unless known?(new_state)
|
169
|
+
|
147
170
|
if may_transition_to?(new_state)
|
148
171
|
dispatch_callbacks_before_transition(new_state) if @callbacks_via
|
149
|
-
|
172
|
+
|
150
173
|
previous = @state
|
151
174
|
@state = new_state.to_sym
|
152
175
|
@flow << new_state.to_sym
|
153
|
-
|
176
|
+
|
154
177
|
dispatch_callbacks_after_transition(previous) if @callbacks_via
|
155
178
|
previous
|
156
179
|
else
|
157
|
-
raise InvalidFlow,
|
158
|
-
|
180
|
+
raise InvalidFlow,
|
181
|
+
"Cannot change states from #{@state} to #{new_state} (flow so far: #{@flow.join(' > ')})"
|
159
182
|
end
|
160
183
|
end
|
161
|
-
|
184
|
+
|
162
185
|
# Transition to a given state. If the machine already is in that state, do nothing.
|
163
186
|
# If the transition has to happen (the requested state is different than the current)
|
164
187
|
# transition! will be called instead.
|
@@ -169,54 +192,55 @@ class VeryTinyStateMachine
|
|
169
192
|
# @return [void]
|
170
193
|
def transition_or_maintain!(new_state)
|
171
194
|
return if in_state?(new_state)
|
195
|
+
|
172
196
|
transition! new_state
|
173
197
|
end
|
174
|
-
|
198
|
+
|
175
199
|
# Returns the flow of the transitions the machine went through so far
|
176
200
|
#
|
177
201
|
# @return [Array] the array of states
|
178
202
|
def flow_so_far
|
179
203
|
@flow.dup
|
180
204
|
end
|
181
|
-
|
205
|
+
|
182
206
|
private
|
183
|
-
|
207
|
+
|
184
208
|
def dispatch_callbacks_after_transition(from)
|
185
209
|
to = @state
|
186
|
-
if @callbacks_via.respond_to?("after_transitioning_from_#{from}_to_#{to}_state",
|
210
|
+
if @callbacks_via.respond_to?("after_transitioning_from_#{from}_to_#{to}_state", INCLUDE_PRIVATES)
|
187
211
|
@callbacks_via.send("after_transitioning_from_#{from}_to_#{to}_state")
|
188
212
|
end
|
189
|
-
|
190
|
-
if @callbacks_via.respond_to?("after_leaving_#{from}_state",
|
213
|
+
|
214
|
+
if @callbacks_via.respond_to?("after_leaving_#{from}_state", INCLUDE_PRIVATES)
|
191
215
|
@callbacks_via.send("after_leaving_#{from}_state")
|
192
216
|
end
|
193
|
-
|
194
|
-
if @callbacks_via.respond_to?("after_entering_#{to}_state",
|
217
|
+
|
218
|
+
if @callbacks_via.respond_to?("after_entering_#{to}_state", INCLUDE_PRIVATES)
|
195
219
|
@callbacks_via.send("after_entering_#{to}_state")
|
196
220
|
end
|
197
|
-
|
198
|
-
if @callbacks_via.respond_to?(:after_every_transition,
|
221
|
+
|
222
|
+
if @callbacks_via.respond_to?(:after_every_transition, INCLUDE_PRIVATES)
|
199
223
|
@callbacks_via.send(:after_every_transition, from, to)
|
200
224
|
end
|
201
225
|
end
|
202
|
-
|
226
|
+
|
203
227
|
def dispatch_callbacks_before_transition(to)
|
204
228
|
from = @state
|
205
|
-
|
206
|
-
if @callbacks_via.respond_to?(:before_every_transition,
|
229
|
+
|
230
|
+
if @callbacks_via.respond_to?(:before_every_transition, INCLUDE_PRIVATES)
|
207
231
|
@callbacks_via.send(:before_every_transition, from, to)
|
208
232
|
end
|
209
|
-
|
210
|
-
if @callbacks_via.respond_to?("leaving_#{from}_state",
|
233
|
+
|
234
|
+
if @callbacks_via.respond_to?("leaving_#{from}_state", INCLUDE_PRIVATES)
|
211
235
|
@callbacks_via.send("leaving_#{from}_state")
|
212
236
|
end
|
213
|
-
|
214
|
-
if @callbacks_via.respond_to?("entering_#{to}_state",
|
237
|
+
|
238
|
+
if @callbacks_via.respond_to?("entering_#{to}_state", INCLUDE_PRIVATES)
|
215
239
|
@callbacks_via.send("entering_#{to}_state")
|
216
240
|
end
|
217
|
-
|
218
|
-
if @callbacks_via.respond_to?("transitioning_from_#{from}_to_#{to}",
|
241
|
+
|
242
|
+
if @callbacks_via.respond_to?("transitioning_from_#{from}_to_#{to}", INCLUDE_PRIVATES)
|
219
243
|
@callbacks_via.send("transitioning_from_#{from}_to_#{to}")
|
220
244
|
end
|
221
245
|
end
|
222
|
-
end
|
246
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
4
|
|
3
5
|
describe VeryTinyStateMachine do
|
@@ -7,126 +9,151 @@ describe VeryTinyStateMachine do
|
|
7
9
|
expect(machine).to be_known(:started)
|
8
10
|
expect(machine).to be_in_state(:started)
|
9
11
|
end
|
10
|
-
|
12
|
+
|
11
13
|
it 'accepts a second argument' do
|
12
14
|
acceptor = double('Callbacks')
|
13
|
-
|
15
|
+
described_class.new(:started, acceptor)
|
14
16
|
end
|
15
17
|
end
|
16
|
-
|
18
|
+
|
17
19
|
describe '#permit_state' do
|
18
20
|
it 'makes the state known to the state machine' do
|
19
21
|
machine = described_class.new(:started)
|
20
|
-
|
22
|
+
|
21
23
|
first_result_of_permission = machine.permit_state :closed
|
22
24
|
second_result_of_permission = machine.permit_state :closed, :open
|
23
|
-
|
25
|
+
|
24
26
|
expect(first_result_of_permission).to eq(Set.new([:closed]))
|
25
27
|
expect(second_result_of_permission).to eq(Set.new([:open]))
|
26
|
-
|
28
|
+
|
27
29
|
expect(machine).to be_known(:started)
|
28
30
|
expect(machine).to be_known(:closed)
|
29
31
|
expect(machine).to be_known(:open)
|
30
32
|
end
|
31
|
-
|
33
|
+
|
32
34
|
it 'does not permit transitions to the newly added state by default' do
|
33
35
|
machine = described_class.new(:started)
|
34
36
|
machine.permit_state :running
|
35
|
-
|
37
|
+
|
36
38
|
expect(machine).not_to be_may_transition_to(:running)
|
37
|
-
|
38
|
-
expect
|
39
|
+
|
40
|
+
expect do
|
39
41
|
machine.transition! :running
|
40
|
-
|
42
|
+
end.to raise_error(described_class::InvalidFlow, /Cannot change states from started to running/)
|
41
43
|
end
|
42
44
|
end
|
43
|
-
|
45
|
+
|
44
46
|
describe '#permit_transition' do
|
45
47
|
it 'raises on an unknown state specified as source' do
|
46
48
|
machine = described_class.new(:started)
|
47
|
-
expect
|
48
|
-
machine.permit_transition :
|
49
|
-
|
49
|
+
expect do
|
50
|
+
machine.permit_transition unknown: :started
|
51
|
+
end.to raise_error(VeryTinyStateMachine::UnknownState)
|
50
52
|
end
|
51
|
-
|
53
|
+
|
52
54
|
it 'raises on an unknwon state specified as destination' do
|
53
55
|
machine = described_class.new(:started)
|
54
|
-
expect
|
55
|
-
machine.permit_transition :
|
56
|
-
|
56
|
+
expect do
|
57
|
+
machine.permit_transition started: :unknown
|
58
|
+
end.to raise_error(VeryTinyStateMachine::UnknownState)
|
57
59
|
end
|
58
|
-
|
60
|
+
|
59
61
|
it 'returns a Set of transitions permitted after the call' do
|
60
62
|
machine = described_class.new(:started)
|
61
63
|
machine.permit_state :running
|
62
|
-
|
63
|
-
result = machine.permit_transition :
|
64
|
-
|
64
|
+
|
65
|
+
result = machine.permit_transition started: :running
|
66
|
+
|
65
67
|
expect(result).to be_kind_of(Set)
|
66
|
-
expect(result).to eq(Set.new([{:
|
67
|
-
|
68
|
-
adding_second_time = machine.permit_transition :
|
68
|
+
expect(result).to eq(Set.new([{ started: :running }]))
|
69
|
+
|
70
|
+
adding_second_time = machine.permit_transition started: :running
|
69
71
|
expect(adding_second_time).to be_kind_of(Set)
|
70
72
|
expect(adding_second_time).to be_empty
|
71
73
|
end
|
72
|
-
|
74
|
+
|
73
75
|
it 'is able to perform the transition after it has been defined' do
|
74
76
|
machine = described_class.new(:started)
|
75
77
|
machine.permit_state :running
|
76
|
-
machine.permit_transition :
|
78
|
+
machine.permit_transition started: :running
|
77
79
|
machine.transition! :running
|
78
80
|
end
|
79
|
-
|
81
|
+
|
80
82
|
it 'allows the transition from a state to itself only explicitly' do
|
81
83
|
machine = described_class.new(:started)
|
82
|
-
expect
|
84
|
+
expect do
|
83
85
|
machine.transition! :started
|
84
|
-
|
85
|
-
|
86
|
-
machine.permit_transition :
|
86
|
+
end.to raise_error(described_class::InvalidFlow)
|
87
|
+
|
88
|
+
machine.permit_transition started: :started
|
87
89
|
machine.transition! :started
|
88
|
-
expect(machine.flow_so_far).to eq([
|
90
|
+
expect(machine.flow_so_far).to eq(%i[started started])
|
89
91
|
end
|
90
92
|
end
|
91
|
-
|
93
|
+
|
94
|
+
describe '#permit_states_and_transitions' do
|
95
|
+
it 'accepts any states and sets up transitions' do
|
96
|
+
machine = described_class.new(:started)
|
97
|
+
|
98
|
+
same_machine = machine.permit_states_and_transitions(started: [:running, :overheat], running: [:stopped])
|
99
|
+
|
100
|
+
expect(same_machine).to eq(machine)
|
101
|
+
expect(machine.may_transition_to?(:running)).to eq(true)
|
102
|
+
expect(machine.may_transition_to?(:stopped)).to eq(false)
|
103
|
+
|
104
|
+
machine.transition! :running
|
105
|
+
expect(machine.may_transition_to?(:stopped)).to eq(true)
|
106
|
+
expect(machine.may_transition_to?(:overheat)).to eq(false)
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'accepts same transitions and states multiple times' do
|
110
|
+
machine = described_class.new(:started)
|
111
|
+
|
112
|
+
machine.permit_states_and_transitions(started: [:running, :overheat], running: [:stopped])
|
113
|
+
machine.permit_states_and_transitions(started: [:running, :overheat], running: [:stopped])
|
114
|
+
|
115
|
+
expect(machine.may_transition_to?(:running)).to eq(true)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
92
119
|
describe '#flow_so_far' do
|
93
120
|
it 'records the flow' do
|
94
121
|
machine = described_class.new(:started)
|
95
122
|
machine.permit_state :running, :stopped
|
96
|
-
machine.permit_transition :
|
97
|
-
|
123
|
+
machine.permit_transition started: :running, running: :stopped, stopped: :started
|
124
|
+
|
98
125
|
machine.transition! :running
|
99
126
|
machine.transition! :stopped
|
100
127
|
machine.transition! :started
|
101
|
-
|
128
|
+
|
102
129
|
flow = machine.flow_so_far
|
103
|
-
expect(flow).to eq([
|
104
|
-
|
130
|
+
expect(flow).to eq(%i[started running stopped started])
|
131
|
+
|
105
132
|
flow << nil
|
106
|
-
expect(flow).not_to eq(machine.flow_so_far),
|
133
|
+
expect(flow).not_to eq(machine.flow_so_far), 'The flow returned should not link to the mutable array in the machine'
|
107
134
|
end
|
108
135
|
end
|
109
|
-
|
136
|
+
|
110
137
|
describe '#transition!' do
|
111
138
|
it 'returns the previous state the object was in' do
|
112
139
|
machine = described_class.new(:started)
|
113
140
|
machine.permit_state :running
|
114
|
-
machine.permit_transition :
|
141
|
+
machine.permit_transition started: :running
|
115
142
|
transitioned_from = machine.transition! :running
|
116
143
|
expect(transitioned_from).to eq(:started)
|
117
144
|
end
|
118
|
-
|
145
|
+
|
119
146
|
it 'sends all of the callbacks if the object responds to them' do
|
120
147
|
fake_acceptor = double('Callback handler')
|
121
|
-
allow(fake_acceptor).to receive(:respond_to?) {|
|
148
|
+
allow(fake_acceptor).to receive(:respond_to?) { |_method_name, honor_private_and_public|
|
122
149
|
expect(honor_private_and_public).to eq(true)
|
123
150
|
true
|
124
151
|
}
|
125
|
-
|
152
|
+
|
126
153
|
machine = described_class.new(:started, fake_acceptor)
|
127
154
|
machine.permit_state :running, :stopped
|
128
|
-
machine.permit_transition :
|
129
|
-
|
155
|
+
machine.permit_transition started: :running, running: :stopped, stopped: :started
|
156
|
+
|
130
157
|
expect(fake_acceptor).to receive(:before_every_transition).with(:started, :running)
|
131
158
|
expect(fake_acceptor).to receive(:leaving_started_state)
|
132
159
|
expect(fake_acceptor).to receive(:entering_running_state)
|
@@ -138,70 +165,71 @@ describe VeryTinyStateMachine do
|
|
138
165
|
|
139
166
|
machine.transition! :running
|
140
167
|
end
|
141
|
-
|
168
|
+
|
142
169
|
it 'dispatches callbacks to private methods as well' do
|
143
170
|
acceptor = Class.new do
|
144
171
|
def called?
|
145
172
|
@entered_state
|
146
173
|
end
|
147
|
-
|
148
|
-
|
174
|
+
|
175
|
+
private
|
176
|
+
|
149
177
|
def entering_running_state
|
150
178
|
@entered_state = true
|
151
179
|
end
|
152
180
|
end.new
|
153
|
-
|
181
|
+
|
154
182
|
machine = described_class.new(:started, acceptor)
|
155
183
|
machine.permit_state :running, :stopped
|
156
|
-
machine.permit_transition :
|
157
|
-
|
184
|
+
machine.permit_transition started: :running, running: :stopped, stopped: :started
|
185
|
+
|
158
186
|
machine.transition! :running
|
159
187
|
expect(acceptor).to be_called
|
160
188
|
end
|
161
|
-
|
189
|
+
|
162
190
|
it 'does not send the messages to an acceptor that does not respond to those messages' do
|
163
191
|
fake_acceptor = double('Callback handler')
|
164
|
-
allow(fake_acceptor).to receive(:respond_to?) {|
|
192
|
+
allow(fake_acceptor).to receive(:respond_to?) { |_method_name, honor_private_and_public|
|
165
193
|
expect(honor_private_and_public).to eq(true)
|
166
194
|
false
|
167
195
|
}
|
168
|
-
|
196
|
+
|
169
197
|
machine = described_class.new(:started, fake_acceptor)
|
170
198
|
machine.permit_state :running, :stopped
|
171
|
-
machine.permit_transition :
|
172
|
-
|
199
|
+
machine.permit_transition started: :running, running: :stopped, stopped: :started
|
200
|
+
|
173
201
|
machine.transition! :running
|
174
202
|
end
|
175
203
|
end
|
176
|
-
|
204
|
+
|
177
205
|
describe '#transition_or_maintain!' do
|
178
206
|
it 'does not perform any transitions if the object is already in the requisite state' do
|
179
207
|
machine = described_class.new(:perfect)
|
180
208
|
machine.transition_or_maintain! :perfect
|
181
209
|
expect(machine.flow_so_far).to eq([:perfect])
|
182
210
|
end
|
183
|
-
|
211
|
+
|
184
212
|
it 'does perform a transition if the object is not in the requisite state' do
|
185
213
|
machine = described_class.new(:perfect)
|
186
214
|
machine.permit_state :perfect, :improving
|
187
|
-
machine.permit_transition :
|
188
|
-
|
215
|
+
machine.permit_transition perfect: :improving, improving: :perfect
|
216
|
+
|
189
217
|
machine.transition_or_maintain! :improving
|
190
|
-
expect(machine.flow_so_far).to eq([
|
218
|
+
expect(machine.flow_so_far).to eq(%i[perfect improving])
|
191
219
|
end
|
192
220
|
end
|
193
|
-
|
221
|
+
|
194
222
|
describe '#expect!' do
|
195
223
|
it 'returns true when the machine is in the requisite state' do
|
196
224
|
machine = described_class.new(:started)
|
197
225
|
expect(machine.expect!(:started)).to eq(true)
|
198
226
|
end
|
199
|
-
|
227
|
+
|
200
228
|
it 'raises an exception if the machine is not in that state' do
|
201
229
|
machine = described_class.new(:started)
|
202
|
-
expect
|
230
|
+
expect do
|
203
231
|
machine.expect!(:running)
|
204
|
-
|
232
|
+
end.to raise_error(VeryTinyStateMachine::InvalidFlow, 'Must be in :running state, but was in :started')
|
205
233
|
end
|
206
234
|
end
|
207
235
|
end
|
@@ -1,63 +1,34 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('../lib', __FILE__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'very_tiny_state_machine'
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
|
-
s.name =
|
9
|
-
s.version =
|
8
|
+
s.name = 'very_tiny_state_machine'
|
9
|
+
s.version = VeryTinyStateMachine::VERSION
|
10
10
|
|
11
|
-
s.required_rubygems_version = Gem::Requirement.new(
|
12
|
-
s.require_paths = [
|
13
|
-
s.authors = [
|
14
|
-
s.date = "2016-11-01"
|
11
|
+
s.required_rubygems_version = Gem::Requirement.new('>= 0') if s.respond_to? :required_rubygems_version=
|
12
|
+
s.require_paths = ['lib']
|
13
|
+
s.authors = ['Julik Tarkhanov']
|
15
14
|
s.description = "You wouldn't beleive how tiny it is"
|
16
|
-
s.email =
|
15
|
+
s.email = 'me@julik.nl'
|
17
16
|
s.extra_rdoc_files = [
|
18
|
-
|
19
|
-
|
20
|
-
]
|
21
|
-
s.files = [
|
22
|
-
".document",
|
23
|
-
".rspec",
|
24
|
-
".travis.yml",
|
25
|
-
"Gemfile",
|
26
|
-
"LICENSE.txt",
|
27
|
-
"README.md",
|
28
|
-
"Rakefile",
|
29
|
-
"lib/very_tiny_state_machine.rb",
|
30
|
-
"spec/spec_helper.rb",
|
31
|
-
"spec/very_tiny_state_machine_spec.rb",
|
32
|
-
"very_tiny_state_machine.gemspec"
|
17
|
+
'LICENSE.txt',
|
18
|
+
'README.md'
|
33
19
|
]
|
34
|
-
s.
|
35
|
-
s.
|
36
|
-
s.
|
37
|
-
s.
|
20
|
+
s.files = `git ls-files -z`.split("\x0")
|
21
|
+
s.homepage = 'http://github.com/WeTransfer/very_tiny_state_machine'
|
22
|
+
s.licenses = ['MIT']
|
23
|
+
s.rubygems_version = '2.4.5.1'
|
24
|
+
s.summary = 'A minuscule state machine for storing state of interesting objects'
|
38
25
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
s.add_development_dependency(%q<simplecov>, ["~> 0.10"])
|
48
|
-
else
|
49
|
-
s.add_dependency(%q<rspec>, ["~> 3.2.0"])
|
50
|
-
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
51
|
-
s.add_dependency(%q<bundler>, ["~> 1.0"])
|
52
|
-
s.add_dependency(%q<jeweler>, ["~> 2.0.1"])
|
53
|
-
s.add_dependency(%q<simplecov>, ["~> 0.10"])
|
54
|
-
end
|
55
|
-
else
|
56
|
-
s.add_dependency(%q<rspec>, ["~> 3.2.0"])
|
57
|
-
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
58
|
-
s.add_dependency(%q<bundler>, ["~> 1.0"])
|
59
|
-
s.add_dependency(%q<jeweler>, ["~> 2.0.1"])
|
60
|
-
s.add_dependency(%q<simplecov>, ["~> 0.10"])
|
61
|
-
end
|
26
|
+
s.specification_version = 4
|
27
|
+
s.add_development_dependency('bundler')
|
28
|
+
s.add_development_dependency('rake', '~> 12')
|
29
|
+
s.add_development_dependency('rdoc', ['~> 3'])
|
30
|
+
s.add_development_dependency('rspec', ['~> 3'])
|
31
|
+
s.add_development_dependency('simplecov', ['~> 0.10'])
|
32
|
+
s.add_development_dependency('wetransfer_style', '0.6.0') # Lock since we want to be backwards-compat down to Ruby 2.1
|
33
|
+
s.add_development_dependency('yard')
|
62
34
|
end
|
63
|
-
|
metadata
CHANGED
@@ -1,71 +1,71 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: very_tiny_state_machine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Julik Tarkhanov
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-05-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: bundler
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: '0'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '12'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '12'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: rdoc
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
47
|
+
version: '3'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
54
|
+
version: '3'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: rspec
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version:
|
61
|
+
version: '3'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version:
|
68
|
+
version: '3'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: simplecov
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -80,6 +80,34 @@ dependencies:
|
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0.10'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: wetransfer_style
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - '='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 0.6.0
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - '='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 0.6.0
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: yard
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
83
111
|
description: You wouldn't beleive how tiny it is
|
84
112
|
email: me@julik.nl
|
85
113
|
executables: []
|
@@ -89,7 +117,9 @@ extra_rdoc_files:
|
|
89
117
|
- README.md
|
90
118
|
files:
|
91
119
|
- ".document"
|
120
|
+
- ".gitignore"
|
92
121
|
- ".rspec"
|
122
|
+
- ".rubocop.yml"
|
93
123
|
- ".travis.yml"
|
94
124
|
- Gemfile
|
95
125
|
- LICENSE.txt
|
@@ -118,8 +148,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
118
148
|
- !ruby/object:Gem::Version
|
119
149
|
version: '0'
|
120
150
|
requirements: []
|
121
|
-
|
122
|
-
rubygems_version: 2.4.5.1
|
151
|
+
rubygems_version: 3.0.3
|
123
152
|
signing_key:
|
124
153
|
specification_version: 4
|
125
154
|
summary: A minuscule state machine for storing state of interesting objects
|