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.
@@ -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 halt_step!
44
- @job.halt_step!
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
- AcidicJob::Execution
30
- else
31
- context.definition["on"].constantize
32
- end
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
@@ -15,7 +15,7 @@ module AcidicJob
15
15
 
16
16
  def deserialize(hash)
17
17
  job = ::ActiveJob::Base.deserialize(hash)
18
- job.send(:deserialize_arguments_if_needed)
18
+ job.__send__(:deserialize_arguments_if_needed)
19
19
  job
20
20
  end
21
21
 
@@ -17,9 +17,7 @@ module AcidicJob
17
17
  klass.new(*Arguments.deserialize(hash.values_at(*KEYS)))
18
18
  end
19
19
 
20
- private
21
-
22
- def klass
20
+ private def klass
23
21
  ::Range
24
22
  end
25
23
  end
@@ -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 # <DatabaseCleaner::ActiveRecord::Truncation>
56
- .class # DatabaseCleaner::ActiveRecord::Truncation
57
- .name # "DatabaseCleaner::ActiveRecord::Truncation"
58
- .rpartition("::") # ["DatabaseCleaner::ActiveRecord", "::", "Truncation"]
59
- .first # "DatabaseCleaner::ActiveRecord"
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")
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AcidicJob
4
- VERSION = "1.0.0.rc3"
4
+ VERSION = "1.0.0.rc4"
5
5
  end
@@ -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: [Plugins::TransactionalStep], &block)
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
- # SQLite doesn't support `serializable` transactions
34
- when :sqlite
35
- {}
36
- else
37
- { isolation: :serializable }
38
- end
39
- idempotency_key = Digest::SHA256.hexdigest(JSON.fast_generate([self.class.name, unique_by], strict: true))
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
- workflow_definition["steps"].keys.first
65
- else
66
- # TODO: add deprecation warning
67
- workflow_definition.keys.first
68
- end
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) # rubocop:disable Style/Not
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__.update!(recover_to: recover_to)
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 halt_step!
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) # rubocop:disable Style/IfUnlessModifier, Style/Not
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, timestamp: Time.now)
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!(step: curr_step, action: :succeeded, timestamp: Time.now, result: result)
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 StandardError => e
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 StandardError => e
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
- callable.call
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 "create_acidic_job_tables_migration.rb.erb",
16
- "db/migrate/create_acidic_job_tables.rb",
17
- migration_version: migration_version
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, default: "{}"
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.json :definition, null: true, default: "{}"
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.json :data, null: true, default: "{}"
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.json :value, null: false, default: "{}"
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.rc3
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-05-16 00:00:00.000000000 Z
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