stepper_motor 0.1.12 → 0.1.16
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/.github/workflows/ci.yml +50 -8
- data/CHANGELOG.md +17 -0
- data/lib/generators/stepper_motor_migration_005.rb.erb +58 -0
- data/lib/stepper_motor/journey/flow_control.rb +49 -0
- data/lib/stepper_motor/journey.rb +53 -10
- data/lib/stepper_motor/step.rb +38 -8
- data/lib/stepper_motor/version.rb +1 -1
- data/manual/MANUAL.md +352 -90
- data/rbi/stepper_motor.rbi +48 -6
- data/sig/stepper_motor.rbs +44 -5
- data/stepper_motor.gemspec +2 -0
- data/test/dummy/config/database.mysql2.yml +14 -0
- data/test/dummy/config/database.postgres.yml +14 -0
- data/test/dummy/config/database.sqlite3.yml +32 -0
- data/test/dummy/config/initializers/stepper_motor.rb +1 -1
- data/test/dummy/db/migrate/20250609221201_stepper_motor_migration_005.rb +58 -0
- data/test/dummy/db/schema.rb +7 -6
- data/test/stepper_motor/journey/exception_handling_test.rb +57 -0
- data/test/stepper_motor/journey/flow_control_test.rb +164 -0
- data/test/stepper_motor/journey/if_condition_test.rb +355 -0
- data/test/stepper_motor/journey/step_definition_test.rb +1 -0
- metadata +37 -6
@@ -0,0 +1,355 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "test_helper"
|
4
|
+
require "minitest/mock"
|
5
|
+
|
6
|
+
class IfConditionTest < ActiveSupport::TestCase
|
7
|
+
include ActiveJob::TestHelper
|
8
|
+
include SideEffects::TestHelper
|
9
|
+
include StepperMotor::TestHelper
|
10
|
+
|
11
|
+
test "supports skip_if: with symbol condition that returns false (performs step)" do
|
12
|
+
journey_class = create_journey_subclass do
|
13
|
+
step :one, skip_if: :should_skip do
|
14
|
+
SideEffects.touch!("step executed")
|
15
|
+
end
|
16
|
+
|
17
|
+
def should_skip
|
18
|
+
false
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
journey = journey_class.create!
|
23
|
+
assert_produced_side_effects("step executed") do
|
24
|
+
journey.perform_next_step!
|
25
|
+
end
|
26
|
+
assert journey.finished?
|
27
|
+
end
|
28
|
+
|
29
|
+
test "supports skip_if: with symbol condition that returns true (skips step)" do
|
30
|
+
journey_class = create_journey_subclass do
|
31
|
+
step :one, skip_if: :should_skip do
|
32
|
+
SideEffects.touch!("step executed")
|
33
|
+
end
|
34
|
+
|
35
|
+
step :two do
|
36
|
+
SideEffects.touch!("second step executed")
|
37
|
+
end
|
38
|
+
|
39
|
+
def should_skip
|
40
|
+
true
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
journey = journey_class.create!
|
45
|
+
speedrun_journey(journey)
|
46
|
+
assert SideEffects.produced?("second step executed")
|
47
|
+
refute SideEffects.produced?("step executed")
|
48
|
+
end
|
49
|
+
|
50
|
+
test "supports skip_if: with block condition that returns false (performs step)" do
|
51
|
+
journey_class = create_journey_subclass do
|
52
|
+
step :one, skip_if: -> { hero.nil? } do
|
53
|
+
SideEffects.touch!("step executed")
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
journey = journey_class.create!(hero: create_journey_subclass.create!)
|
58
|
+
assert_produced_side_effects("step executed") do
|
59
|
+
journey.perform_next_step!
|
60
|
+
end
|
61
|
+
assert journey.finished?
|
62
|
+
end
|
63
|
+
|
64
|
+
test "supports skip_if: with block condition that returns true (skips step)" do
|
65
|
+
journey_class = create_journey_subclass do
|
66
|
+
step :one, skip_if: -> { hero.present? } do
|
67
|
+
SideEffects.touch!("step executed")
|
68
|
+
end
|
69
|
+
|
70
|
+
step :two do
|
71
|
+
SideEffects.touch!("second step executed")
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
journey = journey_class.create!(hero: create_journey_subclass.create!)
|
76
|
+
speedrun_journey(journey)
|
77
|
+
assert SideEffects.produced?("second step executed")
|
78
|
+
refute SideEffects.produced?("step executed")
|
79
|
+
end
|
80
|
+
|
81
|
+
test "supports skip_if: with block condition that accesses journey instance variables" do
|
82
|
+
journey_class = create_journey_subclass do
|
83
|
+
step :one, skip_if: -> { @condition_met } do
|
84
|
+
SideEffects.touch!("step executed")
|
85
|
+
end
|
86
|
+
|
87
|
+
step :two do
|
88
|
+
SideEffects.touch!("second step executed")
|
89
|
+
end
|
90
|
+
|
91
|
+
def initialize(*args)
|
92
|
+
super
|
93
|
+
@condition_met = true
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
journey = journey_class.create!
|
98
|
+
speedrun_journey(journey)
|
99
|
+
assert SideEffects.produced?("second step executed")
|
100
|
+
refute SideEffects.produced?("step executed")
|
101
|
+
end
|
102
|
+
|
103
|
+
test "supports skip_if: with block condition that can be changed during journey execution" do
|
104
|
+
journey_class = create_journey_subclass do
|
105
|
+
step :one, skip_if: -> { @condition_met } do
|
106
|
+
SideEffects.touch!("first step executed")
|
107
|
+
end
|
108
|
+
|
109
|
+
step :two do
|
110
|
+
SideEffects.touch!("second step executed")
|
111
|
+
@condition_met = false
|
112
|
+
end
|
113
|
+
|
114
|
+
step :three, skip_if: -> { @condition_met } do
|
115
|
+
SideEffects.touch!("third step executed")
|
116
|
+
end
|
117
|
+
|
118
|
+
def initialize(*args)
|
119
|
+
super
|
120
|
+
@condition_met = true
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
journey = journey_class.create!
|
125
|
+
speedrun_journey(journey)
|
126
|
+
assert SideEffects.produced?("second step executed")
|
127
|
+
assert SideEffects.produced?("third step executed")
|
128
|
+
refute SideEffects.produced?("first step executed")
|
129
|
+
end
|
130
|
+
|
131
|
+
test "skips step when skip_if: condition is true and continues to next step" do
|
132
|
+
journey_class = create_journey_subclass do
|
133
|
+
step :one, skip_if: :true_condition do
|
134
|
+
SideEffects.touch!("first step executed")
|
135
|
+
end
|
136
|
+
|
137
|
+
step :two do
|
138
|
+
SideEffects.touch!("second step executed")
|
139
|
+
end
|
140
|
+
|
141
|
+
step :three do
|
142
|
+
SideEffects.touch!("third step executed")
|
143
|
+
end
|
144
|
+
|
145
|
+
def true_condition
|
146
|
+
true
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
journey = journey_class.create!
|
151
|
+
speedrun_journey(journey)
|
152
|
+
assert SideEffects.produced?("second step executed")
|
153
|
+
assert SideEffects.produced?("third step executed")
|
154
|
+
refute SideEffects.produced?("first step executed")
|
155
|
+
end
|
156
|
+
|
157
|
+
test "skips step when skip_if: condition is true and finishes journey if no more steps" do
|
158
|
+
journey_class = create_journey_subclass do
|
159
|
+
step :one, skip_if: :true_condition do
|
160
|
+
SideEffects.touch!("step executed")
|
161
|
+
end
|
162
|
+
|
163
|
+
def true_condition
|
164
|
+
true
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
journey = journey_class.create!
|
169
|
+
speedrun_journey(journey)
|
170
|
+
refute SideEffects.produced?("step executed")
|
171
|
+
end
|
172
|
+
|
173
|
+
test "supports skip_if: with literal false (performs step)" do
|
174
|
+
journey_class = create_journey_subclass do
|
175
|
+
step :one, skip_if: false do
|
176
|
+
SideEffects.touch!("step executed")
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
journey = journey_class.create!
|
181
|
+
assert_produced_side_effects("step executed") do
|
182
|
+
journey.perform_next_step!
|
183
|
+
end
|
184
|
+
assert journey.finished?
|
185
|
+
end
|
186
|
+
|
187
|
+
test "supports skip_if: with literal true (skips step)" do
|
188
|
+
journey_class = create_journey_subclass do
|
189
|
+
step :one, skip_if: true do
|
190
|
+
SideEffects.touch!("step executed")
|
191
|
+
end
|
192
|
+
|
193
|
+
step :two do
|
194
|
+
SideEffects.touch!("second step executed")
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
journey = journey_class.create!
|
199
|
+
speedrun_journey(journey)
|
200
|
+
assert SideEffects.produced?("second step executed")
|
201
|
+
refute SideEffects.produced?("step executed")
|
202
|
+
end
|
203
|
+
|
204
|
+
test "supports skip_if: with literal true and finishes journey if no more steps" do
|
205
|
+
journey_class = create_journey_subclass do
|
206
|
+
step :one, skip_if: true do
|
207
|
+
SideEffects.touch!("step executed")
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
journey = journey_class.create!
|
212
|
+
speedrun_journey(journey)
|
213
|
+
refute SideEffects.produced?("step executed")
|
214
|
+
end
|
215
|
+
|
216
|
+
test "defaults to false when skip_if: is not specified" do
|
217
|
+
journey_class = create_journey_subclass do
|
218
|
+
step :one do
|
219
|
+
SideEffects.touch!("step executed")
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
journey = journey_class.create!
|
224
|
+
assert_produced_side_effects("step executed") do
|
225
|
+
journey.perform_next_step!
|
226
|
+
end
|
227
|
+
assert journey.finished?
|
228
|
+
end
|
229
|
+
|
230
|
+
test "treats nil as false in skip_if condition (performs step)" do
|
231
|
+
journey_class = create_journey_subclass do
|
232
|
+
step :one, skip_if: nil do
|
233
|
+
SideEffects.touch!("step executed")
|
234
|
+
end
|
235
|
+
|
236
|
+
step :two do
|
237
|
+
SideEffects.touch!("second step executed")
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
journey = journey_class.create!
|
242
|
+
speedrun_journey(journey)
|
243
|
+
assert SideEffects.produced?("step executed")
|
244
|
+
assert SideEffects.produced?("second step executed")
|
245
|
+
end
|
246
|
+
|
247
|
+
test "raises ArgumentError when skip_if: condition is neither symbol nor callable" do
|
248
|
+
assert_raises(ArgumentError) do
|
249
|
+
create_journey_subclass do
|
250
|
+
step :one, skip_if: "not a symbol or callable" do
|
251
|
+
# noop
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
test "passes skip_if: parameter to step definition" do
|
258
|
+
step_def = StepperMotor::Step.new(name: "a_step", seq: 1, on_exception: :reattempt!)
|
259
|
+
assert_skip_if_parameter = ->(**options) {
|
260
|
+
assert options.key?(:skip_if)
|
261
|
+
assert_equal :test_condition, options[:skip_if]
|
262
|
+
# Return the original definition
|
263
|
+
step_def
|
264
|
+
}
|
265
|
+
|
266
|
+
StepperMotor::Step.stub :new, assert_skip_if_parameter do
|
267
|
+
create_journey_subclass do
|
268
|
+
step :test_step, skip_if: :test_condition do
|
269
|
+
# noop
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
# Backward compatibility tests for if: parameter
|
276
|
+
test "supports if: with symbol condition that returns true (performs step, backward compatibility)" do
|
277
|
+
journey_class = create_journey_subclass do
|
278
|
+
step :one, if: :should_run do
|
279
|
+
SideEffects.touch!("step executed")
|
280
|
+
end
|
281
|
+
|
282
|
+
def should_run
|
283
|
+
true
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
journey = journey_class.create!
|
288
|
+
assert_produced_side_effects("step executed") do
|
289
|
+
journey.perform_next_step!
|
290
|
+
end
|
291
|
+
assert journey.finished?
|
292
|
+
end
|
293
|
+
|
294
|
+
test "supports if: with symbol condition that returns false (skips step, backward compatibility)" do
|
295
|
+
journey_class = create_journey_subclass do
|
296
|
+
step :one, if: :should_run do
|
297
|
+
SideEffects.touch!("step executed")
|
298
|
+
end
|
299
|
+
|
300
|
+
step :two do
|
301
|
+
SideEffects.touch!("second step executed")
|
302
|
+
end
|
303
|
+
|
304
|
+
def should_run
|
305
|
+
false
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
journey = journey_class.create!
|
310
|
+
speedrun_journey(journey)
|
311
|
+
assert SideEffects.produced?("second step executed")
|
312
|
+
refute SideEffects.produced?("step executed")
|
313
|
+
end
|
314
|
+
|
315
|
+
test "supports if: with literal true (performs step, backward compatibility)" do
|
316
|
+
journey_class = create_journey_subclass do
|
317
|
+
step :one, if: true do
|
318
|
+
SideEffects.touch!("step executed")
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
journey = journey_class.create!
|
323
|
+
assert_produced_side_effects("step executed") do
|
324
|
+
journey.perform_next_step!
|
325
|
+
end
|
326
|
+
assert journey.finished?
|
327
|
+
end
|
328
|
+
|
329
|
+
test "supports if: with literal false (skips step, backward compatibility)" do
|
330
|
+
journey_class = create_journey_subclass do
|
331
|
+
step :one, if: false do
|
332
|
+
SideEffects.touch!("step executed")
|
333
|
+
end
|
334
|
+
|
335
|
+
step :two do
|
336
|
+
SideEffects.touch!("second step executed")
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
journey = journey_class.create!
|
341
|
+
speedrun_journey(journey)
|
342
|
+
assert SideEffects.produced?("second step executed")
|
343
|
+
refute SideEffects.produced?("step executed")
|
344
|
+
end
|
345
|
+
|
346
|
+
test "raises error when both skip_if: and if: are specified" do
|
347
|
+
assert_raises(StepperMotor::StepConfigurationError) do
|
348
|
+
create_journey_subclass do
|
349
|
+
step :one, skip_if: :condition1, if: :condition2 do
|
350
|
+
# noop
|
351
|
+
end
|
352
|
+
end
|
353
|
+
end
|
354
|
+
end
|
355
|
+
end
|
@@ -6,6 +6,7 @@ require "minitest/mock"
|
|
6
6
|
class StepDefinitionTest < ActiveSupport::TestCase
|
7
7
|
include ActiveJob::TestHelper
|
8
8
|
include SideEffects::TestHelper
|
9
|
+
include StepperMotor::TestHelper
|
9
10
|
|
10
11
|
test "requires either a block or a name" do
|
11
12
|
assert_raises(StepperMotor::StepConfigurationError) do
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: stepper_motor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.16
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Julik Tarkhanov
|
8
|
-
autorequire:
|
9
8
|
bindir: exe
|
10
9
|
cert_chain: []
|
11
|
-
date: 2025-06-
|
10
|
+
date: 2025-06-20 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: activerecord
|
@@ -94,6 +93,34 @@ dependencies:
|
|
94
93
|
- - ">="
|
95
94
|
- !ruby/object:Gem::Version
|
96
95
|
version: '0'
|
96
|
+
- !ruby/object:Gem::Dependency
|
97
|
+
name: mysql2
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0'
|
103
|
+
type: :development
|
104
|
+
prerelease: false
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - ">="
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: pg
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0'
|
117
|
+
type: :development
|
118
|
+
prerelease: false
|
119
|
+
version_requirements: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - ">="
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '0'
|
97
124
|
- !ruby/object:Gem::Dependency
|
98
125
|
name: rake
|
99
126
|
requirement: !ruby/object:Gem::Requirement
|
@@ -206,6 +233,7 @@ files:
|
|
206
233
|
- lib/generators/stepper_motor_migration_002.rb.erb
|
207
234
|
- lib/generators/stepper_motor_migration_003.rb.erb
|
208
235
|
- lib/generators/stepper_motor_migration_004.rb.erb
|
236
|
+
- lib/generators/stepper_motor_migration_005.rb.erb
|
209
237
|
- lib/stepper_motor.rb
|
210
238
|
- lib/stepper_motor/base_job.rb
|
211
239
|
- lib/stepper_motor/cyclic_scheduler.rb
|
@@ -246,6 +274,9 @@ files:
|
|
246
274
|
- test/dummy/config/application.rb
|
247
275
|
- test/dummy/config/boot.rb
|
248
276
|
- test/dummy/config/cable.yml
|
277
|
+
- test/dummy/config/database.mysql2.yml
|
278
|
+
- test/dummy/config/database.postgres.yml
|
279
|
+
- test/dummy/config/database.sqlite3.yml
|
249
280
|
- test/dummy/config/database.yml
|
250
281
|
- test/dummy/config/environment.rb
|
251
282
|
- test/dummy/config/environments/development.rb
|
@@ -263,6 +294,7 @@ files:
|
|
263
294
|
- test/dummy/db/migrate/20250525132524_stepper_motor_migration_002.rb
|
264
295
|
- test/dummy/db/migrate/20250525132525_stepper_motor_migration_003.rb
|
265
296
|
- test/dummy/db/migrate/20250528141038_stepper_motor_migration_004.rb
|
297
|
+
- test/dummy/db/migrate/20250609221201_stepper_motor_migration_005.rb
|
266
298
|
- test/dummy/db/schema.rb
|
267
299
|
- test/dummy/public/400.html
|
268
300
|
- test/dummy/public/404.html
|
@@ -280,6 +312,7 @@ files:
|
|
280
312
|
- test/stepper_motor/journey/exception_handling_test.rb
|
281
313
|
- test/stepper_motor/journey/flow_control_test.rb
|
282
314
|
- test/stepper_motor/journey/idempotency_test.rb
|
315
|
+
- test/stepper_motor/journey/if_condition_test.rb
|
283
316
|
- test/stepper_motor/journey/step_definition_test.rb
|
284
317
|
- test/stepper_motor/journey/uniqueness_test.rb
|
285
318
|
- test/stepper_motor/journey_test.rb
|
@@ -297,7 +330,6 @@ metadata:
|
|
297
330
|
homepage_uri: https://steppermotor.dev
|
298
331
|
source_code_uri: https://github.com/stepper-motor/stepper_motor
|
299
332
|
changelog_uri: https://github.com/stepper-motor/stepper_motor/blob/main/CHANGELOG.md
|
300
|
-
post_install_message:
|
301
333
|
rdoc_options: []
|
302
334
|
require_paths:
|
303
335
|
- lib
|
@@ -312,8 +344,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
312
344
|
- !ruby/object:Gem::Version
|
313
345
|
version: '0'
|
314
346
|
requirements: []
|
315
|
-
rubygems_version: 3.
|
316
|
-
signing_key:
|
347
|
+
rubygems_version: 3.6.6
|
317
348
|
specification_version: 4
|
318
349
|
summary: Effortless step workflows that embed nicely inside Rails
|
319
350
|
test_files: []
|