very_tiny_state_machine 2.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|