stepper_motor 0.1.2 → 0.1.3
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/README.md +5 -5
- data/lib/generators/stepper_motor_migration_001.rb.erb +2 -1
- data/lib/stepper_motor/forward_scheduler.rb +1 -3
- data/lib/stepper_motor/journey.rb +1 -1
- data/lib/stepper_motor/version.rb +1 -1
- data/spec/helpers/side_effects.rb +7 -0
- data/spec/spec_helper.rb +36 -0
- data/spec/stepper_motor/cyclic_scheduler_spec.rb +16 -15
- data/spec/stepper_motor/journey_spec.rb +53 -78
- data/spec/stepper_motor/test_helper_spec.rb +15 -13
- metadata +36 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2496495ef6f6748068d2a726432ef1e23c8c17880b464da61530457396342701
|
4
|
+
data.tar.gz: 003f7998b41ccf526038de029a0e839ebc9a413ddc7cf1fed47c2f2ec07e96e5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d522012c349187573966dacf65f90ecc476ccc1f34e20d818d7c4619b6804881491b9058361f16cf2eeb020a15b837645c76e2a951f8c5620dd3c9d6ba79e42f
|
7
|
+
data.tar.gz: 97a05abbd99ef244d5de94f37f50d10338e4618dd4b103a7efeffa5de4957e3e0dce854ebd8122f51b6e8c15d7a8735c108950df5f24adf6456f6136f767128e
|
data/README.md
CHANGED
@@ -16,7 +16,7 @@ class SignupJourney < StepperMotor::Journey
|
|
16
16
|
ServiceUpdateMailer.two_days_spent_email(hero).deliver_later
|
17
17
|
end
|
18
18
|
|
19
|
-
step :
|
19
|
+
step :onboarding_complete, wait: 15.days do
|
20
20
|
OnboardingCompleteMailer.onboarding_complete_email(hero).deliver_later
|
21
21
|
end
|
22
22
|
end
|
@@ -358,16 +358,16 @@ This creates a large number of jobs on your queue, but will be easier to manage.
|
|
358
358
|
```ruby
|
359
359
|
StepperMotor.configure do |c|
|
360
360
|
# Use jobs per journey step and enqueue them early
|
361
|
-
c.
|
361
|
+
c.scheduler = StepperMotor::ForwardScheduler.new
|
362
362
|
end
|
363
363
|
```
|
364
364
|
|
365
|
-
or, for
|
365
|
+
or, for cyclic scheduling (less jobs on the queue, but you need a decent scheduler for your background jobs to be present:
|
366
366
|
|
367
367
|
```ruby
|
368
368
|
StepperMotor.configure do |c|
|
369
|
-
#
|
370
|
-
c.
|
369
|
+
# Check for jobs to be created every 5 minutes
|
370
|
+
c.scheduler = StepperMotor::CyclicScheduler.new((cycle_duration: 5.minutes)
|
371
371
|
end
|
372
372
|
```
|
373
373
|
|
@@ -29,7 +29,8 @@ class StepperMotorMigration001 < ActiveRecord::Migration[<%= migration_version %
|
|
29
29
|
add_index :stepper_motor_journeys, [:type, :hero_type, :hero_id]
|
30
30
|
|
31
31
|
# A unique index prevents multiple journeys of the same type from being created for a particular hero
|
32
|
-
|
32
|
+
quoted_false = connection.quote(false)
|
33
|
+
add_index :stepper_motor_journeys, [:type, :hero_id, :hero_type], where: "allow_multiple = '#{quoted_false}' AND state IN ('ready', 'performing')", unique: true, name: :one_per_hero_index
|
33
34
|
|
34
35
|
# An index is also needed for cleaning up finished and canceled journeys quickly
|
35
36
|
# for a specific hero of a specific class
|
@@ -13,8 +13,6 @@
|
|
13
13
|
# this scheduler is not a good fit for you, and you will need to use the {CyclicScheduler} instead.
|
14
14
|
class StepperMotor::ForwardScheduler
|
15
15
|
def schedule(journey)
|
16
|
-
|
17
|
-
wait = 0 if wait.negative?
|
18
|
-
StepperMotor::PerformStepJob.set(wait: wait).perform_later(journey.to_global_id.to_s)
|
16
|
+
StepperMotor::PerformStepJob.set(scheduled_at: journey.next_step_to_be_performed_at).perform_later(journey.to_global_id.to_s)
|
19
17
|
end
|
20
18
|
end
|
@@ -44,7 +44,7 @@ module StepperMotor
|
|
44
44
|
belongs_to :hero, polymorphic: true, optional: true
|
45
45
|
|
46
46
|
STATES = %w[ready performing canceled finished]
|
47
|
-
enum state
|
47
|
+
enum :state, STATES.zip(STATES).to_h, default: "ready"
|
48
48
|
|
49
49
|
# Allows querying for journeys for this specific hero. This uses a scope for convenience as the hero
|
50
50
|
# is referenced using it's global ID (same ID that ActiveJob uses for serialization)
|
@@ -24,6 +24,13 @@ module SideEffects
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def self.touch!(name)
|
27
|
+
if Thread.current[:side_effects].nil?
|
28
|
+
raise <<~ERROR
|
29
|
+
The current thread locals do not contain :side_effects, which means that your job
|
30
|
+
is running on a different thread than the specs. This is probably due to bad configuration
|
31
|
+
of the ActiveJob test adapter.
|
32
|
+
ERROR
|
33
|
+
end
|
27
34
|
Thread.current[:side_effects][name.to_s] = true
|
28
35
|
end
|
29
36
|
|
data/spec/spec_helper.rb
CHANGED
@@ -6,6 +6,7 @@ require "active_job"
|
|
6
6
|
require "active_record"
|
7
7
|
require "globalid"
|
8
8
|
require_relative "helpers/side_effects"
|
9
|
+
require "fileutils"
|
9
10
|
|
10
11
|
module StepperMotorRailtieTestHelpers
|
11
12
|
def establish_test_connection
|
@@ -34,6 +35,33 @@ module StepperMotorRailtieTestHelpers
|
|
34
35
|
ActiveRecord::Tasks::DatabaseTasks.root = fake_app_root
|
35
36
|
ActiveRecord::Tasks::DatabaseTasks.migrate
|
36
37
|
end
|
38
|
+
extend self
|
39
|
+
end
|
40
|
+
|
41
|
+
module ActiveSupportTestCaseMethodsStub
|
42
|
+
def self.included(into)
|
43
|
+
into.before(:each) { before_setup }
|
44
|
+
into.after(:each) { after_teardown }
|
45
|
+
end
|
46
|
+
|
47
|
+
def before_setup
|
48
|
+
# Blank implementation as TestCase modules super() into it
|
49
|
+
end
|
50
|
+
|
51
|
+
def after_teardown
|
52
|
+
# Blank implementation as TestCase modules super() into it
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
module JourneyDefinitionHelper
|
57
|
+
def create_journey_subclass(&blk)
|
58
|
+
# https://stackoverflow.com/questions/4113479/dynamic-class-definition-with-a-class-name
|
59
|
+
random_component = Random.hex(8)
|
60
|
+
random_name = "JourneySubclass#{random_component}"
|
61
|
+
klass = Class.new(StepperMotor::Journey, &blk)
|
62
|
+
Object.const_set(random_name, klass)
|
63
|
+
klass
|
64
|
+
end
|
37
65
|
end
|
38
66
|
|
39
67
|
RSpec.configure do |config|
|
@@ -50,4 +78,12 @@ RSpec.configure do |config|
|
|
50
78
|
config.include ActiveSupport::Testing::TimeHelpers
|
51
79
|
config.include StepperMotorRailtieTestHelpers
|
52
80
|
config.include SideEffects::SpecHelper
|
81
|
+
config.include ActiveSupportTestCaseMethodsStub
|
82
|
+
config.include JourneyDefinitionHelper
|
83
|
+
|
84
|
+
config.before :suite do
|
85
|
+
StepperMotorRailtieTestHelpers.establish_test_connection
|
86
|
+
StepperMotorRailtieTestHelpers.run_generator
|
87
|
+
StepperMotorRailtieTestHelpers.run_migrations
|
88
|
+
end
|
53
89
|
end
|
@@ -3,17 +3,8 @@ require_relative "../spec_helper"
|
|
3
3
|
RSpec.describe "StepperMotor::CyclicScheduler" do
|
4
4
|
include ActiveJob::TestHelper
|
5
5
|
|
6
|
-
class FarFutureJourney < StepperMotor::Journey
|
7
|
-
step :do_thing, wait: 40.minutes do
|
8
|
-
raise "We do not test this so it should never run"
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
6
|
before do
|
13
7
|
@previous_scheduler = StepperMotor.scheduler
|
14
|
-
establish_test_connection
|
15
|
-
run_generator
|
16
|
-
run_migrations
|
17
8
|
StepperMotor::Journey.delete_all
|
18
9
|
end
|
19
10
|
|
@@ -21,12 +12,22 @@ RSpec.describe "StepperMotor::CyclicScheduler" do
|
|
21
12
|
StepperMotor.scheduler = @previous_scheduler
|
22
13
|
end
|
23
14
|
|
15
|
+
def far_future_journey_class
|
16
|
+
@klass ||= begin
|
17
|
+
create_journey_subclass do
|
18
|
+
step :do_thing, wait: 40.minutes do
|
19
|
+
raise "We do not test this so it should never run"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
24
25
|
it "does not schedule a journey which is too far in the future" do
|
25
26
|
scheduler = StepperMotor::CyclicScheduler.new(cycle_duration: 30.seconds)
|
26
27
|
StepperMotor.scheduler = scheduler
|
27
28
|
|
28
|
-
expect(scheduler).to receive(:schedule).with(instance_of(
|
29
|
-
_journey =
|
29
|
+
expect(scheduler).to receive(:schedule).with(instance_of(far_future_journey_class)).once.and_call_original
|
30
|
+
_journey = far_future_journey_class.create!
|
30
31
|
|
31
32
|
expect(scheduler).not_to receive(:schedule)
|
32
33
|
scheduler.run_scheduling_cycle
|
@@ -36,8 +37,8 @@ RSpec.describe "StepperMotor::CyclicScheduler" do
|
|
36
37
|
scheduler = StepperMotor::CyclicScheduler.new(cycle_duration: 40.minutes)
|
37
38
|
StepperMotor.scheduler = scheduler
|
38
39
|
|
39
|
-
expect(scheduler).to receive(:schedule).with(instance_of(
|
40
|
-
journey =
|
40
|
+
expect(scheduler).to receive(:schedule).with(instance_of(far_future_journey_class)).once.and_call_original
|
41
|
+
journey = far_future_journey_class.create!
|
41
42
|
|
42
43
|
expect(scheduler).to receive(:schedule).with(journey).and_call_original
|
43
44
|
scheduler.run_scheduling_cycle
|
@@ -47,8 +48,8 @@ RSpec.describe "StepperMotor::CyclicScheduler" do
|
|
47
48
|
scheduler = StepperMotor::CyclicScheduler.new(cycle_duration: 10.seconds)
|
48
49
|
StepperMotor.scheduler = scheduler
|
49
50
|
|
50
|
-
expect(scheduler).to receive(:schedule).with(instance_of(
|
51
|
-
journey =
|
51
|
+
expect(scheduler).to receive(:schedule).with(instance_of(far_future_journey_class)).once.and_call_original
|
52
|
+
journey = far_future_journey_class.create!
|
52
53
|
journey.update!(next_step_to_be_performed_at: 10.minutes.ago)
|
53
54
|
|
54
55
|
expect(scheduler).to receive(:schedule).with(journey).and_call_original
|
@@ -4,44 +4,21 @@ require_relative "../spec_helper"
|
|
4
4
|
RSpec.describe "StepperMotor::Journey" do
|
5
5
|
include ActiveJob::TestHelper
|
6
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
7
|
it "allows an empty journey to be defined and performed to completion" do
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
journey = PointlessJourney.create!
|
8
|
+
pointless_class = create_journey_subclass
|
9
|
+
journey = pointless_class.create!
|
33
10
|
journey.perform_next_step!
|
34
11
|
expect(journey).to be_finished
|
35
12
|
end
|
36
13
|
|
37
14
|
it "allows a journey consisting of one step to be defined and performed to completion" do
|
38
|
-
|
15
|
+
single_step_class = create_journey_subclass do
|
39
16
|
step :do_thing do
|
40
17
|
SideEffects.touch!("do_thing")
|
41
18
|
end
|
42
19
|
end
|
43
20
|
|
44
|
-
journey =
|
21
|
+
journey = single_step_class.create!
|
45
22
|
expect(journey.next_step_to_be_performed_at).not_to be_nil
|
46
23
|
journey.perform_next_step!
|
47
24
|
expect(journey).to be_finished
|
@@ -51,16 +28,15 @@ RSpec.describe "StepperMotor::Journey" do
|
|
51
28
|
it "allows a journey consisting of multiple named steps to be defined and performed to completion" do
|
52
29
|
step_names = [:step1, :step2, :step3]
|
53
30
|
|
54
|
-
|
55
|
-
|
56
|
-
step_names.each do |step_name|
|
31
|
+
multi_step_journey_class = create_journey_subclass do
|
32
|
+
[:step1, :step2, :step3].each do |step_name|
|
57
33
|
step step_name do
|
58
34
|
SideEffects.touch!("from_#{step_name}")
|
59
35
|
end
|
60
36
|
end
|
61
37
|
end
|
62
38
|
|
63
|
-
journey =
|
39
|
+
journey = multi_step_journey_class.create!
|
64
40
|
expect(journey.next_step_name).to eq("step1")
|
65
41
|
|
66
42
|
journey.perform_next_step!
|
@@ -82,7 +58,7 @@ RSpec.describe "StepperMotor::Journey" do
|
|
82
58
|
end
|
83
59
|
|
84
60
|
it "allows a journey consisting of multiple anonymous steps to be defined and performed to completion" do
|
85
|
-
|
61
|
+
anonymous_steps_class = create_journey_subclass do
|
86
62
|
3.times do |n|
|
87
63
|
step do
|
88
64
|
SideEffects.touch!("sidefx_#{n}")
|
@@ -90,7 +66,7 @@ RSpec.describe "StepperMotor::Journey" do
|
|
90
66
|
end
|
91
67
|
end
|
92
68
|
|
93
|
-
journey =
|
69
|
+
journey = anonymous_steps_class.create!
|
94
70
|
expect(journey.next_step_name).to eq("step_1")
|
95
71
|
|
96
72
|
journey.perform_next_step!
|
@@ -112,27 +88,22 @@ RSpec.describe "StepperMotor::Journey" do
|
|
112
88
|
end
|
113
89
|
|
114
90
|
it "allows an arbitrary ActiveRecord to be attached as the hero" do
|
115
|
-
|
116
|
-
|
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
|
91
|
+
carried_journey_class = create_journey_subclass
|
92
|
+
carrier_journey_class = create_journey_subclass do
|
122
93
|
step :only do
|
123
|
-
raise "Incorrect" unless hero.instance_of?(
|
94
|
+
raise "Incorrect" unless hero.instance_of?(carried_journey_class)
|
124
95
|
end
|
125
96
|
end
|
126
97
|
|
127
|
-
hero =
|
128
|
-
journey =
|
98
|
+
hero = carried_journey_class.create!
|
99
|
+
journey = carrier_journey_class.create!(hero: hero)
|
129
100
|
expect {
|
130
101
|
journey.perform_next_step!
|
131
102
|
}.not_to raise_error
|
132
103
|
end
|
133
104
|
|
134
105
|
it "allows a journey where steps are delayed in time using wait:" do
|
135
|
-
|
106
|
+
timely_journey_class = carrier_journey_class = create_journey_subclass do
|
136
107
|
step wait: 10.hours do
|
137
108
|
SideEffects.touch! "after_10_hours.txt"
|
138
109
|
end
|
@@ -147,7 +118,7 @@ RSpec.describe "StepperMotor::Journey" do
|
|
147
118
|
end
|
148
119
|
|
149
120
|
freeze_time
|
150
|
-
|
121
|
+
timely_journey_class.create!
|
151
122
|
|
152
123
|
expect {
|
153
124
|
perform_enqueued_jobs
|
@@ -174,7 +145,7 @@ RSpec.describe "StepperMotor::Journey" do
|
|
174
145
|
end
|
175
146
|
|
176
147
|
it "allows a journey where steps are delayed in time using after:" do
|
177
|
-
|
148
|
+
journey_class = create_journey_subclass do
|
178
149
|
step after: 10.hours do
|
179
150
|
SideEffects.touch! "step1"
|
180
151
|
end
|
@@ -188,36 +159,41 @@ RSpec.describe "StepperMotor::Journey" do
|
|
188
159
|
end
|
189
160
|
end
|
190
161
|
|
191
|
-
|
162
|
+
timely_journey = journey_class.create!
|
192
163
|
freeze_time
|
164
|
+
|
165
|
+
# Note that the "perform_enqueued_jobs" helper method performs the job even if
|
166
|
+
# its "scheduled_at" lies in the future. Presumably this is done so that testing is
|
167
|
+
# easier to do, but we check the time the journey was set to perform the next step at
|
168
|
+
# - and therefore a job which runs too early will produce another job that replaces it.
|
193
169
|
expect { perform_enqueued_jobs }.to not_have_produced_any_side_effects
|
194
170
|
|
195
|
-
|
196
|
-
perform_enqueued_jobs
|
171
|
+
travel_to(timely_journey.next_step_to_be_performed_at + 1.second)
|
197
172
|
expect { perform_enqueued_jobs }.to have_produced_side_effects_named("step1")
|
198
173
|
|
199
|
-
travel
|
174
|
+
travel(4.minutes)
|
200
175
|
expect { perform_enqueued_jobs }.to not_have_produced_any_side_effects
|
201
176
|
|
202
|
-
travel 1.
|
177
|
+
travel(1.minutes + 1.second)
|
203
178
|
expect { perform_enqueued_jobs }.to have_produced_side_effects_named("step2")
|
204
179
|
expect { perform_enqueued_jobs }.to have_produced_side_effects_named("step3")
|
180
|
+
expect(enqueued_jobs).to be_empty # Journey ended
|
205
181
|
end
|
206
182
|
|
207
183
|
it "tracks steps entered and completed using counters" do
|
208
|
-
|
184
|
+
failing = create_journey_subclass do
|
209
185
|
step do
|
210
186
|
raise "oops"
|
211
187
|
end
|
212
188
|
end
|
213
189
|
|
214
|
-
|
190
|
+
not_failing = create_journey_subclass do
|
215
191
|
step do
|
216
192
|
true # no-op
|
217
193
|
end
|
218
194
|
end
|
219
195
|
|
220
|
-
failing_journey =
|
196
|
+
failing_journey = failing.create!
|
221
197
|
expect { failing_journey.perform_next_step! }.to raise_error(/oops/)
|
222
198
|
expect(failing_journey.steps_entered).to eq(1)
|
223
199
|
expect(failing_journey.steps_completed).to eq(0)
|
@@ -227,7 +203,7 @@ RSpec.describe "StepperMotor::Journey" do
|
|
227
203
|
expect(failing_journey.steps_entered).to eq(2)
|
228
204
|
expect(failing_journey.steps_completed).to eq(0)
|
229
205
|
|
230
|
-
non_failing_journey =
|
206
|
+
non_failing_journey = not_failing.create!
|
231
207
|
non_failing_journey.perform_next_step!
|
232
208
|
expect(non_failing_journey.steps_entered).to eq(1)
|
233
209
|
expect(non_failing_journey.steps_completed).to eq(1)
|
@@ -235,7 +211,7 @@ RSpec.describe "StepperMotor::Journey" do
|
|
235
211
|
|
236
212
|
it "does not allow invalid values for after: and wait:" do
|
237
213
|
expect {
|
238
|
-
|
214
|
+
create_journey_subclass do
|
239
215
|
step after: 10.hours do
|
240
216
|
# pass
|
241
217
|
end
|
@@ -247,7 +223,7 @@ RSpec.describe "StepperMotor::Journey" do
|
|
247
223
|
}.to raise_error(ArgumentError)
|
248
224
|
|
249
225
|
expect {
|
250
|
-
|
226
|
+
create_journey_subclass do
|
251
227
|
step wait: -5.hours do
|
252
228
|
# pass
|
253
229
|
end
|
@@ -255,7 +231,7 @@ RSpec.describe "StepperMotor::Journey" do
|
|
255
231
|
}.to raise_error(ArgumentError)
|
256
232
|
|
257
233
|
expect {
|
258
|
-
|
234
|
+
create_journey_subclass do
|
259
235
|
step after: 5.hours, wait: 2.seconds do
|
260
236
|
# pass
|
261
237
|
end
|
@@ -264,14 +240,14 @@ RSpec.describe "StepperMotor::Journey" do
|
|
264
240
|
end
|
265
241
|
|
266
242
|
it "allows a step to reattempt itself" do
|
267
|
-
|
243
|
+
deferring = create_journey_subclass do
|
268
244
|
step do
|
269
245
|
reattempt! wait: 5.minutes
|
270
246
|
raise "Should never be reached"
|
271
247
|
end
|
272
248
|
end
|
273
249
|
|
274
|
-
journey =
|
250
|
+
journey = deferring.create!
|
275
251
|
perform_enqueued_jobs
|
276
252
|
|
277
253
|
journey.reload
|
@@ -289,7 +265,7 @@ RSpec.describe "StepperMotor::Journey" do
|
|
289
265
|
end
|
290
266
|
|
291
267
|
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
|
-
|
268
|
+
interrupting = create_journey_subclass do
|
293
269
|
step :step1 do
|
294
270
|
SideEffects.touch!("step1_before_cancel")
|
295
271
|
cancel!
|
@@ -301,7 +277,7 @@ RSpec.describe "StepperMotor::Journey" do
|
|
301
277
|
end
|
302
278
|
end
|
303
279
|
|
304
|
-
journey =
|
280
|
+
journey = interrupting.create!
|
305
281
|
expect(journey.next_step_name).to eq("step1")
|
306
282
|
|
307
283
|
perform_enqueued_jobs
|
@@ -311,32 +287,31 @@ RSpec.describe "StepperMotor::Journey" do
|
|
311
287
|
end
|
312
288
|
|
313
289
|
it "forbids multiple similar journeys for the same hero at the same time unless allow_multiple is set" do
|
314
|
-
|
315
|
-
|
316
|
-
hero = SomeActor.create!
|
290
|
+
actor_class = create_journey_subclass
|
291
|
+
hero = actor_class.create!
|
317
292
|
|
318
|
-
|
293
|
+
exclusive_journey_class = create_journey_subclass do
|
319
294
|
step do
|
320
295
|
raise "The step should never be entered as we are not testing the step itself here"
|
321
296
|
end
|
322
297
|
end
|
323
298
|
|
324
299
|
expect {
|
325
|
-
2.times {
|
300
|
+
2.times { exclusive_journey_class.create! }
|
326
301
|
}.not_to raise_error
|
327
302
|
|
328
303
|
expect {
|
329
|
-
2.times {
|
304
|
+
2.times { exclusive_journey_class.create!(hero: hero) }
|
330
305
|
}.to raise_error(ActiveRecord::RecordNotUnique)
|
331
306
|
|
332
307
|
expect {
|
333
|
-
2.times {
|
308
|
+
2.times { exclusive_journey_class.create!(hero: hero, allow_multiple: true) }
|
334
309
|
}.not_to raise_error
|
335
310
|
end
|
336
311
|
|
337
312
|
it "forbids multiple steps with the same name within a journey" do
|
338
313
|
expect {
|
339
|
-
|
314
|
+
create_journey_subclass do
|
340
315
|
step :foo do
|
341
316
|
true
|
342
317
|
end
|
@@ -349,7 +324,7 @@ RSpec.describe "StepperMotor::Journey" do
|
|
349
324
|
end
|
350
325
|
|
351
326
|
it "finishes the journey after perform_next_step" do
|
352
|
-
|
327
|
+
rapid = create_journey_subclass do
|
353
328
|
step :one do
|
354
329
|
true # no-op
|
355
330
|
end
|
@@ -358,7 +333,7 @@ RSpec.describe "StepperMotor::Journey" do
|
|
358
333
|
end
|
359
334
|
end
|
360
335
|
|
361
|
-
journey =
|
336
|
+
journey = rapid.create!
|
362
337
|
expect(journey).to be_ready
|
363
338
|
journey.perform_next_step!
|
364
339
|
expect(journey).to be_ready
|
@@ -367,7 +342,7 @@ RSpec.describe "StepperMotor::Journey" do
|
|
367
342
|
end
|
368
343
|
|
369
344
|
it "does not enter next step on a finished journey" do
|
370
|
-
|
345
|
+
near_instant = create_journey_subclass do
|
371
346
|
step :one do
|
372
347
|
finished!
|
373
348
|
end
|
@@ -377,7 +352,7 @@ RSpec.describe "StepperMotor::Journey" do
|
|
377
352
|
end
|
378
353
|
end
|
379
354
|
|
380
|
-
journey =
|
355
|
+
journey = near_instant.create!
|
381
356
|
expect(journey).to be_ready
|
382
357
|
journey.perform_next_step!
|
383
358
|
expect(journey).to be_finished
|
@@ -386,20 +361,20 @@ RSpec.describe "StepperMotor::Journey" do
|
|
386
361
|
end
|
387
362
|
|
388
363
|
it "raises an exception if a step changes the journey but does not save it" do
|
389
|
-
|
364
|
+
mutating = create_journey_subclass do
|
390
365
|
step :one do
|
391
366
|
self.state = "canceled"
|
392
367
|
end
|
393
368
|
end
|
394
369
|
|
395
|
-
journey =
|
370
|
+
journey = mutating.create!
|
396
371
|
expect {
|
397
372
|
journey.perform_next_step!
|
398
373
|
}.to raise_error(StepperMotor::JourneyNotPersisted)
|
399
374
|
end
|
400
375
|
|
401
376
|
it "resets the instance variables after performing a step" do
|
402
|
-
|
377
|
+
self_resetting = create_journey_subclass do
|
403
378
|
step :one do
|
404
379
|
raise unless @current_step_definition
|
405
380
|
end
|
@@ -409,7 +384,7 @@ RSpec.describe "StepperMotor::Journey" do
|
|
409
384
|
end
|
410
385
|
end
|
411
386
|
|
412
|
-
journey =
|
387
|
+
journey = self_resetting.create!
|
413
388
|
expect { journey.perform_next_step! }.not_to raise_error
|
414
389
|
expect(journey.instance_variable_get(:@current_step_definition)).to be_nil
|
415
390
|
|
@@ -10,22 +10,24 @@ RSpec.describe "StepperMotor::TestHelper" do
|
|
10
10
|
run_migrations
|
11
11
|
end
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
13
|
+
def speedy_journey_class
|
14
|
+
create_journey_subclass do
|
15
|
+
step :step_1, wait: 40.minutes do
|
16
|
+
SideEffects.touch!("step_1")
|
17
|
+
end
|
18
|
+
|
19
|
+
step :step_2, wait: 2.days do
|
20
|
+
SideEffects.touch!("step_2")
|
21
|
+
end
|
22
|
+
|
23
|
+
step do
|
24
|
+
SideEffects.touch!("step_3")
|
25
|
+
end
|
24
26
|
end
|
25
27
|
end
|
26
28
|
|
27
29
|
it "speedruns the journey despite waits being configured" do
|
28
|
-
journey =
|
30
|
+
journey = speedy_journey_class.create!
|
29
31
|
expect(journey).to be_ready
|
30
32
|
|
31
33
|
expect {
|
@@ -34,7 +36,7 @@ RSpec.describe "StepperMotor::TestHelper" do
|
|
34
36
|
end
|
35
37
|
|
36
38
|
it "is able to perform a single step forcibly" do
|
37
|
-
journey =
|
39
|
+
journey = speedy_journey_class.create!
|
38
40
|
expect(journey).to be_ready
|
39
41
|
|
40
42
|
expect {
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
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.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Julik Tarkhanov
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-02-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '7'
|
20
20
|
type: :runtime
|
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: '7'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: activejob
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -108,6 +108,20 @@ dependencies:
|
|
108
108
|
- - "~>"
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '3.0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rspec-rails
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
111
125
|
- !ruby/object:Gem::Dependency
|
112
126
|
name: standard
|
113
127
|
requirement: !ruby/object:Gem::Requirement
|
@@ -136,6 +150,20 @@ dependencies:
|
|
136
150
|
- - ">="
|
137
151
|
- !ruby/object:Gem::Version
|
138
152
|
version: '0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: redcarpet
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
139
167
|
description: Step workflows for Rails/ActiveRecord
|
140
168
|
email:
|
141
169
|
- me@julik.nl
|
@@ -173,14 +201,14 @@ files:
|
|
173
201
|
- spec/stepper_motor/journey_spec.rb
|
174
202
|
- spec/stepper_motor/test_helper_spec.rb
|
175
203
|
- spec/stepper_motor_spec.rb
|
176
|
-
homepage: https://
|
204
|
+
homepage: https://steppermotor.dev
|
177
205
|
licenses:
|
178
206
|
- LGPL
|
179
207
|
metadata:
|
180
208
|
allowed_push_host: https://rubygems.org
|
181
|
-
homepage_uri: https://
|
182
|
-
source_code_uri: https://github.com/
|
183
|
-
changelog_uri: https://github.com/
|
209
|
+
homepage_uri: https://steppermotor.dev
|
210
|
+
source_code_uri: https://github.com/stepper-motor/stepper_motor
|
211
|
+
changelog_uri: https://github.com/stepper-motor/stepper_motor/blob/main/CHANGELOG.md
|
184
212
|
post_install_message:
|
185
213
|
rdoc_options: []
|
186
214
|
require_paths:
|