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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c914825dceae121cfabc62d93c48e952d0d1ae4995bc0e477d1e2aa2e0cf9107
4
- data.tar.gz: e49f5d7fd2517d88a7ae8c225baa452c75aa6cba94828e2e182fcb3462ffccec
3
+ metadata.gz: a4ac3088aa966d3650145f5294d93731291dcd39397597dbb7d35eb69adb5a17
4
+ data.tar.gz: 4e8d593dd1facb0757146b580f3f05c6cfb335c2b163cfa0f09f12d5b072e55d
5
5
  SHA512:
6
- metadata.gz: 1042ce00609a2501647803fce1a41b689c45084726011c6f0f36e2498b9ca48110cf7d99160311eed74204f939255332be79894056098ccf2c1883dd88d0e6da
7
- data.tar.gz: ba89c3bd3d36181c109ab56d5d9a6d8f68f735bfc645075d3dd16f2e8c7f829ff82746ef1db093f76743d05532159b864e75f78a4e960389d22c8ad818d76f84
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: 1.3.0
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: 2024-08-21 00:00:00.000000000 Z
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 management for ActiveRecord, with states; transitions; and
28
- \ just the right amount of syntactic sugar. "
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.5'
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: 3.5.17
80
- signing_key:
79
+ rubygems_version: 4.0.18
81
80
  specification_version: 4
82
- summary: State management for ActiveRecord.
81
+ summary: State Management for ActiveRecord.
83
82
  test_files: []