stepper_motor 0.1.9 → 0.1.11

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.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +19 -6
  3. data/README.md +2 -4
  4. data/lib/generators/install_generator.rb +5 -5
  5. data/lib/generators/stepper_motor_migration_004.rb.erb +2 -2
  6. data/lib/stepper_motor/base_job.rb +5 -0
  7. data/lib/stepper_motor/cyclic_scheduler.rb +1 -1
  8. data/lib/stepper_motor/delete_completed_journeys_job.rb +16 -0
  9. data/lib/stepper_motor/forward_scheduler.rb +1 -1
  10. data/lib/stepper_motor/housekeeping_job.rb +8 -0
  11. data/lib/stepper_motor/perform_step_job.rb +25 -2
  12. data/lib/stepper_motor/{recover_stuck_journeys_job_v1.rb → recover_stuck_journeys_job.rb} +5 -4
  13. data/lib/stepper_motor/version.rb +1 -1
  14. data/lib/stepper_motor.rb +14 -2
  15. data/lib/tasks/stepper_motor_tasks.rake +1 -1
  16. data/manual/MANUAL.md +0 -11
  17. data/rbi/stepper_motor.rbi +48 -16
  18. data/sig/stepper_motor.rbs +40 -12
  19. data/stepper_motor.gemspec +1 -1
  20. data/test/dummy/config/initializers/stepper_motor.rb +39 -0
  21. data/test/dummy/db/migrate/{20250525132526_stepper_motor_migration_004.rb → 20250528141038_stepper_motor_migration_004.rb} +2 -2
  22. data/test/dummy/db/schema.rb +1 -1
  23. data/test/stepper_motor/completed_journeys_cleanup_test.rb +96 -0
  24. data/test/stepper_motor/configuration_test.rb +8 -0
  25. data/test/stepper_motor/cyclic_scheduler_test.rb +2 -2
  26. data/test/stepper_motor/forward_scheduler_test.rb +1 -1
  27. data/test/stepper_motor/housekeeping_job_test.rb +13 -0
  28. data/test/stepper_motor/perform_step_job_test.rb +60 -0
  29. data/test/stepper_motor/recover_stuck_journeys_job_test.rb +8 -3
  30. metadata +25 -10
  31. data/lib/stepper_motor/perform_step_job_v2.rb +0 -12
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: da2327efc5b8425041ad9cf331bb450dfd81049ac767daab8f8b445d0c518c53
4
- data.tar.gz: 02305e571a142384e3dce303c64637fa44029a14ca9ca5770f369b5c7c707ab2
3
+ metadata.gz: 619a3e8a9435e06f7fcf5b61ddc41d7ebb62450cb9fafccf89f8399818264ef0
4
+ data.tar.gz: 3c3009f6b5c48a8dcdcd92187dbadc21d92c37b86746b8426dba8b6b60cb007f
5
5
  SHA512:
6
- metadata.gz: ecb7dfe9e36759bf2b789d364fcc46399778487f352a634ac73822698b822b4ca1556818da958a480404f9710381e40a96a53310d65f2c77ba90b84ebd2664f7
7
- data.tar.gz: a8a7d73d008e2bf5286446e8aeb709310a2a475066c6101101131adf5346ca966ae45e8f0c38d98822771e1310b0de160284409b188fa98b5e0787fec2a6b865
6
+ metadata.gz: 1911106735b7ef6d54e7b3149cfbf26a13a6a0ec8e69a45429e244216c32f46b63b114b8101b369a36c22cdae10682a8c0adf599f18efe9bcfd2a9ded05be3ab
7
+ data.tar.gz: e9996b145db211444300230ae742e8da61f8db21f3841a21c97a37779203767f4fc46535656c698c494001bbe024c6cd681599595df5a73fdaae101417ebde4c
data/CHANGELOG.md CHANGED
@@ -1,16 +1,30 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.11] - 2025-06-08
4
+
5
+ - Add automatic cleanup of completed journeys after a configurable time period
6
+ - Add `HousekeepingJob` to run cleanup and recovery tasks
7
+ - Add ability to extend all StepperMotor jobs with custom configuration
8
+ - Merge V2/V1 job variants into single classes with backward compatibility
9
+ - Pin standardrb version to avoid Rubocop errors
10
+ - Improve documentation and test coverage
11
+
12
+ ## [0.1.10] - 2025-05-28
13
+
14
+ - Remove `algorithm: :concurrently` from migrations. If a user needs to conform with strong_migrations
15
+ they can always edit the migration themselves.
16
+
3
17
  ## [0.1.9] - 2025-05-25
4
18
 
5
19
  - Repair bodged migration from the previous release
6
20
 
7
21
  ## [0.1.8] - 2025-05-25
8
22
 
9
- - Add ability to pause and resume journeys (#24)
10
- - Add basic exception rescue during steps (#23)
11
- - Add idempotency keys when performing steps (#21)
12
- - Add support for blockless step definitions (#22)
13
- - Migrate from RSpec to Minitest (#19)
23
+ - Add ability to pause and resume journeys (https://github.com/stepper-motor/stepper_motor/pull/24)
24
+ - Add basic exception rescue during steps (https://github.com/stepper-motor/stepper_motor/pull/23)
25
+ - Add idempotency keys when performing steps (https://github.com/stepper-motor/stepper_motor/pull/21)
26
+ - Add support for blockless step definitions (https://github.com/stepper-motor/stepper_motor/pull/22)
27
+ - Migrate from RSpec to Minitest (https://github.com/stepper-motor/stepper_motor/pull/19)
14
28
  - Add Rake task for recovery
15
29
  - Add proper Rails engine tests
16
30
  - Improve test organization and coverage
@@ -41,7 +55,6 @@
41
55
  - Refactor PerformStepJob to use Journey class in job arguments
42
56
  - Remove GlobalID dependency
43
57
  - Add ability to resolve Journey from base class using `find()`
44
- - Prepare groundwork for future improvements (#12)
45
58
 
46
59
  ## [0.1.4] - 2025-03-11
47
60
 
data/README.md CHANGED
@@ -24,7 +24,7 @@ end
24
24
  SignupJourney.create!(hero: current_user)
25
25
  ```
26
26
 
27
- Want to know more? Dive into the [manual](file.MANUAL.html) we provide.
27
+ Want to know more? Dive into the [manual](/manual/MANUAL.md) we provide.
28
28
 
29
29
  ## Installation
30
30
 
@@ -47,9 +47,7 @@ Starting with versions 0.2.x and up, stepper_motor will make the best possible e
47
47
 
48
48
  ## Development
49
49
 
50
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
51
-
52
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
50
+ After checking out the repo, run `bundle` to install dependencies. The development process from there on is like any other gem.
53
51
 
54
52
  ## Is it any good?
55
53
 
@@ -9,8 +9,8 @@ module StepperMotor
9
9
  # Run it with `bin/rails g stepper_motor:install` in your console.
10
10
  class InstallGenerator < Rails::Generators::Base
11
11
  UUID_MESSAGE = <<~MSG
12
- If set, uuid type will be used for hero_id. Use this
13
- if most of your models use UUD as primary key"
12
+ If set, uuid type will be used for hero_id of the Journeys, as well as for the Journey IDs.
13
+ Use this if most of your models use UUD as primary key"
14
14
  MSG
15
15
 
16
16
  include ActiveRecord::Generators::Migration
@@ -31,9 +31,9 @@ module StepperMotor
31
31
  end
32
32
 
33
33
  def create_initializer
34
- create_file "config/initializers/stepper_motor.rb", <<~RUBY
35
- StepperMotor.scheduler = StepperMotor::ForwardScheduler.new
36
- RUBY
34
+ # Take the initializer code from the test dummy app, it is the best place really.
35
+ initializer_code_from_dummy_app = File.read(__dir__ + "/../../test/dummy/config/initializers/stepper_motor.rb")
36
+ create_file "config/initializers/stepper_motor.rb", initializer_code_from_dummy_app
37
37
  end
38
38
 
39
39
  private
@@ -7,7 +7,7 @@ class StepperMotorMigration004 < ActiveRecord::Migration[<%= migration_version %
7
7
  name: :idx_journeys_one_per_hero_with_paused
8
8
 
9
9
  # Remove old indexes that only include 'ready' state
10
- remove_index :stepper_motor_journeys, [:type, :hero_id, :hero_type], name: :one_per_hero_index, where: "allow_multiple = '0' AND state IN ('ready', 'performing')", algorithm: :concurrently
10
+ remove_index :stepper_motor_journeys, [:type, :hero_id, :hero_type], name: :one_per_hero_index, where: "allow_multiple = '0' AND state IN ('ready', 'performing')"
11
11
  end
12
12
 
13
13
  def down
@@ -19,6 +19,6 @@ class StepperMotorMigration004 < ActiveRecord::Migration[<%= migration_version %
19
19
  name: :one_per_hero_index
20
20
 
21
21
  # Remove new indexes
22
- remove_index :stepper_motor_journeys, name: :idx_journeys_one_per_hero_with_paused, algorithm: :concurrently
22
+ remove_index :stepper_motor_journeys, name: :idx_journeys_one_per_hero_with_paused
23
23
  end
24
24
  end
@@ -0,0 +1,5 @@
1
+ # All StepperMotor job classes inherit from this one. It is available for
2
+ # extension from StepperMotor.extend_base_job_class so that you can set
3
+ # priority, include and prepend modules and so forth.
4
+ class StepperMotor::BaseJob < ActiveJob::Base
5
+ end
@@ -21,7 +21,7 @@
21
21
  #
22
22
  # The scheduler needs to be configured in your cron table.
23
23
  class StepperMotor::CyclicScheduler < StepperMotor::ForwardScheduler
24
- class RunSchedulingCycleJob < ActiveJob::Base
24
+ class RunSchedulingCycleJob < StepperMotor::BaseJob
25
25
  def perform
26
26
  scheduler = StepperMotor.scheduler
27
27
  return unless scheduler.is_a?(StepperMotor::CyclicScheduler)
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The purpose of this job is to find journeys which have completed (finished or canceled) some
4
+ # time ago and to delete them. The time is configured in the initializer.
5
+ class StepperMotor::DeleteCompletedJourneysJob < StepperMotor::BaseJob
6
+ def perform(completed_for: StepperMotor.delete_completed_journeys_after, **)
7
+ return unless completed_for.present?
8
+
9
+ scope = StepperMotor::Journey.where(state: ["finished", "canceled"], updated_at: ..completed_for.ago)
10
+ scope.in_batches.each do |rel|
11
+ rel.delete_all
12
+ rescue => e
13
+ Rails.try(:error).try(:report, e)
14
+ end
15
+ end
16
+ end
@@ -19,7 +19,7 @@
19
19
  # scheduled to be performed). For good_job the {CyclicScheduler} is also likely to be a better option.
20
20
  class StepperMotor::ForwardScheduler
21
21
  def schedule(journey)
22
- StepperMotor::PerformStepJobV2
22
+ StepperMotor::PerformStepJob
23
23
  .set(wait_until: journey.next_step_to_be_performed_at)
24
24
  .perform_later(journey_id: journey.id, journey_class_name: journey.class.to_s, idempotency_key: journey.idempotency_key)
25
25
  end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ class StepperMotor::HousekeepingJob < StepperMotor::BaseJob
4
+ def perform(**)
5
+ StepperMotor::RecoverStuckJourneysJob.perform_later
6
+ StepperMotor::DeleteCompletedJourneysJob.perform_later
7
+ end
8
+ end
@@ -2,8 +2,18 @@
2
2
 
3
3
  require "active_job"
4
4
 
5
- class StepperMotor::PerformStepJob < ActiveJob::Base
6
- def perform(journey_gid)
5
+ class StepperMotor::PerformStepJob < StepperMotor::BaseJob
6
+ def perform(*posargs, **kwargs)
7
+ if posargs.length == 1 && kwargs.empty?
8
+ perform_via_journey_gid(*posargs)
9
+ else
10
+ perform_via_kwargs(**kwargs)
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def perform_via_journey_gid(journey_gid)
7
17
  # Pass the GlobalID instead of the record itself, so that we can rescue the non-existing record
8
18
  # exception here as opposed to the job deserialization
9
19
  journey = begin
@@ -13,4 +23,17 @@ class StepperMotor::PerformStepJob < ActiveJob::Base
13
23
  end
14
24
  journey.perform_next_step!
15
25
  end
26
+
27
+ def perform_via_kwargs(journey_id:, journey_class_name:, idempotency_key: nil, **)
28
+ journey = begin
29
+ StepperMotor::Journey.find(journey_id)
30
+ rescue ActiveRecord::RecordNotFound
31
+ # The journey has been canceled and destroyed previously or elsewhere
32
+ return
33
+ end
34
+ journey.perform_next_step!(idempotency_key: idempotency_key)
35
+ end
16
36
  end
37
+
38
+ # Alias for the previous job name
39
+ StepperMotor::PerformStepJobV2 = StepperMotor::PerformStepJob
@@ -1,19 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_job"
4
-
5
3
  # The purpose of this job is to find journeys which have, for whatever reason, remained in the
6
4
  # `performing` state for far longer than the journey is supposed to. At the moment it assumes
7
5
  # any journey that stayed in `performing` for longer than 1 hour has hung. Add this job to your
8
6
  # cron table and perform it regularly.
9
- class StepperMotor::RecoverStuckJourneysJobV1 < ActiveJob::Base
7
+ class StepperMotor::RecoverStuckJourneysJob < StepperMotor::BaseJob
10
8
  DEFAULT_STUCK_FOR = 2.days
11
9
 
12
10
  def perform(stuck_for: DEFAULT_STUCK_FOR)
13
11
  StepperMotor::Journey.stuck(stuck_for.ago).find_each do |journey|
14
12
  journey.recover!
15
13
  rescue => e
16
- Rails&.error&.report(e)
14
+ Rails.try(:error).try(:report, e)
17
15
  end
18
16
  end
19
17
  end
18
+
19
+ # Alias for the previous job name
20
+ StepperMotor::RecoverStuckJourneysJobV1 = StepperMotor::RecoverStuckJourneysJob
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module StepperMotor
4
- VERSION = "0.1.9"
4
+ VERSION = "0.1.11"
5
5
  end
data/lib/stepper_motor.rb CHANGED
@@ -13,13 +13,25 @@ module StepperMotor
13
13
 
14
14
  autoload :Journey, File.dirname(__FILE__) + "/stepper_motor/journey.rb"
15
15
  autoload :Step, File.dirname(__FILE__) + "/stepper_motor/step.rb"
16
+
17
+ autoload :BaseJob, File.dirname(__FILE__) + "/stepper_motor/base_job.rb"
16
18
  autoload :PerformStepJob, File.dirname(__FILE__) + "/stepper_motor/perform_step_job.rb"
17
- autoload :PerformStepJobV2, File.dirname(__FILE__) + "/stepper_motor/perform_step_job_v2.rb"
18
- autoload :RecoverStuckJourneysJobV1, File.dirname(__FILE__) + "/stepper_motor/recover_stuck_journeys_job_v1.rb"
19
+ autoload :PerformStepJobV2, File.dirname(__FILE__) + "/stepper_motor/perform_step_job.rb"
20
+ autoload :HousekeepingJob, File.dirname(__FILE__) + "/stepper_motor/housekeeping_job.rb"
21
+ autoload :DeleteCompletedJourneysJob, File.dirname(__FILE__) + "/stepper_motor/delete_completed_journeys_job.rb"
22
+ autoload :RecoverStuckJourneysJob, File.dirname(__FILE__) + "/stepper_motor/recover_stuck_journeys_job.rb"
23
+ autoload :RecoverStuckJourneysJobV1, File.dirname(__FILE__) + "/stepper_motor/recover_stuck_journeys_job.rb"
24
+
19
25
  autoload :InstallGenerator, File.dirname(__FILE__) + "/generators/install_generator.rb"
20
26
  autoload :ForwardScheduler, File.dirname(__FILE__) + "/stepper_motor/forward_scheduler.rb"
21
27
  autoload :CyclicScheduler, File.dirname(__FILE__) + "/stepper_motor/cyclic_scheduler.rb"
22
28
  autoload :TestHelper, File.dirname(__FILE__) + "/stepper_motor/test_helper.rb"
23
29
 
24
30
  mattr_accessor :scheduler, default: ForwardScheduler.new
31
+ mattr_accessor :delete_completed_journeys_after, default: 30.days
32
+
33
+ # Extends the BaseJob of the library with any additional options
34
+ def self.extend_base_job(&blk)
35
+ BaseJob.class_eval(&blk)
36
+ end
25
37
  end
@@ -3,6 +3,6 @@
3
3
  namespace :stepper_motor do
4
4
  desc "Recover all journeys hanging in the 'performing' state"
5
5
  task :recovery do
6
- StepperMotor::RecoverStuckJourneysJobV1.perform_now
6
+ StepperMotor::RecoverStuckJourneysJob.perform_now
7
7
  end
8
8
  end
data/manual/MANUAL.md CHANGED
@@ -1,14 +1,3 @@
1
-
2
- ## Installation
3
-
4
- Add the gem to the application's Gemfile, and then generate and run the migration
5
-
6
- $ bundle add stepper_motor
7
- $ bundle install
8
- $ bin/rails g stepper_motor:install --uuid # Pass "uuid" if you are using UUID for your primary and foreign keys
9
- $ bin/rails db:migrate
10
-
11
-
12
1
  ## Intro
13
2
 
14
3
  `stepper_motor` solves a real, tangible problem in Rails apps - tracking activities over long periods of time. It does so in a durable, reentrant and consistent manner, utilizing the guarantees provided by your relational database you already have.
@@ -2,7 +2,14 @@
2
2
  # StepperMotor is a module for building multi-step flows where steps are sequential and only
3
3
  # ever progress forward. The building block of StepperMotor is StepperMotor::Journey
4
4
  module StepperMotor
5
- VERSION = T.let("0.1.9", T.untyped)
5
+ VERSION = T.let("0.1.11", T.untyped)
6
+ PerformStepJobV2 = T.let(StepperMotor::PerformStepJob, T.untyped)
7
+ RecoverStuckJourneysJobV1 = T.let(StepperMotor::RecoverStuckJourneysJob, T.untyped)
8
+
9
+ # sord omit - no YARD return type given, using untyped
10
+ # Extends the BaseJob of the library with any additional options
11
+ sig { params(blk: T.untyped).returns(T.untyped) }
12
+ def self.extend_base_job(&blk); end
6
13
 
7
14
  class Error < StandardError
8
15
  end
@@ -304,6 +311,12 @@ module StepperMotor
304
311
  class Railtie < Rails::Railtie
305
312
  end
306
313
 
314
+ # All StepperMotor job classes inherit from this one. It is available for
315
+ # extension from StepperMotor.extend_base_job_class so that you can set
316
+ # priority, include and prepend modules and so forth.
317
+ class BaseJob < ActiveJob::Base
318
+ end
319
+
307
320
  module TestHelper
308
321
  # Allows running a given Journey to completion, skipping across the waiting periods.
309
322
  # This is useful to evaluate all side effects of a Journey. The helper will ensure
@@ -334,8 +347,8 @@ module StepperMotor
334
347
  class InstallGenerator < Rails::Generators::Base
335
348
  include ActiveRecord::Generators::Migration
336
349
  UUID_MESSAGE = T.let(<<~MSG, T.untyped)
337
- If set, uuid type will be used for hero_id. Use this
338
- if most of your models use UUD as primary key"
350
+ If set, uuid type will be used for hero_id of the Journeys, as well as for the Journey IDs.
351
+ Use this if most of your models use UUD as primary key"
339
352
  MSG
340
353
 
341
354
  # sord omit - no YARD return type given, using untyped
@@ -397,18 +410,37 @@ MSG
397
410
  sig { params(journey: T.untyped).returns(T.untyped) }
398
411
  def schedule(journey); end
399
412
 
400
- class RunSchedulingCycleJob < ActiveJob::Base
413
+ class RunSchedulingCycleJob < StepperMotor::BaseJob
401
414
  # sord omit - no YARD return type given, using untyped
402
415
  sig { returns(T.untyped) }
403
416
  def perform; end
404
417
  end
405
418
  end
406
419
 
407
- class PerformStepJob < ActiveJob::Base
420
+ class HousekeepingJob < StepperMotor::BaseJob
421
+ # sord omit - no YARD return type given, using untyped
422
+ sig { returns(T.untyped) }
423
+ def perform; end
424
+ end
425
+
426
+ class PerformStepJob < StepperMotor::BaseJob
427
+ # sord omit - no YARD type given for "*posargs", using untyped
428
+ # sord omit - no YARD type given for "**kwargs", using untyped
429
+ # sord omit - no YARD return type given, using untyped
430
+ sig { params(posargs: T.untyped, kwargs: T.untyped).returns(T.untyped) }
431
+ def perform(*posargs, **kwargs); end
432
+
408
433
  # sord omit - no YARD type given for "journey_gid", using untyped
409
434
  # sord omit - no YARD return type given, using untyped
410
435
  sig { params(journey_gid: T.untyped).returns(T.untyped) }
411
- def perform(journey_gid); end
436
+ def perform_via_journey_gid(journey_gid); end
437
+
438
+ # sord omit - no YARD type given for "journey_id:", using untyped
439
+ # sord omit - no YARD type given for "journey_class_name:", using untyped
440
+ # sord omit - no YARD type given for "idempotency_key:", using untyped
441
+ # sord omit - no YARD return type given, using untyped
442
+ sig { params(journey_id: T.untyped, journey_class_name: T.untyped, idempotency_key: T.untyped).returns(T.untyped) }
443
+ def perform_via_kwargs(journey_id:, journey_class_name:, idempotency_key: nil); end
412
444
  end
413
445
 
414
446
  # The forward scheduler enqueues a job for every Journey that
@@ -435,20 +467,11 @@ MSG
435
467
  def schedule(journey); end
436
468
  end
437
469
 
438
- class PerformStepJobV2 < ActiveJob::Base
439
- # sord omit - no YARD type given for "journey_id:", using untyped
440
- # sord omit - no YARD type given for "journey_class_name:", using untyped
441
- # sord omit - no YARD type given for "idempotency_key:", using untyped
442
- # sord omit - no YARD return type given, using untyped
443
- sig { params(journey_id: T.untyped, journey_class_name: T.untyped, idempotency_key: T.untyped).returns(T.untyped) }
444
- def perform(journey_id:, journey_class_name:, idempotency_key: nil); end
445
- end
446
-
447
470
  # The purpose of this job is to find journeys which have, for whatever reason, remained in the
448
471
  # `performing` state for far longer than the journey is supposed to. At the moment it assumes
449
472
  # any journey that stayed in `performing` for longer than 1 hour has hung. Add this job to your
450
473
  # cron table and perform it regularly.
451
- class RecoverStuckJourneysJobV1 < ActiveJob::Base
474
+ class RecoverStuckJourneysJob < StepperMotor::BaseJob
452
475
  DEFAULT_STUCK_FOR = T.let(2.days, T.untyped)
453
476
 
454
477
  # sord omit - no YARD type given for "stuck_for:", using untyped
@@ -456,4 +479,13 @@ MSG
456
479
  sig { params(stuck_for: T.untyped).returns(T.untyped) }
457
480
  def perform(stuck_for: DEFAULT_STUCK_FOR); end
458
481
  end
482
+
483
+ # The purpose of this job is to find journeys which have completed (finished or canceled) some
484
+ # time ago and to delete them. The time is configured in the initializer.
485
+ class DeleteCompletedJourneysJob < StepperMotor::BaseJob
486
+ # sord omit - no YARD type given for "completed_for:", using untyped
487
+ # sord omit - no YARD return type given, using untyped
488
+ sig { params(completed_for: T.untyped).returns(T.untyped) }
489
+ def perform(completed_for: StepperMotor.delete_completed_journeys_after); end
490
+ end
459
491
  end
@@ -2,6 +2,12 @@
2
2
  # ever progress forward. The building block of StepperMotor is StepperMotor::Journey
3
3
  module StepperMotor
4
4
  VERSION: untyped
5
+ PerformStepJobV2: untyped
6
+ RecoverStuckJourneysJobV1: untyped
7
+
8
+ # sord omit - no YARD return type given, using untyped
9
+ # Extends the BaseJob of the library with any additional options
10
+ def self.extend_base_job: () -> untyped
5
11
 
6
12
  class Error < StandardError
7
13
  end
@@ -269,6 +275,12 @@ module StepperMotor
269
275
  class Railtie < Rails::Railtie
270
276
  end
271
277
 
278
+ # All StepperMotor job classes inherit from this one. It is available for
279
+ # extension from StepperMotor.extend_base_job_class so that you can set
280
+ # priority, include and prepend modules and so forth.
281
+ class BaseJob < ActiveJob::Base
282
+ end
283
+
272
284
  module TestHelper
273
285
  # Allows running a given Journey to completion, skipping across the waiting periods.
274
286
  # This is useful to evaluate all side effects of a Journey. The helper will ensure
@@ -350,16 +362,32 @@ module StepperMotor
350
362
  # sord omit - no YARD return type given, using untyped
351
363
  def schedule: (untyped journey) -> untyped
352
364
 
353
- class RunSchedulingCycleJob < ActiveJob::Base
365
+ class RunSchedulingCycleJob < StepperMotor::BaseJob
354
366
  # sord omit - no YARD return type given, using untyped
355
367
  def perform: () -> untyped
356
368
  end
357
369
  end
358
370
 
359
- class PerformStepJob < ActiveJob::Base
371
+ class HousekeepingJob < StepperMotor::BaseJob
372
+ # sord omit - no YARD return type given, using untyped
373
+ def perform: () -> untyped
374
+ end
375
+
376
+ class PerformStepJob < StepperMotor::BaseJob
377
+ # sord omit - no YARD type given for "*posargs", using untyped
378
+ # sord omit - no YARD type given for "**kwargs", using untyped
379
+ # sord omit - no YARD return type given, using untyped
380
+ def perform: (*untyped posargs, **untyped kwargs) -> untyped
381
+
360
382
  # sord omit - no YARD type given for "journey_gid", using untyped
361
383
  # sord omit - no YARD return type given, using untyped
362
- def perform: (untyped journey_gid) -> untyped
384
+ def perform_via_journey_gid: (untyped journey_gid) -> untyped
385
+
386
+ # sord omit - no YARD type given for "journey_id:", using untyped
387
+ # sord omit - no YARD type given for "journey_class_name:", using untyped
388
+ # sord omit - no YARD type given for "idempotency_key:", using untyped
389
+ # sord omit - no YARD return type given, using untyped
390
+ def perform_via_kwargs: (journey_id: untyped, journey_class_name: untyped, ?idempotency_key: untyped) -> untyped
363
391
  end
364
392
 
365
393
  # The forward scheduler enqueues a job for every Journey that
@@ -385,23 +413,23 @@ module StepperMotor
385
413
  def schedule: (untyped journey) -> untyped
386
414
  end
387
415
 
388
- class PerformStepJobV2 < ActiveJob::Base
389
- # sord omit - no YARD type given for "journey_id:", using untyped
390
- # sord omit - no YARD type given for "journey_class_name:", using untyped
391
- # sord omit - no YARD type given for "idempotency_key:", using untyped
392
- # sord omit - no YARD return type given, using untyped
393
- def perform: (journey_id: untyped, journey_class_name: untyped, ?idempotency_key: untyped) -> untyped
394
- end
395
-
396
416
  # The purpose of this job is to find journeys which have, for whatever reason, remained in the
397
417
  # `performing` state for far longer than the journey is supposed to. At the moment it assumes
398
418
  # any journey that stayed in `performing` for longer than 1 hour has hung. Add this job to your
399
419
  # cron table and perform it regularly.
400
- class RecoverStuckJourneysJobV1 < ActiveJob::Base
420
+ class RecoverStuckJourneysJob < StepperMotor::BaseJob
401
421
  DEFAULT_STUCK_FOR: untyped
402
422
 
403
423
  # sord omit - no YARD type given for "stuck_for:", using untyped
404
424
  # sord omit - no YARD return type given, using untyped
405
425
  def perform: (?stuck_for: untyped) -> untyped
406
426
  end
427
+
428
+ # The purpose of this job is to find journeys which have completed (finished or canceled) some
429
+ # time ago and to delete them. The time is configured in the initializer.
430
+ class DeleteCompletedJourneysJob < StepperMotor::BaseJob
431
+ # sord omit - no YARD type given for "completed_for:", using untyped
432
+ # sord omit - no YARD return type given, using untyped
433
+ def perform: (?completed_for: untyped) -> untyped
434
+ end
407
435
  end
@@ -38,7 +38,7 @@ Gem::Specification.new do |spec|
38
38
  spec.add_development_dependency "rails", "~> 7.0"
39
39
  spec.add_development_dependency "sqlite3"
40
40
  spec.add_development_dependency "rake", "~> 13.0"
41
- spec.add_development_dependency "standard"
41
+ spec.add_development_dependency "standard", "~> 1.50.0", "< 2.0"
42
42
  spec.add_development_dependency "magic_frozen_string_literal"
43
43
  spec.add_development_dependency "yard"
44
44
  spec.add_development_dependency "redcarpet" # needed for the yard gem to enable Github Flavored Markdown
@@ -1 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Sets the scheduler. The ForwardScheduler will enqueue jobs for performing steps
4
+ # regardless of how far in the future a step needs to be taken. The CyclicScheduler
5
+ # will only enqueue jobs for steps which are to be performed soon. If you want to use
6
+ # the CyclicScheduler, you will need to configure it for the proper interval duration:
7
+ #
8
+ # StepperMotor.scheduler = StepperMotor::CyclicScheduler.new(cycle_duration: 30.minutes)
9
+ #
10
+ # and add its cycle job into your recurring jobs table. For example, for solid_queue:
11
+ #
12
+ # stepper_motor_houseleeping:
13
+ # schedule: "*/30 * * * *" # Every 30 minutes
14
+ # class: "StepperMotor::CyclicScheduler::RunSchedulingCycleJob"
15
+ #
16
+ # The cadence of the cyclic scheduler and the cadence of your cron job should be equal.
17
+ #
18
+ # If your queue is not susceptible to performance degradation with large numbers of
19
+ # "far future" jobs and allows scheduling "far ahead" - you can use the `ForwardScheduler`
20
+ # which is the default.
1
21
  StepperMotor.scheduler = StepperMotor::ForwardScheduler.new
22
+
23
+ # Sets the amount of time after which completed (finished and canceled)
24
+ # Journeys are going to be deleted from the database. If you want to keep
25
+ # them in the database indefinitely, set this parameter to `nil`.
26
+ # To perform the actual cleanups, add the `StepperMotor::HousekeepingJob` to your
27
+ # recurring jobs table. For example, for solid_queue:
28
+ #
29
+ # stepper_motor_housekeeping:
30
+ # schedule: "*/30 * * * *" # Every 30 minutes
31
+ # class: "StepperMotor::HousekeepingJob"
32
+ StepperMotor.delete_completed_journeys_after = 30.days
33
+
34
+ # Extends the base StepperMotor ActiveJob with any calls you would use to customise a
35
+ # job in your codebase. At the minimum, we recommend setting all StepperMotor job priorities
36
+ # to "high" - according to the priority denomination you are using.
37
+ # StepperMotor.extend_base_job do
38
+ # priority :high
39
+ # discard_on ActiveRecord::NotFound
40
+ # end
@@ -7,7 +7,7 @@ class StepperMotorMigration004 < ActiveRecord::Migration[7.2]
7
7
  name: :idx_journeys_one_per_hero_with_paused
8
8
 
9
9
  # Remove old indexes that only include 'ready' state
10
- remove_index :stepper_motor_journeys, [:type, :hero_id, :hero_type], name: :one_per_hero_index, where: "allow_multiple = '0' AND state IN ('ready', 'performing')", algorithm: :concurrently
10
+ remove_index :stepper_motor_journeys, [:type, :hero_id, :hero_type], name: :one_per_hero_index, where: "allow_multiple = '0' AND state IN ('ready', 'performing')"
11
11
  end
12
12
 
13
13
  def down
@@ -19,6 +19,6 @@ class StepperMotorMigration004 < ActiveRecord::Migration[7.2]
19
19
  name: :one_per_hero_index
20
20
 
21
21
  # Remove new indexes
22
- remove_index :stepper_motor_journeys, name: :idx_journeys_one_per_hero_with_paused, algorithm: :concurrently
22
+ remove_index :stepper_motor_journeys, name: :idx_journeys_one_per_hero_with_paused
23
23
  end
24
24
  end
@@ -10,7 +10,7 @@
10
10
  #
11
11
  # It's strongly recommended that you check this file into your version control system.
12
12
 
13
- ActiveRecord::Schema[7.2].define(version: 2025_05_25_132526) do
13
+ ActiveRecord::Schema[7.2].define(version: 2025_05_28_141038) do
14
14
  create_table "stepper_motor_journeys", force: :cascade do |t|
15
15
  t.string "type", null: false
16
16
  t.string "state", default: "ready"
@@ -0,0 +1,96 @@
1
+ require "test_helper"
2
+
3
+ class CompletedJourneysCleanupTest < ActiveSupport::TestCase
4
+ include SideEffects::TestHelper
5
+
6
+ test "defines a variable on StepperMotor and sets it to a default value" do
7
+ assert StepperMotor.delete_completed_journeys_after
8
+ assert_equal 30.days, StepperMotor.delete_completed_journeys_after
9
+ end
10
+
11
+ test "cleans up finished journeys" do
12
+ previous_setting = StepperMotor.delete_completed_journeys_after
13
+
14
+ journey_class = create_journey_subclass do
15
+ step wait: 20.minutes do
16
+ SideEffects.touch! :a_step
17
+ end
18
+ end
19
+ journey = journey_class.create!
20
+ another_journey = journey_class.create!
21
+
22
+ assert_no_changes "StepperMotor::Journey.count" do
23
+ StepperMotor::DeleteCompletedJourneysJob.new.perform
24
+ end
25
+
26
+ travel_to Time.current + 20.minutes + 1.second
27
+ journey.perform_next_step!
28
+
29
+ assert SideEffects.produced?(:a_step)
30
+ assert journey.finished?
31
+
32
+ assert_no_changes "StepperMotor::Journey.count" do
33
+ StepperMotor::DeleteCompletedJourneysJob.new.perform
34
+ end
35
+
36
+ StepperMotor.delete_completed_journeys_after = 7.minutes
37
+ travel_to Time.current + 7.minutes + 1.second
38
+
39
+ assert_changes "StepperMotor::Journey.count", -1 do
40
+ StepperMotor::DeleteCompletedJourneysJob.new.perform
41
+ end
42
+
43
+ assert_raises(ActiveRecord::RecordNotFound) { journey.reload }
44
+ assert_nothing_raised { another_journey.reload }
45
+ ensure
46
+ StepperMotor.delete_completed_journeys_after = previous_setting
47
+ end
48
+
49
+ test "cleans up canceled journeys" do
50
+ previous_setting = StepperMotor.delete_completed_journeys_after
51
+
52
+ journey_class = create_journey_subclass do
53
+ step wait: 20.minutes do
54
+ # noop
55
+ end
56
+ end
57
+ journey = journey_class.create!
58
+ journey.cancel!
59
+
60
+ assert_no_changes "StepperMotor::Journey.count" do
61
+ StepperMotor::DeleteCompletedJourneysJob.new.perform
62
+ end
63
+
64
+ StepperMotor.delete_completed_journeys_after = 7.minutes
65
+ travel_to Time.current + 7.minutes + 1.second
66
+
67
+ assert_changes "StepperMotor::Journey.count", -1 do
68
+ StepperMotor::DeleteCompletedJourneysJob.new.perform
69
+ end
70
+
71
+ assert_raises(ActiveRecord::RecordNotFound) { journey.reload }
72
+ ensure
73
+ StepperMotor.delete_completed_journeys_after = previous_setting
74
+ end
75
+
76
+ test "does not delete any journeys if the setting is set to nil" do
77
+ previous_setting = StepperMotor.delete_completed_journeys_after
78
+ StepperMotor.delete_completed_journeys_after = nil
79
+
80
+ journey_class = create_journey_subclass do
81
+ step wait: 20.minutes do
82
+ # noop
83
+ end
84
+ end
85
+ journey = journey_class.create!
86
+ journey.cancel!
87
+
88
+ travel_to Time.current + 365.days
89
+ assert_no_changes "StepperMotor::Journey.count" do
90
+ StepperMotor::DeleteCompletedJourneysJob.new.perform
91
+ end
92
+ assert_nothing_raised { journey.reload }
93
+ ensure
94
+ StepperMotor.delete_completed_journeys_after = previous_setting
95
+ end
96
+ end
@@ -0,0 +1,8 @@
1
+ require "test_helper"
2
+
3
+ class ConfigurationTest < ActiveSupport::TestCase
4
+ test "allows extending the base job" do
5
+ retrieved_name = StepperMotor.extend_base_job { name }
6
+ assert_equal "StepperMotor::BaseJob", retrieved_name
7
+ end
8
+ end
@@ -39,7 +39,7 @@ class CyclicSchedulerTest < ActiveSupport::TestCase
39
39
  scheduler = StepperMotor::CyclicScheduler.new(cycle_duration: 40.minutes)
40
40
  StepperMotor.scheduler = scheduler
41
41
 
42
- assert_enqueued_jobs 1, only: StepperMotor::PerformStepJobV2 do
42
+ assert_enqueued_jobs 1, only: StepperMotor::PerformStepJob do
43
43
  far_future_journey_class.create!
44
44
  end
45
45
  end
@@ -54,7 +54,7 @@ class CyclicSchedulerTest < ActiveSupport::TestCase
54
54
  end
55
55
  journey.update!(next_step_to_be_performed_at: 10.minutes.ago)
56
56
 
57
- assert_enqueued_jobs 1, only: StepperMotor::PerformStepJobV2 do
57
+ assert_enqueued_jobs 1, only: StepperMotor::PerformStepJob do
58
58
  scheduler.run_scheduling_cycle
59
59
  end
60
60
  end
@@ -31,7 +31,7 @@ class ForwardSchedulerTest < ActiveSupport::TestCase
31
31
  assert_equal 1, enqueued_jobs.size
32
32
  job = enqueued_jobs.first
33
33
 
34
- assert_equal "StepperMotor::PerformStepJobV2", job["job_class"]
34
+ assert_equal "StepperMotor::PerformStepJob", job["job_class"]
35
35
  assert_not_nil job["scheduled_at"]
36
36
 
37
37
  scheduled_at = Time.parse(job["scheduled_at"])
@@ -0,0 +1,13 @@
1
+ require "test_helper"
2
+
3
+ class HousekeepingJobTest < ActiveSupport::TestCase
4
+ include ActiveJob::TestHelper
5
+
6
+ test "runs without exceptions and enqueues the two actual jobs" do
7
+ assert_nothing_raised do
8
+ StepperMotor::HousekeepingJob.perform_now
9
+ end
10
+
11
+ assert_enqueued_jobs 2
12
+ end
13
+ end
@@ -0,0 +1,60 @@
1
+ require "test_helper"
2
+
3
+ class PerformStepJobTest < ActiveSupport::TestCase
4
+ include SideEffects::TestHelper
5
+
6
+ test "exposes the V2 variant in a constant to allow old jobs to be unserialized" do
7
+ assert defined?(StepperMotor::PerformStepJobV2)
8
+ assert_equal StepperMotor::PerformStepJob, StepperMotor::PerformStepJobV2
9
+ end
10
+
11
+ test "allows perform() with a GlobalID as argument" do
12
+ journey = create_journey_subclass do
13
+ step do
14
+ # noop
15
+ end
16
+ end.create!
17
+
18
+ assert_nothing_raised { StepperMotor::PerformStepJob.new.perform(journey.to_global_id) }
19
+ assert journey.reload.finished?
20
+ end
21
+
22
+ test "allows perform() with the journey class name and ID" do
23
+ journey = create_journey_subclass do
24
+ step do
25
+ # noop
26
+ end
27
+ end.create!
28
+
29
+ assert_nothing_raised do
30
+ StepperMotor::PerformStepJob.new.perform(journey_id: journey.id, journey_class_name: journey.class.name)
31
+ end
32
+ assert journey.reload.finished?
33
+ end
34
+
35
+ test "allows perform() with the journey class name, ID and idempotency key" do
36
+ journey = create_journey_subclass do
37
+ step do
38
+ # noop
39
+ end
40
+ end.create!
41
+
42
+ assert_nothing_raised do
43
+ StepperMotor::PerformStepJob.new.perform(journey_id: journey.id, journey_class_name: journey.class.name, idempotency_key: journey.idempotency_key)
44
+ end
45
+ assert journey.reload.finished?
46
+ end
47
+
48
+ test "skips without exceptions if the idempotency key is incorrect" do
49
+ journey = create_journey_subclass do
50
+ step do
51
+ # noop
52
+ end
53
+ end.create!
54
+
55
+ assert_nothing_raised do
56
+ StepperMotor::PerformStepJob.new.perform(journey_id: journey.id, journey_class_name: journey.class.name, idempotency_key: "wrong")
57
+ end
58
+ assert journey.reload.ready?
59
+ end
60
+ end
@@ -7,6 +7,11 @@ class RecoverStuckJourneysJobTest < ActiveSupport::TestCase
7
7
  StepperMotor::Journey.delete_all
8
8
  end
9
9
 
10
+ test "still has the previous job class name available to allow older jobs to be unserialized" do
11
+ assert defined?(StepperMotor::RecoverStuckJourneysJobV1)
12
+ assert_equal StepperMotor::RecoverStuckJourneysJob, StepperMotor::RecoverStuckJourneysJobV1
13
+ end
14
+
10
15
  test "handles recovery from a background job" do
11
16
  stuck_journey_class1 = create_journey_subclass do
12
17
  self.when_stuck = :cancel
@@ -52,12 +57,12 @@ class RecoverStuckJourneysJobTest < ActiveSupport::TestCase
52
57
  assert journey_to_cancel.reload.performing?
53
58
  assert journey_to_reattempt.reload.performing?
54
59
 
55
- StepperMotor::RecoverStuckJourneysJobV1.perform_now(stuck_for: 2.days)
60
+ StepperMotor::RecoverStuckJourneysJob.perform_now(stuck_for: 2.days)
56
61
  assert journey_to_cancel.reload.performing?
57
62
  assert journey_to_reattempt.reload.performing?
58
63
 
59
64
  travel_to Time.now + 2.days + 1.second
60
- StepperMotor::RecoverStuckJourneysJobV1.perform_now(stuck_for: 2.days)
65
+ StepperMotor::RecoverStuckJourneysJob.perform_now(stuck_for: 2.days)
61
66
 
62
67
  assert journey_to_cancel.reload.canceled?
63
68
  assert journey_to_reattempt.reload.ready?
@@ -83,7 +88,7 @@ class RecoverStuckJourneysJobTest < ActiveSupport::TestCase
83
88
  journey_to_cancel.class.update_all(type: "UnknownJourneySubclass")
84
89
 
85
90
  assert_nothing_raised do
86
- StepperMotor::RecoverStuckJourneysJobV1.perform_now(stuck_for: 2.days)
91
+ StepperMotor::RecoverStuckJourneysJob.perform_now(stuck_for: 2.days)
87
92
  end
88
93
  end
89
94
  end
metadata CHANGED
@@ -1,13 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stepper_motor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.9
4
+ version: 0.1.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julik Tarkhanov
8
+ autorequire:
8
9
  bindir: exe
9
10
  cert_chain: []
10
- date: 2025-05-25 00:00:00.000000000 Z
11
+ date: 2025-06-08 00:00:00.000000000 Z
11
12
  dependencies:
12
13
  - !ruby/object:Gem::Dependency
13
14
  name: activerecord
@@ -111,16 +112,22 @@ dependencies:
111
112
  name: standard
112
113
  requirement: !ruby/object:Gem::Requirement
113
114
  requirements:
114
- - - ">="
115
+ - - "~>"
115
116
  - !ruby/object:Gem::Version
116
- version: '0'
117
+ version: 1.50.0
118
+ - - "<"
119
+ - !ruby/object:Gem::Version
120
+ version: '2.0'
117
121
  type: :development
118
122
  prerelease: false
119
123
  version_requirements: !ruby/object:Gem::Requirement
120
124
  requirements:
121
- - - ">="
125
+ - - "~>"
122
126
  - !ruby/object:Gem::Version
123
- version: '0'
127
+ version: 1.50.0
128
+ - - "<"
129
+ - !ruby/object:Gem::Version
130
+ version: '2.0'
124
131
  - !ruby/object:Gem::Dependency
125
132
  name: magic_frozen_string_literal
126
133
  requirement: !ruby/object:Gem::Requirement
@@ -200,15 +207,17 @@ files:
200
207
  - lib/generators/stepper_motor_migration_003.rb.erb
201
208
  - lib/generators/stepper_motor_migration_004.rb.erb
202
209
  - lib/stepper_motor.rb
210
+ - lib/stepper_motor/base_job.rb
203
211
  - lib/stepper_motor/cyclic_scheduler.rb
212
+ - lib/stepper_motor/delete_completed_journeys_job.rb
204
213
  - lib/stepper_motor/forward_scheduler.rb
214
+ - lib/stepper_motor/housekeeping_job.rb
205
215
  - lib/stepper_motor/journey.rb
206
216
  - lib/stepper_motor/journey/flow_control.rb
207
217
  - lib/stepper_motor/journey/recovery.rb
208
218
  - lib/stepper_motor/perform_step_job.rb
209
- - lib/stepper_motor/perform_step_job_v2.rb
210
219
  - lib/stepper_motor/railtie.rb
211
- - lib/stepper_motor/recover_stuck_journeys_job_v1.rb
220
+ - lib/stepper_motor/recover_stuck_journeys_job.rb
212
221
  - lib/stepper_motor/step.rb
213
222
  - lib/stepper_motor/test_helper.rb
214
223
  - lib/stepper_motor/version.rb
@@ -253,7 +262,7 @@ files:
253
262
  - test/dummy/db/migrate/20250525132523_stepper_motor_migration_001.rb
254
263
  - test/dummy/db/migrate/20250525132524_stepper_motor_migration_002.rb
255
264
  - test/dummy/db/migrate/20250525132525_stepper_motor_migration_003.rb
256
- - test/dummy/db/migrate/20250525132526_stepper_motor_migration_004.rb
265
+ - test/dummy/db/migrate/20250528141038_stepper_motor_migration_004.rb
257
266
  - test/dummy/db/schema.rb
258
267
  - test/dummy/public/400.html
259
268
  - test/dummy/public/404.html
@@ -263,14 +272,18 @@ files:
263
272
  - test/dummy/public/icon.png
264
273
  - test/dummy/public/icon.svg
265
274
  - test/side_effects_helper.rb
275
+ - test/stepper_motor/completed_journeys_cleanup_test.rb
276
+ - test/stepper_motor/configuration_test.rb
266
277
  - test/stepper_motor/cyclic_scheduler_test.rb
267
278
  - test/stepper_motor/forward_scheduler_test.rb
279
+ - test/stepper_motor/housekeeping_job_test.rb
268
280
  - test/stepper_motor/journey/exception_handling_test.rb
269
281
  - test/stepper_motor/journey/flow_control_test.rb
270
282
  - test/stepper_motor/journey/idempotency_test.rb
271
283
  - test/stepper_motor/journey/step_definition_test.rb
272
284
  - test/stepper_motor/journey/uniqueness_test.rb
273
285
  - test/stepper_motor/journey_test.rb
286
+ - test/stepper_motor/perform_step_job_test.rb
274
287
  - test/stepper_motor/recover_stuck_journeys_job_test.rb
275
288
  - test/stepper_motor/recovery_test.rb
276
289
  - test/stepper_motor/test_helper_test.rb
@@ -284,6 +297,7 @@ metadata:
284
297
  homepage_uri: https://steppermotor.dev
285
298
  source_code_uri: https://github.com/stepper-motor/stepper_motor
286
299
  changelog_uri: https://github.com/stepper-motor/stepper_motor/blob/main/CHANGELOG.md
300
+ post_install_message:
287
301
  rdoc_options: []
288
302
  require_paths:
289
303
  - lib
@@ -298,7 +312,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
298
312
  - !ruby/object:Gem::Version
299
313
  version: '0'
300
314
  requirements: []
301
- rubygems_version: 3.6.6
315
+ rubygems_version: 3.4.10
316
+ signing_key:
302
317
  specification_version: 4
303
318
  summary: Effortless step workflows that embed nicely inside Rails
304
319
  test_files: []
@@ -1,12 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "active_job"
4
-
5
- class StepperMotor::PerformStepJobV2 < ActiveJob::Base
6
- def perform(journey_id:, journey_class_name:, idempotency_key: nil, **)
7
- journey = StepperMotor::Journey.find(journey_id)
8
- journey.perform_next_step!(idempotency_key: idempotency_key)
9
- rescue ActiveRecord::RecordNotFound
10
- # The journey has been canceled and destroyed previously or elsewhere
11
- end
12
- end