state_gate 1.3.0 → 2.0.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 +4 -4
- data/lib/state_gate/assertions.rb +246 -0
- data/lib/state_gate/engine/configurator.rb +4 -0
- data/lib/state_gate/version.rb +34 -0
- metadata +9 -10
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a4ac3088aa966d3650145f5294d93731291dcd39397597dbb7d35eb69adb5a17
|
|
4
|
+
data.tar.gz: 4e8d593dd1facb0757146b580f3f05c6cfb335c2b163cfa0f09f12d5b072e55d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 90471292a3c2be72e97274785d6934104ce2b3014d424bb4b91517de0d1ccade7f735c2ff6ca4ddfadcfae716d985708b3eb106466b9a0d8eb2c8ed59ab42a6b
|
|
7
|
+
data.tar.gz: 9afb2f7c2d284cf6456a61637403bb513c20bf2a93a37229059ae81c97e11731f0a2c5746b8ea4a2d797bbb7b8ad745b0d4c491b16746b64ec12d75df5c984a9
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StateGate
|
|
4
|
+
##
|
|
5
|
+
# = Description
|
|
6
|
+
#
|
|
7
|
+
# Minitest assertions to verify StateGate defined states and transitions.
|
|
8
|
+
#
|
|
9
|
+
# The Minitest equivalent of the StateGate RSpec +have_states+ and
|
|
10
|
+
# +allow_transitions_on+ matchers.
|
|
11
|
+
#
|
|
12
|
+
# [:source_obj]
|
|
13
|
+
# The Class or Instance to be tested.
|
|
14
|
+
#
|
|
15
|
+
# [:attribute]
|
|
16
|
+
# The attribute being tested.
|
|
17
|
+
#
|
|
18
|
+
# [:states]
|
|
19
|
+
# The expected states as Symbols or Strings.
|
|
20
|
+
#
|
|
21
|
+
# [:from]
|
|
22
|
+
# The state being transitioned from.
|
|
23
|
+
#
|
|
24
|
+
# [:to]
|
|
25
|
+
# The states being transitioned to, as a Symbol, String or Array.
|
|
26
|
+
#
|
|
27
|
+
# assert_has_states Sysop, :status, :pending, :active
|
|
28
|
+
# refute_has_states Sysop, :status, :deleted
|
|
29
|
+
#
|
|
30
|
+
# assert_allows_transitions Sysop, :status, from: :active, to: [:locked, :archived]
|
|
31
|
+
# refute_allows_transitions Sysop, :status, from: :active, to: :pending
|
|
32
|
+
#
|
|
33
|
+
# +assert_has_states+ fails if an existing state is missing, or if given
|
|
34
|
+
# states are not defined for the attribute.
|
|
35
|
+
#
|
|
36
|
+
# +refute_has_states+ fails if any of the given states are defined for
|
|
37
|
+
# the attribute.
|
|
38
|
+
#
|
|
39
|
+
# +assert_allows_transitions+ fails if an allowed transition is missing, or
|
|
40
|
+
# if a given transition is not allowed.
|
|
41
|
+
#
|
|
42
|
+
# +refute_allows_transitions+ fails if any of the given transitions are
|
|
43
|
+
# allowed for the state.
|
|
44
|
+
#
|
|
45
|
+
module Assertions
|
|
46
|
+
|
|
47
|
+
# States
|
|
48
|
+
# ======================================================================
|
|
49
|
+
|
|
50
|
+
##
|
|
51
|
+
# Asserts the given states are exactly the states defined for the attribute.
|
|
52
|
+
#
|
|
53
|
+
# assert_has_states Sysop, :status, :pending, :active, :locked
|
|
54
|
+
# assert_has_states sysop, :status, %w[pending active locked]
|
|
55
|
+
#
|
|
56
|
+
def assert_has_states(source_obj, attribute, *states)
|
|
57
|
+
engine = state_gate_engine(source_obj, attribute)
|
|
58
|
+
key = StateGate.symbolize(attribute)
|
|
59
|
+
expected = state_gate_symbolize_states(states)
|
|
60
|
+
|
|
61
|
+
missing = engine.states - expected
|
|
62
|
+
extra = expected - engine.states
|
|
63
|
+
|
|
64
|
+
return pass if missing.empty? && extra.empty?
|
|
65
|
+
|
|
66
|
+
messages = []
|
|
67
|
+
messages << state_gate_also_valid_message(missing, key) if missing.any?
|
|
68
|
+
messages << state_gate_not_valid_message(extra, key) if extra.any?
|
|
69
|
+
|
|
70
|
+
flunk messages.join(' ')
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
##
|
|
75
|
+
# Asserts none of the given states are defined for the attribute.
|
|
76
|
+
#
|
|
77
|
+
# refute_has_states Sysop, :status, :deleted, :banned
|
|
78
|
+
#
|
|
79
|
+
def refute_has_states(source_obj, attribute, *states)
|
|
80
|
+
engine = state_gate_engine(source_obj, attribute)
|
|
81
|
+
key = StateGate.symbolize(attribute)
|
|
82
|
+
expected = state_gate_symbolize_states(states)
|
|
83
|
+
|
|
84
|
+
found = expected.select { |s| engine.states.include?(s) }
|
|
85
|
+
|
|
86
|
+
return pass if found.empty?
|
|
87
|
+
|
|
88
|
+
if found.one?
|
|
89
|
+
flunk "#{state_gate_list(found)} is a valid state for ##{key}."
|
|
90
|
+
else
|
|
91
|
+
flunk "#{state_gate_list(found)} are valid states for ##{key}."
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# Transitions
|
|
97
|
+
# ======================================================================
|
|
98
|
+
|
|
99
|
+
##
|
|
100
|
+
# Asserts the given states are exactly the transitions allowed from the
|
|
101
|
+
# +:from+ state.
|
|
102
|
+
#
|
|
103
|
+
# assert_allows_transitions Sysop, :status, from: :active,
|
|
104
|
+
# to: [:locked, :suspended, :archived]
|
|
105
|
+
# assert_allows_transitions sysop, :status, from: :pending, to: :active
|
|
106
|
+
#
|
|
107
|
+
def assert_allows_transitions(source_obj, attribute, from: nil, to: nil)
|
|
108
|
+
key, engine, state, expected = state_gate_transition_setup(source_obj, attribute, from, to)
|
|
109
|
+
|
|
110
|
+
allowed = engine.transitions_for_state(state)
|
|
111
|
+
missing = allowed - expected
|
|
112
|
+
extra = expected - allowed
|
|
113
|
+
|
|
114
|
+
return pass if missing.empty? && extra.empty?
|
|
115
|
+
|
|
116
|
+
messages = []
|
|
117
|
+
if missing.any?
|
|
118
|
+
messages << "##{key} also transitions from :#{state} to #{state_gate_list(missing)}."
|
|
119
|
+
end
|
|
120
|
+
if extra.any?
|
|
121
|
+
messages << "##{key} does not transition from :#{state} to #{state_gate_list(extra)}."
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
flunk messages.join(' ')
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
##
|
|
129
|
+
# Asserts none of the given states are transitions allowed from the
|
|
130
|
+
# +:from+ state.
|
|
131
|
+
#
|
|
132
|
+
# refute_allows_transitions Sysop, :status, from: :archived, to: [:locked, :suspended]
|
|
133
|
+
#
|
|
134
|
+
def refute_allows_transitions(source_obj, attribute, from: nil, to: nil)
|
|
135
|
+
_key, engine, state, expected = state_gate_transition_setup(source_obj, attribute, from, to)
|
|
136
|
+
|
|
137
|
+
found = expected & engine.transitions_for_state(state)
|
|
138
|
+
|
|
139
|
+
return pass if found.empty?
|
|
140
|
+
|
|
141
|
+
flunk ":#{state} is allowed to transition to #{state_gate_list(found)}."
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
# Helpers
|
|
146
|
+
# ======================================================================
|
|
147
|
+
|
|
148
|
+
private
|
|
149
|
+
|
|
150
|
+
##
|
|
151
|
+
# Returns the StateGate engine for the attribute, flunking with the
|
|
152
|
+
# relevant message when the setup is incomplete or incorrect.
|
|
153
|
+
#
|
|
154
|
+
def state_gate_engine(source_obj, attribute) # :nodoc:
|
|
155
|
+
key = StateGate.symbolize(attribute)
|
|
156
|
+
|
|
157
|
+
flunk 'missing the <attribute> to be tested.' if key.blank?
|
|
158
|
+
|
|
159
|
+
unless source_obj.respond_to?(:stateables)
|
|
160
|
+
flunk "no state machines are defined for #{state_gate_source_name(source_obj)}."
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
engine = source_obj.stateables[key]
|
|
164
|
+
flunk "no state machine is defined for ##{key}." if engine.blank?
|
|
165
|
+
|
|
166
|
+
engine
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
##
|
|
171
|
+
# Validates the transition parameters, returning the attribute key, the
|
|
172
|
+
# engine, the +:from+ state and the expected +:to+ states.
|
|
173
|
+
#
|
|
174
|
+
def state_gate_transition_setup(source_obj, attribute, from, to) # :nodoc:
|
|
175
|
+
engine = state_gate_engine(source_obj, attribute)
|
|
176
|
+
key = StateGate.symbolize(attribute)
|
|
177
|
+
|
|
178
|
+
flunk 'missing the <from:> state.' if from.blank?
|
|
179
|
+
flunk 'missing the <to:> states.' if to.nil?
|
|
180
|
+
|
|
181
|
+
state = state_gate_symbolize_states([from]).first
|
|
182
|
+
unless engine.states.include?(state)
|
|
183
|
+
flunk ":#{state} is not a valid state for #{state_gate_source_name(source_obj)}##{key}."
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
expected = state_gate_symbolize_states(Array(to))
|
|
187
|
+
invalid = expected.reject { |s| engine.states.include?(s) }
|
|
188
|
+
|
|
189
|
+
if invalid.one?
|
|
190
|
+
flunk "#{state_gate_list(invalid)} is not a valid ##{key} state."
|
|
191
|
+
elsif invalid.any?
|
|
192
|
+
flunk "#{state_gate_list(invalid)} are not valid ##{key} states."
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
[key, engine, state, expected]
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
##
|
|
200
|
+
# The name of the Class or Instance being tested.
|
|
201
|
+
#
|
|
202
|
+
def state_gate_source_name(source_obj) # :nodoc:
|
|
203
|
+
source_obj.is_a?(Class) ? source_obj.name : source_obj.class.name
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
##
|
|
208
|
+
# Flattens and symbolizes the given state names.
|
|
209
|
+
#
|
|
210
|
+
def state_gate_symbolize_states(states) # :nodoc:
|
|
211
|
+
states.flatten.map do |state|
|
|
212
|
+
StateGate.symbolize(state) || flunk("\"#{state}\" is not a valid state name.")
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
# ":pending, :active, and :locked"
|
|
218
|
+
#
|
|
219
|
+
def state_gate_list(states) # :nodoc:
|
|
220
|
+
states.map { |s| ":#{s}" }.to_sentence
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
# "...is also a valid state for #status."
|
|
225
|
+
#
|
|
226
|
+
def state_gate_also_valid_message(missing, key) # :nodoc:
|
|
227
|
+
if missing.one?
|
|
228
|
+
"#{state_gate_list(missing)} is also a valid state for ##{key}."
|
|
229
|
+
else
|
|
230
|
+
"#{state_gate_list(missing)} are also valid states for ##{key}."
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
# "...is not a valid state for #status."
|
|
236
|
+
#
|
|
237
|
+
def state_gate_not_valid_message(extra, key) # :nodoc:
|
|
238
|
+
if extra.one?
|
|
239
|
+
"#{state_gate_list(extra)} is not a valid state for ##{key}."
|
|
240
|
+
else
|
|
241
|
+
"#{state_gate_list(extra)} are not valid states for ##{key}."
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
end # Assertions
|
|
246
|
+
end # StateGate
|
|
@@ -129,10 +129,14 @@ module StateGate
|
|
|
129
129
|
# - verify all transitions lead to existing states
|
|
130
130
|
# - verify each state, except the default, can be reached from a transition
|
|
131
131
|
#
|
|
132
|
+
# @note
|
|
133
|
+
# - gsub both ways to account for ruby 3.4 message change
|
|
134
|
+
#
|
|
132
135
|
def _exec_configuration(&config)
|
|
133
136
|
instance_exec(&config)
|
|
134
137
|
rescue NameError => e
|
|
135
138
|
err_command = e.to_s.gsub('undefined local variable or method `', '')
|
|
139
|
+
.to_s.gsub("undefined local variable or method '", '')
|
|
136
140
|
.split("'")
|
|
137
141
|
.first
|
|
138
142
|
_cerr :bad_command, cmd: err_command
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
##
|
|
4
|
+
# Version for StateGate
|
|
5
|
+
#
|
|
6
|
+
module StateGate
|
|
7
|
+
|
|
8
|
+
##
|
|
9
|
+
# Returns the gem version.
|
|
10
|
+
#
|
|
11
|
+
# @return [Gem::Version]
|
|
12
|
+
#
|
|
13
|
+
def self.gem_version
|
|
14
|
+
Gem::Version.new VERSION::STRING
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
##
|
|
18
|
+
# EncodedToken::VERSION
|
|
19
|
+
#
|
|
20
|
+
# This module represent the current version.
|
|
21
|
+
#
|
|
22
|
+
module VERSION
|
|
23
|
+
|
|
24
|
+
MAJOR = 2
|
|
25
|
+
MINOR = 0
|
|
26
|
+
TINY = 0
|
|
27
|
+
# MICRO = ''
|
|
28
|
+
|
|
29
|
+
STRING = [MAJOR, MINOR, TINY].compact.join(".")
|
|
30
|
+
# STRING = [MAJOR, MINOR, TINY, MICRO].compact.join(".")
|
|
31
|
+
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: state_gate
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 2.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- CodeMeister
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: activerecord
|
|
@@ -24,14 +23,15 @@ dependencies:
|
|
|
24
23
|
- - ">="
|
|
25
24
|
- !ruby/object:Gem::Version
|
|
26
25
|
version: 5.0.0.beta1
|
|
27
|
-
description: " State
|
|
28
|
-
\
|
|
26
|
+
description: " State Management for ActiveRecord, with strict states & transitions.
|
|
27
|
+
\ "
|
|
29
28
|
email: state_gate@codemeister.dev
|
|
30
29
|
executables: []
|
|
31
30
|
extensions: []
|
|
32
31
|
extra_rdoc_files: []
|
|
33
32
|
files:
|
|
34
33
|
- lib/state_gate.rb
|
|
34
|
+
- lib/state_gate/assertions.rb
|
|
35
35
|
- lib/state_gate/builder.rb
|
|
36
36
|
- lib/state_gate/builder/conflict_detection_methods.rb
|
|
37
37
|
- lib/state_gate/builder/dynamic_module_creation_methods.rb
|
|
@@ -54,6 +54,7 @@ files:
|
|
|
54
54
|
- lib/state_gate/rspec/allow_transitions_on.rb
|
|
55
55
|
- lib/state_gate/rspec/have_states.rb
|
|
56
56
|
- lib/state_gate/type.rb
|
|
57
|
+
- lib/state_gate/version.rb
|
|
57
58
|
homepage: https://github.com/Rubology/state_gate
|
|
58
59
|
licenses:
|
|
59
60
|
- MIT
|
|
@@ -61,7 +62,6 @@ metadata:
|
|
|
61
62
|
homepage_uri: https://github.com/Rubology/state_gate
|
|
62
63
|
source_code_uri: https://github.com/Rubology/state_gate
|
|
63
64
|
changelog_uri: https://github.com/Rubology/state_gate/blob/master/CHANGELOG.md
|
|
64
|
-
post_install_message:
|
|
65
65
|
rdoc_options: []
|
|
66
66
|
require_paths:
|
|
67
67
|
- lib
|
|
@@ -69,15 +69,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
69
69
|
requirements:
|
|
70
70
|
- - ">="
|
|
71
71
|
- !ruby/object:Gem::Version
|
|
72
|
-
version: '2.
|
|
72
|
+
version: '2.6'
|
|
73
73
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
74
74
|
requirements:
|
|
75
75
|
- - ">="
|
|
76
76
|
- !ruby/object:Gem::Version
|
|
77
77
|
version: '0'
|
|
78
78
|
requirements: []
|
|
79
|
-
rubygems_version:
|
|
80
|
-
signing_key:
|
|
79
|
+
rubygems_version: 4.0.18
|
|
81
80
|
specification_version: 4
|
|
82
|
-
summary: State
|
|
81
|
+
summary: State Management for ActiveRecord.
|
|
83
82
|
test_files: []
|