acidic_job 1.0.0.rc3 → 1.0.0.rc4
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/.rubocop-https---www-goodcop-style-base-yml +1051 -0
- data/.rubocop.yml +4 -76
- data/Gemfile.lock +89 -81
- data/README.md +6 -6
- data/acidic_job.gemspec +0 -2
- data/app/models/acidic_job/entry.rb +4 -2
- data/app/models/acidic_job/execution.rb +25 -12
- data/app/models/acidic_job/value.rb +2 -0
- data/bin/console +3 -3
- data/lib/acidic_job/builder.rb +2 -2
- data/lib/acidic_job/context.rb +36 -20
- data/lib/acidic_job/errors.rb +2 -3
- data/lib/acidic_job/log_subscriber.rb +4 -6
- data/lib/acidic_job/plugin_context.rb +26 -3
- data/lib/acidic_job/plugins/transactional_step.rb +4 -4
- data/lib/acidic_job/serializer.rb +28 -0
- data/lib/acidic_job/serializers/job_serializer.rb +1 -1
- data/lib/acidic_job/serializers/range_serializer.rb +1 -3
- data/lib/acidic_job/testing.rb +10 -12
- data/lib/acidic_job/version.rb +1 -1
- data/lib/acidic_job/workflow.rb +44 -32
- data/lib/acidic_job.rb +3 -0
- data/lib/generators/acidic_job/install_generator.rb +5 -5
- data/lib/generators/acidic_job/templates/create_acidic_job_tables_migration.rb.erb +6 -6
- metadata +4 -30
@@ -4,13 +4,22 @@ module AcidicJob
|
|
4
4
|
class PluginContext
|
5
5
|
PLUGIN_INACTIVE = :__ACIDIC_JOB_PLUGIN_INACTIVE__
|
6
6
|
|
7
|
-
def initialize(plugin, job, execution, step_definition)
|
7
|
+
def initialize(plugin, job, execution, context, step_definition)
|
8
8
|
@plugin = plugin
|
9
9
|
@job = job
|
10
10
|
@execution = execution
|
11
|
+
@context = context
|
11
12
|
@step_definition = step_definition
|
12
13
|
end
|
13
14
|
|
15
|
+
def set(hash)
|
16
|
+
@context.set(hash)
|
17
|
+
end
|
18
|
+
|
19
|
+
def get(*keys)
|
20
|
+
@context.get(*keys)
|
21
|
+
end
|
22
|
+
|
14
23
|
def definition
|
15
24
|
@step_definition.fetch(@plugin.keyword.to_s, PLUGIN_INACTIVE)
|
16
25
|
end
|
@@ -40,8 +49,22 @@ module AcidicJob
|
|
40
49
|
@job.enqueue(...)
|
41
50
|
end
|
42
51
|
|
43
|
-
def
|
44
|
-
@job.
|
52
|
+
def halt_workflow!
|
53
|
+
@job.halt_workflow!
|
54
|
+
end
|
55
|
+
|
56
|
+
def repeat_step!
|
57
|
+
@job.repeat_step!
|
58
|
+
end
|
59
|
+
|
60
|
+
def resolve_method(method_name)
|
61
|
+
begin
|
62
|
+
method_obj = @job.method(method_name)
|
63
|
+
rescue NameError
|
64
|
+
raise UndefinedMethodError.new(method_name)
|
65
|
+
end
|
66
|
+
|
67
|
+
method_obj
|
45
68
|
end
|
46
69
|
|
47
70
|
def plugin_action(action)
|
@@ -26,10 +26,10 @@ module AcidicJob
|
|
26
26
|
return yield if context.definition == false
|
27
27
|
|
28
28
|
model = if context.definition == true
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
29
|
+
AcidicJob::Execution
|
30
|
+
else
|
31
|
+
context.definition["on"].constantize
|
32
|
+
end
|
33
33
|
|
34
34
|
model.transaction(&block)
|
35
35
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
require_relative "arguments"
|
5
|
+
|
6
|
+
module AcidicJob
|
7
|
+
# Used for `serialize` method in ActiveRecord
|
8
|
+
module Serializer
|
9
|
+
extend self
|
10
|
+
|
11
|
+
def load(json)
|
12
|
+
return if json.nil? || json.empty?
|
13
|
+
|
14
|
+
data = JSON.parse json
|
15
|
+
|
16
|
+
Arguments.__send__ :deserialize_argument, data
|
17
|
+
end
|
18
|
+
|
19
|
+
def dump(obj)
|
20
|
+
data = Arguments.__send__ :serialize_argument, obj
|
21
|
+
|
22
|
+
JSON.generate data, strict: true
|
23
|
+
rescue ActiveJob::SerializationError => e
|
24
|
+
e.message << " (`#{obj.inspect}`)"
|
25
|
+
raise e
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/acidic_job/testing.rb
CHANGED
@@ -22,25 +22,23 @@ module AcidicJob
|
|
22
22
|
::DatabaseCleaner.cleaners = @original_cleaners
|
23
23
|
end
|
24
24
|
|
25
|
-
private
|
26
|
-
|
27
25
|
# Ensure that the system's original DatabaseCleaner configuration is maintained, options included,
|
28
26
|
# except that any `transaction` strategies for any ORMs are replaced with a `deletion` strategy.
|
29
|
-
def transaction_free_cleaners_for(original_cleaners)
|
27
|
+
private def transaction_free_cleaners_for(original_cleaners)
|
30
28
|
non_transaction_cleaners = original_cleaners.dup.to_h do |(orm, opts), cleaner|
|
31
29
|
[[orm, opts], ensure_no_transaction_strategies_for(cleaner)]
|
32
30
|
end
|
33
31
|
::DatabaseCleaner::Cleaners.new(non_transaction_cleaners)
|
34
32
|
end
|
35
33
|
|
36
|
-
def ensure_no_transaction_strategies_for(cleaner)
|
34
|
+
private def ensure_no_transaction_strategies_for(cleaner)
|
37
35
|
return cleaner unless strategy_name_for(cleaner) == "transaction"
|
38
36
|
|
39
37
|
cleaner.strategy = deletion_strategy_for(cleaner)
|
40
38
|
cleaner
|
41
39
|
end
|
42
40
|
|
43
|
-
def strategy_name_for(cleaner)
|
41
|
+
private def strategy_name_for(cleaner)
|
44
42
|
cleaner # <DatabaseCleaner::Cleaner>
|
45
43
|
.strategy # <DatabaseCleaner::ActiveRecord::Truncation>
|
46
44
|
.class # DatabaseCleaner::ActiveRecord::Truncation
|
@@ -50,19 +48,19 @@ module AcidicJob
|
|
50
48
|
.downcase # "truncation"
|
51
49
|
end
|
52
50
|
|
53
|
-
def deletion_strategy_for(cleaner)
|
51
|
+
private def deletion_strategy_for(cleaner)
|
54
52
|
strategy = cleaner.strategy
|
55
|
-
strategy_namespace = strategy
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
53
|
+
strategy_namespace = strategy # <DatabaseCleaner::ActiveRecord::Truncation>
|
54
|
+
.class # DatabaseCleaner::ActiveRecord::Truncation
|
55
|
+
.name # "DatabaseCleaner::ActiveRecord::Truncation"
|
56
|
+
.rpartition("::") # ["DatabaseCleaner::ActiveRecord", "::", "Truncation"]
|
57
|
+
.first # "DatabaseCleaner::ActiveRecord"
|
60
58
|
deletion_strategy_class_name = [strategy_namespace, "::", "Deletion"].join
|
61
59
|
deletion_strategy_class = deletion_strategy_class_name.constantize
|
62
60
|
instance_variable_hash = strategy.instance_variables.to_h do |var|
|
63
61
|
[
|
64
62
|
var.to_s.remove("@"),
|
65
|
-
strategy.instance_variable_get(var)
|
63
|
+
strategy.instance_variable_get(var),
|
66
64
|
]
|
67
65
|
end
|
68
66
|
options = instance_variable_hash.except("db", "connection_class")
|
data/lib/acidic_job/version.rb
CHANGED
data/lib/acidic_job/workflow.rb
CHANGED
@@ -8,7 +8,7 @@ module AcidicJob
|
|
8
8
|
HALT_STEP = :__ACIDIC_JOB_HALT_STEP_SIGNAL__
|
9
9
|
private_constant :REPEAT_STEP, :HALT_STEP
|
10
10
|
|
11
|
-
def execute_workflow(unique_by:, with:
|
11
|
+
def execute_workflow(unique_by:, with: AcidicJob.plugins, &block)
|
12
12
|
@__acidic_job_plugins__ = with
|
13
13
|
serialized_job = serialize
|
14
14
|
|
@@ -30,13 +30,13 @@ module AcidicJob
|
|
30
30
|
|
31
31
|
AcidicJob.instrument(:initialize_workflow, definition: workflow_definition) do
|
32
32
|
transaction_args = case ::ActiveRecord::Base.connection.adapter_name.downcase.to_sym
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
idempotency_key = Digest::SHA256.hexdigest(JSON.
|
33
|
+
# SQLite doesn't support `serializable` transactions
|
34
|
+
when :sqlite
|
35
|
+
{}
|
36
|
+
else
|
37
|
+
{ isolation: :serializable }
|
38
|
+
end
|
39
|
+
idempotency_key = Digest::SHA256.hexdigest(JSON.generate([self.class.name, unique_by], strict: true))
|
40
40
|
|
41
41
|
@__acidic_job_execution__ = ::ActiveRecord::Base.transaction(**transaction_args) do
|
42
42
|
record = Execution.find_by(idempotency_key: idempotency_key)
|
@@ -61,11 +61,11 @@ module AcidicJob
|
|
61
61
|
)
|
62
62
|
else
|
63
63
|
starting_point = if workflow_definition.key?("steps")
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
64
|
+
workflow_definition["steps"].keys.first
|
65
|
+
else
|
66
|
+
# TODO: add deprecation warning
|
67
|
+
workflow_definition.keys.first
|
68
|
+
end
|
69
69
|
|
70
70
|
record = Execution.create!(
|
71
71
|
idempotency_key: idempotency_key,
|
@@ -89,7 +89,7 @@ module AcidicJob
|
|
89
89
|
|
90
90
|
current_step = @__acidic_job_execution__.recover_to
|
91
91
|
|
92
|
-
if not @__acidic_job_execution__.defined?(current_step)
|
92
|
+
if not @__acidic_job_execution__.defined?(current_step)
|
93
93
|
raise UndefinedStepError.new(current_step)
|
94
94
|
end
|
95
95
|
|
@@ -101,11 +101,10 @@ module AcidicJob
|
|
101
101
|
@__acidic_job_execution__.record!(
|
102
102
|
step: step_definition.fetch("does"),
|
103
103
|
action: :halted,
|
104
|
-
timestamp: Time.now
|
105
104
|
)
|
106
105
|
return true
|
107
106
|
else
|
108
|
-
@__acidic_job_execution__.
|
107
|
+
@__acidic_job_execution__.update_column(:recover_to, recover_to)
|
109
108
|
end
|
110
109
|
end
|
111
110
|
end
|
@@ -116,14 +115,19 @@ module AcidicJob
|
|
116
115
|
throw :repeat, REPEAT_STEP
|
117
116
|
end
|
118
117
|
|
119
|
-
def
|
118
|
+
def halt_workflow!
|
120
119
|
throw :halt, HALT_STEP
|
121
120
|
end
|
122
121
|
|
122
|
+
def halt_step!
|
123
|
+
# TODO add deprecation warning
|
124
|
+
halt_workflow!
|
125
|
+
end
|
126
|
+
|
123
127
|
def step_retrying?
|
124
128
|
step_name = caller_locations.first.label
|
125
129
|
|
126
|
-
if not @__acidic_job_execution__.defined?(step_name)
|
130
|
+
if not @__acidic_job_execution__.defined?(step_name)
|
127
131
|
raise UndefinedStepError.new(step_name)
|
128
132
|
end
|
129
133
|
|
@@ -138,9 +142,7 @@ module AcidicJob
|
|
138
142
|
@__acidic_job_context__
|
139
143
|
end
|
140
144
|
|
141
|
-
private
|
142
|
-
|
143
|
-
def take_step(step_definition)
|
145
|
+
private def take_step(step_definition)
|
144
146
|
curr_step = step_definition.fetch("does")
|
145
147
|
next_step = step_definition.fetch("then")
|
146
148
|
|
@@ -148,7 +150,7 @@ module AcidicJob
|
|
148
150
|
|
149
151
|
rescued_error = nil
|
150
152
|
begin
|
151
|
-
@__acidic_job_execution__.record!(step: curr_step, action: :started
|
153
|
+
@__acidic_job_execution__.record!(step: curr_step, action: :started)
|
152
154
|
result = AcidicJob.instrument(:perform_step, **step_definition) do
|
153
155
|
perform_step_for(step_definition)
|
154
156
|
end
|
@@ -156,10 +158,16 @@ module AcidicJob
|
|
156
158
|
when REPEAT_STEP
|
157
159
|
curr_step
|
158
160
|
else
|
159
|
-
@__acidic_job_execution__.record!(
|
161
|
+
@__acidic_job_execution__.record!(
|
162
|
+
step: curr_step,
|
163
|
+
action: :succeeded,
|
164
|
+
ignored: {
|
165
|
+
result: result,
|
166
|
+
}
|
167
|
+
)
|
160
168
|
next_step
|
161
169
|
end
|
162
|
-
rescue
|
170
|
+
rescue => e
|
163
171
|
rescued_error = e
|
164
172
|
raise e
|
165
173
|
ensure
|
@@ -168,11 +176,10 @@ module AcidicJob
|
|
168
176
|
@__acidic_job_execution__.record!(
|
169
177
|
step: curr_step,
|
170
178
|
action: :errored,
|
171
|
-
timestamp: Time.now,
|
172
179
|
exception_class: rescued_error.class.name,
|
173
180
|
message: rescued_error.message
|
174
181
|
)
|
175
|
-
rescue
|
182
|
+
rescue => e
|
176
183
|
# We're already inside an error condition, so swallow any additional
|
177
184
|
# errors from here and just send them to logs.
|
178
185
|
logger.error(
|
@@ -183,7 +190,7 @@ module AcidicJob
|
|
183
190
|
end
|
184
191
|
end
|
185
192
|
|
186
|
-
def perform_step_for(step_definition)
|
193
|
+
private def perform_step_for(step_definition)
|
187
194
|
step_name = step_definition.fetch("does")
|
188
195
|
begin
|
189
196
|
step_method = method(step_name)
|
@@ -191,10 +198,10 @@ module AcidicJob
|
|
191
198
|
raise UndefinedMethodError.new(step_name)
|
192
199
|
end
|
193
200
|
|
194
|
-
raise InvalidMethodError.new(step_name) unless step_method.arity.zero?
|
201
|
+
# raise InvalidMethodError.new(step_name) unless step_method.arity.zero?
|
195
202
|
|
196
203
|
plugin_pipeline_callable = @__acidic_job_plugins__.reverse.reduce(step_method) do |callable, plugin|
|
197
|
-
context = PluginContext.new(plugin, self, @__acidic_job_execution__, step_definition)
|
204
|
+
context = PluginContext.new(plugin, self, @__acidic_job_execution__, @__acidic_job_context__, step_definition)
|
198
205
|
|
199
206
|
if context.inactive?
|
200
207
|
callable
|
@@ -202,14 +209,19 @@ module AcidicJob
|
|
202
209
|
proc do
|
203
210
|
called = false
|
204
211
|
|
205
|
-
result = plugin.around_step(context) do
|
212
|
+
result = plugin.around_step(context) do |*args, **kwargs|
|
206
213
|
raise DoublePluginCallError.new(plugin, step_name) if called
|
207
214
|
|
208
215
|
called = true
|
209
|
-
|
216
|
+
|
217
|
+
if callable.arity.zero?
|
218
|
+
callable.call
|
219
|
+
else
|
220
|
+
callable.call(*args, **kwargs)
|
221
|
+
end
|
210
222
|
end
|
211
223
|
|
212
|
-
raise MissingPluginCallError.new(plugin, step_name) unless called
|
224
|
+
# raise MissingPluginCallError.new(plugin, step_name) unless called
|
213
225
|
|
214
226
|
result
|
215
227
|
end
|
data/lib/acidic_job.rb
CHANGED
@@ -9,6 +9,7 @@ require_relative "acidic_job/arguments"
|
|
9
9
|
require_relative "acidic_job/plugin_context"
|
10
10
|
require_relative "acidic_job/plugins/transactional_step"
|
11
11
|
require_relative "acidic_job/log_subscriber"
|
12
|
+
require_relative "acidic_job/serializer"
|
12
13
|
require_relative "acidic_job/workflow"
|
13
14
|
|
14
15
|
require "active_support"
|
@@ -21,6 +22,8 @@ module AcidicJob
|
|
21
22
|
|
22
23
|
mattr_accessor :logger, default: DEFAULT_LOGGER
|
23
24
|
mattr_accessor :connects_to
|
25
|
+
mattr_accessor :plugins, default: [Plugins::TransactionalStep]
|
26
|
+
mattr_accessor :clear_finished_executions_after, default: 1.week
|
24
27
|
|
25
28
|
def instrument(channel, **options, &block)
|
26
29
|
ActiveSupport::Notifications.instrument("#{channel}.acidic_job", **options.deep_symbolize_keys, &block)
|
@@ -12,13 +12,13 @@ module AcidicJob
|
|
12
12
|
|
13
13
|
# Copies the migration template to db/migrate.
|
14
14
|
def copy_acidic_job_runs_migration_files
|
15
|
-
migration_template
|
16
|
-
|
17
|
-
|
15
|
+
migration_template(
|
16
|
+
"create_acidic_job_tables_migration.rb.erb",
|
17
|
+
"db/migrate/create_acidic_job_tables.rb",
|
18
|
+
migration_version: migration_version
|
19
|
+
)
|
18
20
|
end
|
19
21
|
|
20
|
-
protected
|
21
|
-
|
22
22
|
def migration_version
|
23
23
|
"[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
|
24
24
|
end
|
@@ -2,30 +2,30 @@ class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version
|
|
2
2
|
def change
|
3
3
|
create_table :acidic_job_executions, force: true do |t|
|
4
4
|
t.string :idempotency_key, null: false, index: { unique: true }
|
5
|
-
t.json :serialized_job, null: false
|
5
|
+
t.json :serialized_job, null: false
|
6
6
|
t.datetime :last_run_at, null: true
|
7
7
|
t.datetime :locked_at, null: true
|
8
8
|
t.string :recover_to, null: true
|
9
|
-
t.
|
9
|
+
t.text :definition, null: true
|
10
10
|
|
11
11
|
t.timestamps
|
12
12
|
end
|
13
13
|
|
14
14
|
create_table :acidic_job_entries do |t|
|
15
|
-
t.references :execution, null: false, foreign_key: { to_table: :acidic_job_executions }
|
15
|
+
t.references :execution, null: false, foreign_key: { to_table: :acidic_job_executions, on_delete: :cascade }
|
16
16
|
t.string :step, null: false
|
17
17
|
t.string :action, null: false
|
18
18
|
t.datetime :timestamp, null: false
|
19
|
-
t.
|
19
|
+
t.text :data, null: true
|
20
20
|
|
21
21
|
t.timestamps
|
22
22
|
end
|
23
23
|
add_index :acidic_job_entries, [:execution_id, :step, :action]
|
24
24
|
|
25
25
|
create_table :acidic_job_values do |t|
|
26
|
-
t.references :execution, null: false, foreign_key: { to_table: :acidic_job_executions }
|
26
|
+
t.references :execution, null: false, foreign_key: { to_table: :acidic_job_executions, on_delete: :cascade }
|
27
27
|
t.string :key, null: false
|
28
|
-
t.
|
28
|
+
t.text :value, null: false
|
29
29
|
|
30
30
|
t.timestamps
|
31
31
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: acidic_job
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.0.
|
4
|
+
version: 1.0.0.rc4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- fractaledmind
|
8
8
|
bindir: exe
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-
|
10
|
+
date: 2025-06-13 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: json
|
@@ -163,34 +163,6 @@ dependencies:
|
|
163
163
|
- - ">="
|
164
164
|
- !ruby/object:Gem::Version
|
165
165
|
version: '0'
|
166
|
-
- !ruby/object:Gem::Dependency
|
167
|
-
name: rubocop-minitest
|
168
|
-
requirement: !ruby/object:Gem::Requirement
|
169
|
-
requirements:
|
170
|
-
- - ">="
|
171
|
-
- !ruby/object:Gem::Version
|
172
|
-
version: '0'
|
173
|
-
type: :development
|
174
|
-
prerelease: false
|
175
|
-
version_requirements: !ruby/object:Gem::Requirement
|
176
|
-
requirements:
|
177
|
-
- - ">="
|
178
|
-
- !ruby/object:Gem::Version
|
179
|
-
version: '0'
|
180
|
-
- !ruby/object:Gem::Dependency
|
181
|
-
name: rubocop-rake
|
182
|
-
requirement: !ruby/object:Gem::Requirement
|
183
|
-
requirements:
|
184
|
-
- - ">="
|
185
|
-
- !ruby/object:Gem::Version
|
186
|
-
version: '0'
|
187
|
-
type: :development
|
188
|
-
prerelease: false
|
189
|
-
version_requirements: !ruby/object:Gem::Requirement
|
190
|
-
requirements:
|
191
|
-
- - ">="
|
192
|
-
- !ruby/object:Gem::Version
|
193
|
-
version: '0'
|
194
166
|
- !ruby/object:Gem::Dependency
|
195
167
|
name: simplecov
|
196
168
|
requirement: !ruby/object:Gem::Requirement
|
@@ -230,6 +202,7 @@ files:
|
|
230
202
|
- ".github/FUNDING.yml"
|
231
203
|
- ".github/workflows/main.yml"
|
232
204
|
- ".gitignore"
|
205
|
+
- ".rubocop-https---www-goodcop-style-base-yml"
|
233
206
|
- ".rubocop.yml"
|
234
207
|
- ".ruby-version"
|
235
208
|
- Gemfile
|
@@ -262,6 +235,7 @@ files:
|
|
262
235
|
- lib/acidic_job/log_subscriber.rb
|
263
236
|
- lib/acidic_job/plugin_context.rb
|
264
237
|
- lib/acidic_job/plugins/transactional_step.rb
|
238
|
+
- lib/acidic_job/serializer.rb
|
265
239
|
- lib/acidic_job/serializers/exception_serializer.rb
|
266
240
|
- lib/acidic_job/serializers/job_serializer.rb
|
267
241
|
- lib/acidic_job/serializers/new_record_serializer.rb
|