stepper_motor 0.1.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 +7 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/.standard.yml +8 -0
- data/.yardopts +1 -0
- data/CHANGELOG.md +3 -0
- data/LICENSE.md +3 -0
- data/README.md +421 -0
- data/Rakefile +10 -0
- data/bin/console +11 -0
- data/bin/setup +8 -0
- data/lib/generators/install_generator.rb +38 -0
- data/lib/generators/stepper_motor_migration_001.rb.erb +41 -0
- data/lib/stepper_motor/cyclic_scheduler.rb +63 -0
- data/lib/stepper_motor/forward_scheduler.rb +20 -0
- data/lib/stepper_motor/journey.rb +274 -0
- data/lib/stepper_motor/perform_step_job.rb +14 -0
- data/lib/stepper_motor/railtie.rb +49 -0
- data/lib/stepper_motor/reap_hung_journeys_job.rb +12 -0
- data/lib/stepper_motor/step.rb +17 -0
- data/lib/stepper_motor/test_helper.rb +34 -0
- data/lib/stepper_motor/version.rb +5 -0
- data/lib/stepper_motor.rb +22 -0
- data/sig/stepper_motor.rbs +4 -0
- data/spec/helpers/side_effects.rb +76 -0
- data/spec/spec_helper.rb +53 -0
- data/spec/stepper_motor/cyclic_scheduler_spec.rb +67 -0
- data/spec/stepper_motor/generator_spec.rb +14 -0
- data/spec/stepper_motor/journey_spec.rb +426 -0
- data/spec/stepper_motor/test_helper_spec.rb +44 -0
- data/spec/stepper_motor_spec.rb +7 -0
- metadata +203 -0
@@ -0,0 +1,426 @@
|
|
1
|
+
require_relative "../spec_helper"
|
2
|
+
|
3
|
+
# rubocop:disable Lint/ConstantDefinitionInBlock
|
4
|
+
RSpec.describe "StepperMotor::Journey" do
|
5
|
+
include ActiveJob::TestHelper
|
6
|
+
|
7
|
+
before :all do
|
8
|
+
establish_test_connection
|
9
|
+
run_generator
|
10
|
+
run_migrations
|
11
|
+
ActiveJob::Base.queue_adapter = :test
|
12
|
+
ActiveJob::Base.logger = Logger.new(nil)
|
13
|
+
end
|
14
|
+
|
15
|
+
after :all do
|
16
|
+
FileUtils.rm_rf(fake_app_root)
|
17
|
+
end
|
18
|
+
|
19
|
+
before :each do
|
20
|
+
Thread.current[:stepper_motor_side_effects] = {}
|
21
|
+
end
|
22
|
+
|
23
|
+
after :each do
|
24
|
+
# Remove all jobs that remain in the queue
|
25
|
+
ActiveJob::Base.queue_adapter.enqueued_jobs.clear
|
26
|
+
end
|
27
|
+
|
28
|
+
it "allows an empty journey to be defined and performed to completion" do
|
29
|
+
class PointlessJourney < StepperMotor::Journey
|
30
|
+
end
|
31
|
+
|
32
|
+
journey = PointlessJourney.create!
|
33
|
+
journey.perform_next_step!
|
34
|
+
expect(journey).to be_finished
|
35
|
+
end
|
36
|
+
|
37
|
+
it "allows a journey consisting of one step to be defined and performed to completion" do
|
38
|
+
class SingleStepJourney < StepperMotor::Journey
|
39
|
+
step :do_thing do
|
40
|
+
SideEffects.touch!("do_thing")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
journey = SingleStepJourney.create!
|
45
|
+
expect(journey.next_step_to_be_performed_at).not_to be_nil
|
46
|
+
journey.perform_next_step!
|
47
|
+
expect(journey).to be_finished
|
48
|
+
expect(SideEffects).to be_produced("do_thing")
|
49
|
+
end
|
50
|
+
|
51
|
+
it "allows a journey consisting of multiple named steps to be defined and performed to completion" do
|
52
|
+
step_names = [:step1, :step2, :step3]
|
53
|
+
|
54
|
+
# Use Class.new so that step_names can be passed into the block
|
55
|
+
MultiStepJourney = Class.new(StepperMotor::Journey) do
|
56
|
+
step_names.each do |step_name|
|
57
|
+
step step_name do
|
58
|
+
SideEffects.touch!("from_#{step_name}")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
journey = MultiStepJourney.create!
|
64
|
+
expect(journey.next_step_name).to eq("step1")
|
65
|
+
|
66
|
+
journey.perform_next_step!
|
67
|
+
expect(journey.next_step_name).to eq("step2")
|
68
|
+
expect(journey.previous_step_name).to eq("step1")
|
69
|
+
|
70
|
+
journey.perform_next_step!
|
71
|
+
expect(journey.next_step_name).to eq("step3")
|
72
|
+
expect(journey.previous_step_name).to eq("step2")
|
73
|
+
|
74
|
+
journey.perform_next_step!
|
75
|
+
expect(journey).to be_finished
|
76
|
+
expect(journey.next_step_name).to be_nil
|
77
|
+
expect(journey.previous_step_name).to eq("step3")
|
78
|
+
|
79
|
+
expect(SideEffects).to be_produced("from_step1")
|
80
|
+
expect(SideEffects).to be_produced("from_step2")
|
81
|
+
expect(SideEffects).to be_produced("from_step3")
|
82
|
+
end
|
83
|
+
|
84
|
+
it "allows a journey consisting of multiple anonymous steps to be defined and performed to completion" do
|
85
|
+
AnonymousStepsJourney = Class.new(StepperMotor::Journey) do
|
86
|
+
3.times do |n|
|
87
|
+
step do
|
88
|
+
SideEffects.touch!("sidefx_#{n}")
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
journey = AnonymousStepsJourney.create!
|
94
|
+
expect(journey.next_step_name).to eq("step_1")
|
95
|
+
|
96
|
+
journey.perform_next_step!
|
97
|
+
expect(journey.next_step_name).to eq("step_2")
|
98
|
+
expect(journey.previous_step_name).to eq("step_1")
|
99
|
+
|
100
|
+
journey.perform_next_step!
|
101
|
+
expect(journey.next_step_name).to eq("step_3")
|
102
|
+
expect(journey.previous_step_name).to eq("step_2")
|
103
|
+
|
104
|
+
journey.perform_next_step!
|
105
|
+
expect(journey).to be_finished
|
106
|
+
expect(journey.next_step_name).to be_nil
|
107
|
+
expect(journey.previous_step_name).to eq("step_3")
|
108
|
+
|
109
|
+
expect(SideEffects).to be_produced("sidefx_0")
|
110
|
+
expect(SideEffects).to be_produced("sidefx_1")
|
111
|
+
expect(SideEffects).to be_produced("sidefx_2")
|
112
|
+
end
|
113
|
+
|
114
|
+
it "allows an arbitrary ActiveRecord to be attached as the hero" do
|
115
|
+
class SomeOtherJourney < StepperMotor::Journey
|
116
|
+
step do
|
117
|
+
# nothing, but we need to have a step so that the journey doesn't get destroyed immediately after creation
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
class CarryingJourney < StepperMotor::Journey
|
122
|
+
step :only do
|
123
|
+
raise "Incorrect" unless hero.instance_of?(SomeOtherJourney)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
hero = SomeOtherJourney.create!
|
128
|
+
journey = CarryingJourney.create!(hero: hero)
|
129
|
+
expect {
|
130
|
+
journey.perform_next_step!
|
131
|
+
}.not_to raise_error
|
132
|
+
end
|
133
|
+
|
134
|
+
it "allows a journey where steps are delayed in time using wait:" do
|
135
|
+
class TimelyJourney < StepperMotor::Journey
|
136
|
+
step wait: 10.hours do
|
137
|
+
SideEffects.touch! "after_10_hours.txt"
|
138
|
+
end
|
139
|
+
|
140
|
+
step wait: 5.minutes do
|
141
|
+
SideEffects.touch! "after_5_minutes.txt"
|
142
|
+
end
|
143
|
+
|
144
|
+
step do
|
145
|
+
SideEffects.touch! "final_nowait.txt"
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
freeze_time
|
150
|
+
TimelyJourney.create!
|
151
|
+
|
152
|
+
expect {
|
153
|
+
perform_enqueued_jobs
|
154
|
+
}.to not_have_produced_any_side_effects
|
155
|
+
|
156
|
+
travel 10.hours
|
157
|
+
expect {
|
158
|
+
perform_enqueued_jobs
|
159
|
+
}.to have_produced_side_effects_named("after_10_hours.txt")
|
160
|
+
|
161
|
+
travel 4.minutes
|
162
|
+
expect {
|
163
|
+
perform_enqueued_jobs
|
164
|
+
}.to not_have_produced_any_side_effects
|
165
|
+
|
166
|
+
travel 1.minutes
|
167
|
+
expect {
|
168
|
+
perform_enqueued_jobs
|
169
|
+
}.to have_produced_side_effects_named("after_5_minutes.txt")
|
170
|
+
|
171
|
+
expect {
|
172
|
+
perform_enqueued_jobs
|
173
|
+
}.to have_produced_side_effects_named("final_nowait.txt")
|
174
|
+
end
|
175
|
+
|
176
|
+
it "allows a journey where steps are delayed in time using after:" do
|
177
|
+
class TimelyJourneyUsingAfter < StepperMotor::Journey
|
178
|
+
step after: 10.hours do
|
179
|
+
SideEffects.touch! "step1"
|
180
|
+
end
|
181
|
+
|
182
|
+
step after: 605.minutes do
|
183
|
+
SideEffects.touch! "step2"
|
184
|
+
end
|
185
|
+
|
186
|
+
step do
|
187
|
+
SideEffects.touch! "step3"
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
TimelyJourneyUsingAfter.create!
|
192
|
+
freeze_time
|
193
|
+
expect { perform_enqueued_jobs }.to not_have_produced_any_side_effects
|
194
|
+
|
195
|
+
travel 10.hours
|
196
|
+
perform_enqueued_jobs
|
197
|
+
expect { perform_enqueued_jobs }.to have_produced_side_effects_named("step1")
|
198
|
+
|
199
|
+
travel 4.minutes
|
200
|
+
expect { perform_enqueued_jobs }.to not_have_produced_any_side_effects
|
201
|
+
|
202
|
+
travel 1.minutes
|
203
|
+
expect { perform_enqueued_jobs }.to have_produced_side_effects_named("step2")
|
204
|
+
expect { perform_enqueued_jobs }.to have_produced_side_effects_named("step3")
|
205
|
+
end
|
206
|
+
|
207
|
+
it "tracks steps entered and completed using counters" do
|
208
|
+
class FailingJourney < StepperMotor::Journey
|
209
|
+
step do
|
210
|
+
raise "oops"
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
class NotFailingJourney < StepperMotor::Journey
|
215
|
+
step do
|
216
|
+
true # no-op
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
failing_journey = FailingJourney.create!
|
221
|
+
expect { failing_journey.perform_next_step! }.to raise_error(/oops/)
|
222
|
+
expect(failing_journey.steps_entered).to eq(1)
|
223
|
+
expect(failing_journey.steps_completed).to eq(0)
|
224
|
+
|
225
|
+
failing_journey.ready!
|
226
|
+
expect { failing_journey.perform_next_step! }.to raise_error(/oops/)
|
227
|
+
expect(failing_journey.steps_entered).to eq(2)
|
228
|
+
expect(failing_journey.steps_completed).to eq(0)
|
229
|
+
|
230
|
+
non_failing_journey = NotFailingJourney.create!
|
231
|
+
non_failing_journey.perform_next_step!
|
232
|
+
expect(non_failing_journey.steps_entered).to eq(1)
|
233
|
+
expect(non_failing_journey.steps_completed).to eq(1)
|
234
|
+
end
|
235
|
+
|
236
|
+
it "does not allow invalid values for after: and wait:" do
|
237
|
+
expect {
|
238
|
+
class MisconfiguredJourney1 < StepperMotor::Journey
|
239
|
+
step after: 10.hours do
|
240
|
+
# pass
|
241
|
+
end
|
242
|
+
|
243
|
+
step after: 5.hours do
|
244
|
+
# pass
|
245
|
+
end
|
246
|
+
end
|
247
|
+
}.to raise_error(ArgumentError)
|
248
|
+
|
249
|
+
expect {
|
250
|
+
class MisconfiguredJourney2 < StepperMotor::Journey
|
251
|
+
step wait: -5.hours do
|
252
|
+
# pass
|
253
|
+
end
|
254
|
+
end
|
255
|
+
}.to raise_error(ArgumentError)
|
256
|
+
|
257
|
+
expect {
|
258
|
+
class MisconfiguredJourney3 < StepperMotor::Journey
|
259
|
+
step after: 5.hours, wait: 2.seconds do
|
260
|
+
# pass
|
261
|
+
end
|
262
|
+
end
|
263
|
+
}.to raise_error(ArgumentError)
|
264
|
+
end
|
265
|
+
|
266
|
+
it "allows a step to reattempt itself" do
|
267
|
+
class DeferringJourney < StepperMotor::Journey
|
268
|
+
step do
|
269
|
+
reattempt! wait: 5.minutes
|
270
|
+
raise "Should never be reached"
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
journey = DeferringJourney.create!
|
275
|
+
perform_enqueued_jobs
|
276
|
+
|
277
|
+
journey.reload
|
278
|
+
expect(journey.previous_step_name).to eq("step_1")
|
279
|
+
expect(journey.next_step_name).to eq("step_1")
|
280
|
+
expect(journey.next_step_to_be_performed_at).to be_within(1.second).of(Time.current + 5.minutes)
|
281
|
+
|
282
|
+
travel 5.minutes + 1.second
|
283
|
+
perform_enqueued_jobs
|
284
|
+
|
285
|
+
journey.reload
|
286
|
+
expect(journey.previous_step_name).to eq("step_1")
|
287
|
+
expect(journey.next_step_name).to eq("step_1")
|
288
|
+
expect(journey.next_step_to_be_performed_at).to be_within(1.second).of(Time.current + 5.minutes)
|
289
|
+
end
|
290
|
+
|
291
|
+
it "allows a journey consisting of multiple steps where the first step bails out to be defined and performed to the point of cancellation" do
|
292
|
+
class InterruptedJourney < StepperMotor::Journey
|
293
|
+
step :step1 do
|
294
|
+
SideEffects.touch!("step1_before_cancel")
|
295
|
+
cancel!
|
296
|
+
SideEffects.touch!("step1_after_cancel")
|
297
|
+
end
|
298
|
+
|
299
|
+
step :step2 do
|
300
|
+
raise "Should never be reached"
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
journey = InterruptedJourney.create!
|
305
|
+
expect(journey.next_step_name).to eq("step1")
|
306
|
+
|
307
|
+
perform_enqueued_jobs
|
308
|
+
expect(SideEffects).to be_produced("step1_before_cancel")
|
309
|
+
expect(SideEffects).not_to be_produced("step1_after_cancel")
|
310
|
+
assert_canceled_or_finished(journey)
|
311
|
+
end
|
312
|
+
|
313
|
+
it "forbids multiple similar journeys for the same hero at the same time unless allow_multiple is set" do
|
314
|
+
class SomeActor < StepperMotor::Journey
|
315
|
+
end
|
316
|
+
hero = SomeActor.create!
|
317
|
+
|
318
|
+
class ExclusiveJourney < StepperMotor::Journey
|
319
|
+
step do
|
320
|
+
raise "The step should never be entered as we are not testing the step itself here"
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
expect {
|
325
|
+
2.times { ExclusiveJourney.create! }
|
326
|
+
}.not_to raise_error
|
327
|
+
|
328
|
+
expect {
|
329
|
+
2.times { ExclusiveJourney.create!(hero: hero) }
|
330
|
+
}.to raise_error(ActiveRecord::RecordNotUnique)
|
331
|
+
|
332
|
+
expect {
|
333
|
+
2.times { ExclusiveJourney.create!(hero: hero, allow_multiple: true) }
|
334
|
+
}.not_to raise_error
|
335
|
+
end
|
336
|
+
|
337
|
+
it "forbids multiple steps with the same name within a journey" do
|
338
|
+
expect {
|
339
|
+
class RepeatedStepsJourney < StepperMotor::Journey
|
340
|
+
step :foo do
|
341
|
+
true
|
342
|
+
end
|
343
|
+
|
344
|
+
step "foo" do
|
345
|
+
true
|
346
|
+
end
|
347
|
+
end
|
348
|
+
}.to raise_error(ArgumentError)
|
349
|
+
end
|
350
|
+
|
351
|
+
it "finishes the journey after perform_next_step" do
|
352
|
+
class RapidlyFinishingJourney < StepperMotor::Journey
|
353
|
+
step :one do
|
354
|
+
true # no-op
|
355
|
+
end
|
356
|
+
step :two do
|
357
|
+
true # no-op
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
journey = RapidlyFinishingJourney.create!
|
362
|
+
expect(journey).to be_ready
|
363
|
+
journey.perform_next_step!
|
364
|
+
expect(journey).to be_ready
|
365
|
+
journey.perform_next_step!
|
366
|
+
expect(journey).to be_finished
|
367
|
+
end
|
368
|
+
|
369
|
+
it "does not enter next step on a finished journey" do
|
370
|
+
class NearInstantJourney < StepperMotor::Journey
|
371
|
+
step :one do
|
372
|
+
finished!
|
373
|
+
end
|
374
|
+
|
375
|
+
step :two do
|
376
|
+
raise "Should never be reache"
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
journey = NearInstantJourney.create!
|
381
|
+
expect(journey).to be_ready
|
382
|
+
journey.perform_next_step!
|
383
|
+
expect(journey).to be_finished
|
384
|
+
|
385
|
+
expect { journey.perform_next_step! }.not_to raise_error
|
386
|
+
end
|
387
|
+
|
388
|
+
it "raises an exception if a step changes the journey but does not save it" do
|
389
|
+
class MutatingJourney < StepperMotor::Journey
|
390
|
+
step :one do
|
391
|
+
self.state = "canceled"
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
journey = MutatingJourney.create!
|
396
|
+
expect {
|
397
|
+
journey.perform_next_step!
|
398
|
+
}.to raise_error(StepperMotor::JourneyNotPersisted)
|
399
|
+
end
|
400
|
+
|
401
|
+
it "resets the instance variables after performing a step" do
|
402
|
+
class SelfResettingJourney < StepperMotor::Journey
|
403
|
+
step :one do
|
404
|
+
raise unless @current_step_definition
|
405
|
+
end
|
406
|
+
|
407
|
+
step :two do
|
408
|
+
@reattempt_after = 2.minutes
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
412
|
+
journey = SelfResettingJourney.create!
|
413
|
+
expect { journey.perform_next_step! }.not_to raise_error
|
414
|
+
expect(journey.instance_variable_get(:@current_step_definition)).to be_nil
|
415
|
+
|
416
|
+
expect { journey.perform_next_step! }.not_to raise_error
|
417
|
+
expect(journey.instance_variable_get(:@current_step_definition)).to be_nil
|
418
|
+
expect(journey.instance_variable_get(:@reattempt_after)).to be_nil
|
419
|
+
end
|
420
|
+
|
421
|
+
def assert_canceled_or_finished(model)
|
422
|
+
model.reload
|
423
|
+
expect(model.state).to be_in(["canceled", "finished"])
|
424
|
+
end
|
425
|
+
end
|
426
|
+
# rubocop:enable Lint/ConstantDefinitionInBlock
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require_relative "../spec_helper"
|
2
|
+
|
3
|
+
RSpec.describe "StepperMotor::TestHelper" do
|
4
|
+
include SideEffects::SpecHelper
|
5
|
+
include StepperMotor::TestHelper
|
6
|
+
|
7
|
+
before do
|
8
|
+
establish_test_connection
|
9
|
+
run_generator
|
10
|
+
run_migrations
|
11
|
+
end
|
12
|
+
|
13
|
+
class SpeedyJourney < StepperMotor::Journey
|
14
|
+
step :step_1, wait: 40.minutes do
|
15
|
+
SideEffects.touch!("step_1")
|
16
|
+
end
|
17
|
+
|
18
|
+
step :step_2, wait: 2.days do
|
19
|
+
SideEffects.touch!("step_2")
|
20
|
+
end
|
21
|
+
|
22
|
+
step do
|
23
|
+
SideEffects.touch!("step_3")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
it "speedruns the journey despite waits being configured" do
|
28
|
+
journey = SpeedyJourney.create!
|
29
|
+
expect(journey).to be_ready
|
30
|
+
|
31
|
+
expect {
|
32
|
+
speedrun_journey(journey)
|
33
|
+
}.to have_produced_side_effects_named("step_1", "step_2", "step_3")
|
34
|
+
end
|
35
|
+
|
36
|
+
it "is able to perform a single step forcibly" do
|
37
|
+
journey = SpeedyJourney.create!
|
38
|
+
expect(journey).to be_ready
|
39
|
+
|
40
|
+
expect {
|
41
|
+
immediately_perform_single_step(journey, :step_2)
|
42
|
+
}.to have_produced_side_effects_named("step_2")
|
43
|
+
end
|
44
|
+
end
|
metadata
ADDED
@@ -0,0 +1,203 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: stepper_motor
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Julik Tarkhanov
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2025-01-01 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activerecord
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '6'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '6'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activejob
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: railties
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: globalid
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: sqlite3
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '1.4'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '1.4'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rake
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '13.0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '13.0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rspec
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '3.0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '3.0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: standard
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - '='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 1.28.5
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - '='
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 1.28.5
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: yard
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
description: Step workflows for Rails/ActiveRecord
|
140
|
+
email:
|
141
|
+
- me@julik.nl
|
142
|
+
executables: []
|
143
|
+
extensions: []
|
144
|
+
extra_rdoc_files: []
|
145
|
+
files:
|
146
|
+
- ".rspec"
|
147
|
+
- ".ruby-version"
|
148
|
+
- ".standard.yml"
|
149
|
+
- ".yardopts"
|
150
|
+
- CHANGELOG.md
|
151
|
+
- LICENSE.md
|
152
|
+
- README.md
|
153
|
+
- Rakefile
|
154
|
+
- bin/console
|
155
|
+
- bin/setup
|
156
|
+
- lib/generators/install_generator.rb
|
157
|
+
- lib/generators/stepper_motor_migration_001.rb.erb
|
158
|
+
- lib/stepper_motor.rb
|
159
|
+
- lib/stepper_motor/cyclic_scheduler.rb
|
160
|
+
- lib/stepper_motor/forward_scheduler.rb
|
161
|
+
- lib/stepper_motor/journey.rb
|
162
|
+
- lib/stepper_motor/perform_step_job.rb
|
163
|
+
- lib/stepper_motor/railtie.rb
|
164
|
+
- lib/stepper_motor/reap_hung_journeys_job.rb
|
165
|
+
- lib/stepper_motor/step.rb
|
166
|
+
- lib/stepper_motor/test_helper.rb
|
167
|
+
- lib/stepper_motor/version.rb
|
168
|
+
- sig/stepper_motor.rbs
|
169
|
+
- spec/helpers/side_effects.rb
|
170
|
+
- spec/spec_helper.rb
|
171
|
+
- spec/stepper_motor/cyclic_scheduler_spec.rb
|
172
|
+
- spec/stepper_motor/generator_spec.rb
|
173
|
+
- spec/stepper_motor/journey_spec.rb
|
174
|
+
- spec/stepper_motor/test_helper_spec.rb
|
175
|
+
- spec/stepper_motor_spec.rb
|
176
|
+
homepage: https://github.com/stepper_motor/stepper_motor
|
177
|
+
licenses:
|
178
|
+
- LGPL
|
179
|
+
metadata:
|
180
|
+
allowed_push_host: https://rubygems.org
|
181
|
+
homepage_uri: https://github.com/stepper_motor/stepper_motor
|
182
|
+
source_code_uri: https://github.com/stepper_motor/stepper_motor
|
183
|
+
changelog_uri: https://github.com/stepper_motor/stepper_motor/CHANGELOG.md
|
184
|
+
post_install_message:
|
185
|
+
rdoc_options: []
|
186
|
+
require_paths:
|
187
|
+
- lib
|
188
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
189
|
+
requirements:
|
190
|
+
- - ">="
|
191
|
+
- !ruby/object:Gem::Version
|
192
|
+
version: 2.7.0
|
193
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
194
|
+
requirements:
|
195
|
+
- - ">="
|
196
|
+
- !ruby/object:Gem::Version
|
197
|
+
version: '0'
|
198
|
+
requirements: []
|
199
|
+
rubygems_version: 3.1.6
|
200
|
+
signing_key:
|
201
|
+
specification_version: 4
|
202
|
+
summary: Effortless step workflows that embed nicely inside Rails
|
203
|
+
test_files: []
|