stepper_motor 0.1.12 → 0.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +50 -8
- data/CHANGELOG.md +17 -0
- data/lib/generators/stepper_motor_migration_005.rb.erb +58 -0
- data/lib/stepper_motor/journey/flow_control.rb +49 -0
- data/lib/stepper_motor/journey.rb +53 -10
- data/lib/stepper_motor/step.rb +38 -8
- data/lib/stepper_motor/version.rb +1 -1
- data/manual/MANUAL.md +352 -90
- data/rbi/stepper_motor.rbi +48 -6
- data/sig/stepper_motor.rbs +44 -5
- data/stepper_motor.gemspec +2 -0
- data/test/dummy/config/database.mysql2.yml +14 -0
- data/test/dummy/config/database.postgres.yml +14 -0
- data/test/dummy/config/database.sqlite3.yml +32 -0
- data/test/dummy/config/initializers/stepper_motor.rb +1 -1
- data/test/dummy/db/migrate/20250609221201_stepper_motor_migration_005.rb +58 -0
- data/test/dummy/db/schema.rb +7 -6
- data/test/stepper_motor/journey/exception_handling_test.rb +57 -0
- data/test/stepper_motor/journey/flow_control_test.rb +164 -0
- data/test/stepper_motor/journey/if_condition_test.rb +355 -0
- data/test/stepper_motor/journey/step_definition_test.rb +1 -0
- metadata +37 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4b94d7f405351309bd1fbda83b352031795cbee87aad100d412d847e3d03543c
|
4
|
+
data.tar.gz: 59ecde85caacac0c7e65f6a0c527676bac4700b84fa39ce55443e81592023738
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5305f1f98eb8b8c630e45309280fae524ec458d15a75362e647a36e22fd29ca0fe1a7ecbbba186ed60f33b64b7b6b875e66878280e21a6a3196473652b82f098
|
7
|
+
data.tar.gz: 2e6db70cf8ad6b844c4d7a38eab6f087f785b1a112f446844e25d6d943bffd3f3b71da6bd7baa69c7249a7978a5538d2572235b3f90475b01af8be0d07074cfd
|
data/.github/workflows/ci.yml
CHANGED
@@ -7,6 +7,7 @@ on:
|
|
7
7
|
|
8
8
|
jobs:
|
9
9
|
lint:
|
10
|
+
name: "Lint (standardrb)"
|
10
11
|
runs-on: ubuntu-latest
|
11
12
|
steps:
|
12
13
|
- name: Checkout code
|
@@ -22,17 +23,45 @@ jobs:
|
|
22
23
|
run: bundle exec standardrb
|
23
24
|
|
24
25
|
test:
|
26
|
+
name: "Tests (${{ matrix.database.name }})"
|
25
27
|
runs-on: ubuntu-latest
|
28
|
+
strategy:
|
29
|
+
matrix:
|
30
|
+
database:
|
31
|
+
- { name: 'PostgreSQL', url: 'postgresql://postgres:postgres@localhost:5432/stepper_motor_test' }
|
32
|
+
- { name: 'MySQL', url: 'mysql2://root:root@127.0.0.1:3306/stepper_motor_test?host=127.0.0.1' }
|
33
|
+
- { name: 'SQLite', url: 'sqlite3:db/test.sqlite3' }
|
34
|
+
|
35
|
+
services:
|
36
|
+
postgres:
|
37
|
+
image: postgres:14
|
38
|
+
env:
|
39
|
+
POSTGRES_USER: postgres
|
40
|
+
POSTGRES_PASSWORD: postgres
|
41
|
+
POSTGRES_DB: stepper_motor_test
|
42
|
+
ports:
|
43
|
+
- 5432:5432
|
44
|
+
options: >-
|
45
|
+
--health-cmd pg_isready
|
46
|
+
--health-interval 10s
|
47
|
+
--health-timeout 5s
|
48
|
+
--health-retries 5
|
49
|
+
mysql:
|
50
|
+
image: mysql:8.0
|
51
|
+
env:
|
52
|
+
MYSQL_ROOT_PASSWORD: root
|
53
|
+
MYSQL_DATABASE: stepper_motor_test
|
54
|
+
ports:
|
55
|
+
- 3306:3306
|
56
|
+
options: >-
|
57
|
+
--health-cmd "mysqladmin ping"
|
58
|
+
--health-interval 10s
|
59
|
+
--health-timeout 5s
|
60
|
+
--health-retries 5
|
26
61
|
|
27
|
-
# services:
|
28
|
-
# redis:
|
29
|
-
# image: redis
|
30
|
-
# ports:
|
31
|
-
# - 6379:6379
|
32
|
-
# options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5
|
33
62
|
steps:
|
34
63
|
- name: Install packages
|
35
|
-
run: sudo apt-get update && sudo apt-get install --no-install-recommends -y build-essential git libyaml-dev pkg-config
|
64
|
+
run: sudo apt-get update && sudo apt-get install --no-install-recommends -y build-essential git libyaml-dev pkg-config
|
36
65
|
|
37
66
|
- name: Checkout code
|
38
67
|
uses: actions/checkout@v4
|
@@ -43,9 +72,22 @@ jobs:
|
|
43
72
|
ruby-version: 3.2.2
|
44
73
|
bundler-cache: true
|
45
74
|
|
75
|
+
- name: Remove existing schema.rb
|
76
|
+
run: rm -f test/dummy/db/schema.rb
|
77
|
+
|
78
|
+
- name: Setup database
|
79
|
+
env:
|
80
|
+
RAILS_ENV: test
|
81
|
+
DATABASE_URL: ${{ matrix.database.url }}
|
82
|
+
run: |
|
83
|
+
cd test/dummy
|
84
|
+
bundle exec rails db:create
|
85
|
+
bundle exec rails db:migrate
|
86
|
+
cd ../..
|
87
|
+
|
46
88
|
- name: Run tests
|
47
89
|
env:
|
48
90
|
RAILS_ENV: test
|
49
|
-
|
91
|
+
DATABASE_URL: ${{ matrix.database.url }}
|
50
92
|
run: bin/test
|
51
93
|
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,22 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [Unreleased]
|
4
|
+
|
5
|
+
## [0.1.16] - 2025-06-20
|
6
|
+
|
7
|
+
- Add `skip!` flow control method to skip the current (or next) step and move on to the subsequent step, or finish the journey.
|
8
|
+
- Rename `if:` parameter to `skip_if:` for better clarity. The `if:` parameter is still supported for brevity.
|
9
|
+
|
10
|
+
## [0.1.15] - 2025-06-20
|
11
|
+
|
12
|
+
- Add `if:` condition allowing steps to be skipped. The `if:` can be a boolean, a callable or a symbol for a method name. The method should be on the Journey.
|
13
|
+
|
14
|
+
## [0.1.14] - 2025-06-10
|
15
|
+
|
16
|
+
- Since MySQL does not support partial indexes, use a generated column for the uniqueness checks. Other indexes
|
17
|
+
which are set up as partial indexes just become full indexes - that will make them larger on MySQL but the functionality
|
18
|
+
is going to be the same. This is a tradeoff but it seems sensible for the time being.
|
19
|
+
|
3
20
|
## [0.1.12] - 2025-06-08
|
4
21
|
|
5
22
|
- Ensure base job extension gets done via the reloader, so that app classes are available
|
@@ -0,0 +1,58 @@
|
|
1
|
+
class StepperMotorMigration005 < ActiveRecord::Migration[<%= migration_version %>]
|
2
|
+
def up
|
3
|
+
unless mysql?
|
4
|
+
say "Skipping migration as it is only used with MySQL (mysql2 or trilogy)"
|
5
|
+
return
|
6
|
+
end
|
7
|
+
|
8
|
+
# Add generated column that combines the state, type, hero_id, and hero_type
|
9
|
+
# The column will be NULL if any of the components is NULL
|
10
|
+
execute <<-SQL
|
11
|
+
ALTER TABLE stepper_motor_journeys
|
12
|
+
ADD COLUMN journey_uniq_col_generated VARCHAR(255) GENERATED ALWAYS AS (
|
13
|
+
CASE
|
14
|
+
WHEN state IN ('ready', 'performing', 'paused')
|
15
|
+
AND allow_multiple = 0
|
16
|
+
AND type IS NOT NULL
|
17
|
+
AND hero_id IS NOT NULL
|
18
|
+
AND hero_type IS NOT NULL
|
19
|
+
THEN CONCAT(type, ':', hero_id, ':', hero_type)
|
20
|
+
ELSE NULL
|
21
|
+
END
|
22
|
+
) STORED
|
23
|
+
SQL
|
24
|
+
|
25
|
+
# Add unique index on the generated column with MySQL-specific name
|
26
|
+
add_index :stepper_motor_journeys, :journey_uniq_col_generated,
|
27
|
+
unique: true,
|
28
|
+
name: :idx_journeys_one_per_hero_mysql_generated
|
29
|
+
|
30
|
+
# Remove old indexes that include 'ready', 'performing', and 'paused' states
|
31
|
+
remove_index :stepper_motor_journeys, name: :idx_journeys_one_per_hero_with_paused
|
32
|
+
end
|
33
|
+
|
34
|
+
def down
|
35
|
+
unless mysql?
|
36
|
+
say "Skipping migration as it is only used with MySQL (mysql2 or trilogy)"
|
37
|
+
return
|
38
|
+
end
|
39
|
+
|
40
|
+
# Remove the generated column and its index
|
41
|
+
remove_index :stepper_motor_journeys, name: :idx_journeys_one_per_hero_mysql_generated
|
42
|
+
remove_column :stepper_motor_journeys, :journey_uniq_col_generated
|
43
|
+
|
44
|
+
# Recreate old indexes
|
45
|
+
quoted_false = connection.quote(false)
|
46
|
+
add_index :stepper_motor_journeys, [:type, :hero_id, :hero_type],
|
47
|
+
where: "allow_multiple = '#{quoted_false}' AND state IN ('ready', 'performing', 'paused')",
|
48
|
+
unique: true,
|
49
|
+
name: :idx_journeys_one_per_hero_with_paused
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def mysql?
|
55
|
+
adapter = connection.adapter_name.downcase
|
56
|
+
adapter == 'mysql2' || adapter == 'trilogy'
|
57
|
+
end
|
58
|
+
end
|
@@ -27,6 +27,55 @@ module StepperMotor::Journey::FlowControl
|
|
27
27
|
throw :abort_step if @current_step_definition
|
28
28
|
end
|
29
29
|
|
30
|
+
# Is used to skip the current step and proceed to the next step in the journey. This is useful when you want to
|
31
|
+
# conditionally skip a step based on some business logic without canceling the entire journey. For example,
|
32
|
+
# you might want to skip a reminder email step if the user has already taken the required action.
|
33
|
+
#
|
34
|
+
# If there are more steps after the current step, `skip!` will schedule the next step to be performed.
|
35
|
+
# If the current step is the last step in the journey, `skip!` will finish the journey.
|
36
|
+
#
|
37
|
+
# `skip!` may be called within a step or outside of a step for journeys in the `ready` state.
|
38
|
+
# When called outside of a step, it will skip the next scheduled step and proceed to the following step.
|
39
|
+
#
|
40
|
+
# @return void
|
41
|
+
def skip!
|
42
|
+
if @current_step_definition
|
43
|
+
# Called within a step - set flag to skip current step
|
44
|
+
@skip_current_step = true
|
45
|
+
throw :abort_step if @current_step_definition
|
46
|
+
else
|
47
|
+
# Called outside of a step - skip next scheduled step
|
48
|
+
with_lock do
|
49
|
+
raise "skip! can only be used on journeys in the `ready` state, but was in #{state.inspect}" unless ready?
|
50
|
+
|
51
|
+
current_step_name = next_step_name
|
52
|
+
current_step_definition = lookup_step_definition(current_step_name)
|
53
|
+
|
54
|
+
unless current_step_definition
|
55
|
+
logger.warn { "no step definition found for #{current_step_name} - finishing journey" }
|
56
|
+
finished!
|
57
|
+
update!(previous_step_name: current_step_name, next_step_name: nil)
|
58
|
+
return
|
59
|
+
end
|
60
|
+
|
61
|
+
current_step_seq = current_step_definition.seq
|
62
|
+
next_step_definition = step_definitions[current_step_seq + 1]
|
63
|
+
|
64
|
+
if next_step_definition
|
65
|
+
# There are more steps after this one - schedule the next step
|
66
|
+
logger.info { "skipping scheduled step #{current_step_name}, will continue to #{next_step_definition.name}" }
|
67
|
+
set_next_step_and_enqueue(next_step_definition)
|
68
|
+
ready!
|
69
|
+
else
|
70
|
+
# This is the last step - finish the journey
|
71
|
+
logger.info { "skipping scheduled step #{current_step_name}, finishing journey" }
|
72
|
+
finished!
|
73
|
+
update!(previous_step_name: current_step_name, next_step_name: nil)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
30
79
|
# Is used to pause a Journey at any point. The "paused" state is similar to the "ready" state, except that "perform_next_step!" on the
|
31
80
|
# journey will do nothing - even if it is scheduled to be performed. Pausing a Journey can be useful in the following situations:
|
32
81
|
#
|
@@ -72,15 +72,41 @@ module StepperMotor
|
|
72
72
|
# When the journey gets scheduled, the triggering job is going to be delayed by this amount of time, and the
|
73
73
|
# `next_step_to_be_performed_at` attribute will be set to the current time plus the wait duration. Mutually exclusive with `after:`
|
74
74
|
# @param after[Float,#to_f,ActiveSupport::Duration] the amount of time this step should wait before getting performed
|
75
|
-
# including all the previous waits. This allows you to set the wait time based on the time after the journey started,
|
76
|
-
# to when the previous step has completed. When the journey gets scheduled, the triggering job is going to
|
77
|
-
# amount of time _minus the `wait` values of the preceding steps, and the
|
78
|
-
#
|
79
|
-
#
|
75
|
+
# including all the previous waits. This allows you to set the wait time based on the time after the journey started,
|
76
|
+
# as opposed to when the previous step has completed. When the journey gets scheduled, the triggering job is going to
|
77
|
+
# be delayed by this amount of time _minus the `wait` values of the preceding steps, and the `next_step_to_be_performed_at`
|
78
|
+
# attribute will be set to the current time. The `after` value gets converted into the `wait` value and passed to the step definition.
|
79
|
+
# Mutually exclusive with `wait:`
|
80
80
|
# @param on_exception[Symbol] See {StepperMotor::Step#on_exception}
|
81
|
-
# @param
|
81
|
+
# @param skip_if[TrueClass,FalseClass,Symbol,Proc] condition to check before performing the step. If a symbol is provided,
|
82
|
+
# it will call the method on the Journey. If a block is provided, it will be executed with the Journey as context.
|
83
|
+
# The step will be skipped if the condition returns a truthy value.
|
84
|
+
# @param if[TrueClass,FalseClass,Symbol,Proc] condition to check before performing the step. If a symbol is provided,
|
85
|
+
# it will call the method on the Journey. If a block is provided, it will be executed with the Journey as context.
|
86
|
+
# The step will be performed if the condition returns a truthy value. and skipped otherwise. Inverse of `skip_if`.
|
87
|
+
# @param additional_step_definition_options[Hash] Any remaining options get passed to `StepperMotor::Step.new` as keyword arguments.
|
82
88
|
# @return [StepperMotor::Step] the step definition that has been created
|
83
|
-
def self.step(name = nil, wait: nil, after: nil,
|
89
|
+
def self.step(name = nil, wait: nil, after: nil, **additional_step_definition_options, &blk)
|
90
|
+
# Handle the if: alias for backward compatibility
|
91
|
+
if additional_step_definition_options.key?(:if) && additional_step_definition_options.key?(:skip_if)
|
92
|
+
raise StepConfigurationError, "Either skip_if: or if: can be specified, but not both"
|
93
|
+
end
|
94
|
+
if additional_step_definition_options.key?(:if)
|
95
|
+
if_condition = additional_step_definition_options.delete(:if)
|
96
|
+
# Convert if: to skip_if: by negating either the actual value or the return value of the callable
|
97
|
+
# if: truthy means perform, skip_if: truthy means "skip"
|
98
|
+
additional_step_definition_options[:skip_if] = case if_condition
|
99
|
+
when true, false, nil
|
100
|
+
!if_condition
|
101
|
+
when Symbol
|
102
|
+
# For symbols, we need to create a proc that negates the result
|
103
|
+
-> { !send(if_condition) }
|
104
|
+
else
|
105
|
+
# For callables, we need to create a proc that negates the result
|
106
|
+
-> { !instance_exec(&if_condition) }
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
84
110
|
wait = if wait && after
|
85
111
|
raise StepConfigurationError, "Either wait: or after: can be specified, but not both"
|
86
112
|
elsif !wait && !after
|
@@ -97,8 +123,8 @@ module StepperMotor
|
|
97
123
|
raise StepConfigurationError, <<~MSG
|
98
124
|
Step #{step_definitions.length + 1} of #{self} has no explicit name,
|
99
125
|
and no block with step definition has been provided. Without a name the step
|
100
|
-
must be defined with a block to execute. If you want an instance method
|
101
|
-
|
126
|
+
must be defined with a block to execute. If you want an instance method of
|
127
|
+
this Journey to be used as the step, pass the name of the method as the name of the step.
|
102
128
|
MSG
|
103
129
|
end
|
104
130
|
|
@@ -109,7 +135,7 @@ module StepperMotor
|
|
109
135
|
raise StepConfigurationError, "Step named #{name.inspect} already defined" if known_step_names.include?(name)
|
110
136
|
|
111
137
|
# Create the step definition
|
112
|
-
StepperMotor::Step.new(name: name, wait: wait, seq: step_definitions.length,
|
138
|
+
StepperMotor::Step.new(name: name, wait: wait, seq: step_definitions.length, **additional_step_definition_options, &blk).tap do |step_definition|
|
113
139
|
# As per Rails docs: you need to be aware when using class_attribute with mutable structures
|
114
140
|
# as Array or Hash. In such cases, you don't want to do changes in place. Instead use setters.
|
115
141
|
# See https://apidock.com/rails/v7.1.3.2/Class/class_attribute
|
@@ -229,6 +255,22 @@ module StepperMotor
|
|
229
255
|
logger.info { "will reattempt #{current_step_name} in #{@reattempt_after} seconds" }
|
230
256
|
set_next_step_and_enqueue(@current_step_definition, wait: @reattempt_after)
|
231
257
|
ready!
|
258
|
+
elsif @skip_current_step
|
259
|
+
# The step asked to be skipped
|
260
|
+
current_step_seq = @current_step_definition.seq
|
261
|
+
next_step_definition = step_definitions[current_step_seq + 1]
|
262
|
+
|
263
|
+
if next_step_definition
|
264
|
+
# There are more steps after this one - schedule the next step
|
265
|
+
logger.info { "skipping current step #{current_step_name}, will continue to #{next_step_definition.name}" }
|
266
|
+
set_next_step_and_enqueue(next_step_definition)
|
267
|
+
ready!
|
268
|
+
else
|
269
|
+
# This is the last step - finish the journey
|
270
|
+
logger.info { "skipping current step #{current_step_name}, finishing journey" }
|
271
|
+
finished!
|
272
|
+
update!(previous_step_name: current_step_name, next_step_name: nil)
|
273
|
+
end
|
232
274
|
elsif finished?
|
233
275
|
logger.info { "was marked finished inside the step" }
|
234
276
|
update!(previous_step_name: current_step_name, next_step_name: nil)
|
@@ -247,6 +289,7 @@ module StepperMotor
|
|
247
289
|
# and not via background jobs (which reload the model). This should actually be solved
|
248
290
|
# using some object that contains the state of the action later, but for now - the dirty approach is fine.
|
249
291
|
@reattempt_after = nil
|
292
|
+
@skip_current_step = nil
|
250
293
|
@current_step_definition = nil
|
251
294
|
# Re-raise the exception, now that we have persisted the Journey according to the recovery policy
|
252
295
|
if ex_rescued_at_perform
|
data/lib/stepper_motor/step.rb
CHANGED
@@ -24,12 +24,39 @@ class StepperMotor::Step
|
|
24
24
|
# The possible values are:
|
25
25
|
# * `:cancel!` - cancels the Journey and re-raises the exception. The Journey will be persisted before re-raising.
|
26
26
|
# * `:reattempt!` - reattempts the Journey and re-raises the exception. The Journey will be persisted before re-raising.
|
27
|
-
|
27
|
+
# * `:pause!` - pauses the Journey and re-raises the exception. The Journey will be persisted before re-raising.
|
28
|
+
# * `:skip!` - skips the current step and proceeds to the next step, or finishes the journey if it's the last step.
|
29
|
+
# @param skip_if[TrueClass,FalseClass,NilClass,Symbol,Proc] condition to check before performing the step. If a boolean is provided,
|
30
|
+
# it will be used directly. If nil is provided, it will be treated as false. If a symbol is provided,
|
31
|
+
# it will call the method on the Journey. If a block is provided, it will be executed with the Journey as context.
|
32
|
+
# The step will only be performed if the condition returns a truthy value.
|
33
|
+
def initialize(name:, seq:, on_exception: :pause!, wait: 0, skip_if: false, &step_block)
|
28
34
|
@step_block = step_block
|
29
35
|
@name = name.to_s
|
30
36
|
@wait = wait
|
31
37
|
@seq = seq
|
32
38
|
@on_exception = on_exception # TODO: Validate?
|
39
|
+
@skip_if_condition = skip_if
|
40
|
+
|
41
|
+
# Validate the skip_if condition
|
42
|
+
if ![true, false, nil].include?(@skip_if_condition) && !@skip_if_condition.is_a?(Symbol) && !@skip_if_condition.respond_to?(:call)
|
43
|
+
raise ArgumentError, "skip_if: condition must be a boolean, nil, Symbol or a callable object, but was a #{@skip_if_condition.inspect}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Checks if the step should be skipped based on the skip_if condition
|
48
|
+
#
|
49
|
+
# @param journey[StepperMotor::Journey] the journey to check the condition for
|
50
|
+
# @return [Boolean] true if the step should be skipped, false otherwise
|
51
|
+
def should_skip?(journey)
|
52
|
+
case @skip_if_condition
|
53
|
+
when true, false, nil
|
54
|
+
!!@skip_if_condition
|
55
|
+
when Symbol
|
56
|
+
journey.send(@skip_if_condition) # Allow private methods
|
57
|
+
else
|
58
|
+
journey.instance_exec(&@skip_if_condition)
|
59
|
+
end
|
33
60
|
end
|
34
61
|
|
35
62
|
# Performs the step on the passed Journey, wrapping the step with the required context.
|
@@ -40,6 +67,12 @@ class StepperMotor::Step
|
|
40
67
|
# journey will be called
|
41
68
|
# @return void
|
42
69
|
def perform_in_context_of(journey)
|
70
|
+
# Return early should the `skip_if` condition be truthy
|
71
|
+
if should_skip?(journey)
|
72
|
+
journey.logger.info { "skipping as skip_if: condition was truthy" }
|
73
|
+
return
|
74
|
+
end
|
75
|
+
|
43
76
|
# This is a tricky bit.
|
44
77
|
#
|
45
78
|
# reattempt!, cancel! (and potentially - future flow control methods) all use `throw` to
|
@@ -61,21 +94,18 @@ class StepperMotor::Step
|
|
61
94
|
end
|
62
95
|
end
|
63
96
|
rescue MissingDefinition
|
64
|
-
# This journey won't succeed with any number of reattempts,
|
97
|
+
# This journey won't succeed with any number of reattempts, pause it.
|
65
98
|
catch(:abort_step) { journey.pause! }
|
66
99
|
raise
|
67
100
|
rescue => e
|
68
101
|
# Act according to the set policy. The basic 2 for the moment are :reattempt! and :cancel!,
|
69
102
|
# and can be applied by just calling the methods on the passed journey
|
70
103
|
case @on_exception
|
71
|
-
when :reattempt!
|
72
|
-
catch(:abort_step) { journey.
|
73
|
-
when :cancel!
|
74
|
-
catch(:abort_step) { journey.cancel! }
|
75
|
-
when :pause!
|
76
|
-
catch(:abort_step) { journey.pause! }
|
104
|
+
when :reattempt!, :cancel!, :pause!, :skip!
|
105
|
+
catch(:abort_step) { journey.public_send(@on_exception) }
|
77
106
|
else
|
78
107
|
# Leave the journey hanging in the "performing" state
|
108
|
+
journey.logger.warn { "unusual on_exception: value (#{@on_exception.inspect}) - the journey will be left hanging in 'performing' state and will be collected as hung" }
|
79
109
|
end
|
80
110
|
|
81
111
|
# Re-raise the exception so that the Rails error handling can register it
|