acidic_job 0.7.7 → 1.0.0.beta.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/main.yml +36 -12
  3. data/.gitignore +6 -0
  4. data/.rubocop.yml +21 -0
  5. data/.tool-versions +1 -0
  6. data/Gemfile +0 -25
  7. data/Gemfile.lock +176 -74
  8. data/README.md +299 -46
  9. data/UPGRADE_GUIDE.md +81 -0
  10. data/acidic_job.gemspec +15 -2
  11. data/bin/console +5 -2
  12. data/combustion/log/test.log +0 -0
  13. data/gemfiles/rails_6.1_sidekiq_6.4.gemfile +10 -0
  14. data/gemfiles/rails_6.1_sidekiq_6.5.gemfile +10 -0
  15. data/gemfiles/rails_7.0_sidekiq_6.4.gemfile +10 -0
  16. data/gemfiles/rails_7.0_sidekiq_6.5.gemfile +10 -0
  17. data/gemfiles/rails_7.1_sidekiq_6.4.gemfile +10 -0
  18. data/gemfiles/rails_7.1_sidekiq_6.5.gemfile +10 -0
  19. data/lib/acidic_job/active_kiq.rb +114 -0
  20. data/lib/acidic_job/arguments.rb +22 -0
  21. data/lib/acidic_job/base.rb +11 -0
  22. data/lib/acidic_job/errors.rb +10 -17
  23. data/lib/acidic_job/extensions/action_mailer.rb +19 -0
  24. data/lib/acidic_job/extensions/noticed.rb +46 -0
  25. data/lib/acidic_job/{response.rb → finished_point.rb} +5 -7
  26. data/lib/acidic_job/idempotency_key.rb +27 -0
  27. data/lib/acidic_job/logger.rb +31 -0
  28. data/lib/acidic_job/mixin.rb +253 -0
  29. data/lib/acidic_job/perform_wrapper.rb +14 -27
  30. data/lib/acidic_job/processor.rb +96 -0
  31. data/lib/acidic_job/rails.rb +29 -0
  32. data/lib/acidic_job/recovery_point.rb +4 -5
  33. data/lib/acidic_job/run.rb +299 -0
  34. data/lib/acidic_job/serializer.rb +24 -0
  35. data/lib/acidic_job/serializers/exception_serializer.rb +41 -0
  36. data/lib/acidic_job/serializers/finished_point_serializer.rb +24 -0
  37. data/lib/acidic_job/serializers/job_serializer.rb +35 -0
  38. data/lib/acidic_job/serializers/range_serializer.rb +28 -0
  39. data/lib/acidic_job/serializers/recovery_point_serializer.rb +25 -0
  40. data/lib/acidic_job/serializers/worker_serializer.rb +27 -0
  41. data/lib/acidic_job/testing.rb +73 -0
  42. data/lib/acidic_job/version.rb +1 -1
  43. data/lib/acidic_job/workflow.rb +78 -0
  44. data/lib/acidic_job/workflow_builder.rb +35 -0
  45. data/lib/acidic_job/workflow_step.rb +103 -0
  46. data/lib/acidic_job.rb +33 -334
  47. data/lib/generators/acidic_job/drop_tables_generator.rb +26 -0
  48. data/lib/generators/acidic_job/install_generator.rb +27 -0
  49. data/lib/generators/acidic_job/templates/create_acidic_job_runs_migration.rb.erb +19 -0
  50. data/lib/generators/{templates/create_acidic_job_keys_migration.rb.erb → acidic_job/templates/drop_acidic_job_keys_migration.rb.erb} +10 -3
  51. metadata +214 -20
  52. data/.ruby_version +0 -1
  53. data/lib/acidic_job/deliver_transactionally_extension.rb +0 -26
  54. data/lib/acidic_job/key.rb +0 -33
  55. data/lib/acidic_job/no_op.rb +0 -11
  56. data/lib/acidic_job/perform_transactionally_extension.rb +0 -33
  57. data/lib/acidic_job/sidekiq_callbacks.rb +0 -45
  58. data/lib/acidic_job/staged.rb +0 -50
  59. data/lib/generators/acidic_job_generator.rb +0 -44
  60. data/lib/generators/templates/create_staged_acidic_jobs_migration.rb.erb +0 -10
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AcidicJob
4
+ class Processor
5
+ def initialize(run, job)
6
+ @run = run
7
+ @job = job
8
+ @workflow = Workflow.new(run, job)
9
+ end
10
+
11
+ def process_run
12
+ # if the run record is already marked as finished, immediately return its result
13
+ return @run.succeeded? if @run.finished?
14
+
15
+ AcidicJob.logger.log_run_event("Processing #{@run.current_step_name}...", @job, @run)
16
+ loop do
17
+ break if @run.finished?
18
+
19
+ if !@run.known_recovery_point?
20
+ raise UnknownRecoveryPoint,
21
+ "Defined workflow does not reference this step: #{@run.current_step_name.inspect}"
22
+ elsif !Array(awaited_jobs = @run.current_step_hash.fetch("awaits", [])).compact.empty?
23
+ # We only execute the current step, without progressing to the next step.
24
+ # This ensures that any failures in parallel jobs will have this step retried in the main workflow
25
+ step_result = @workflow.execute_current_step
26
+ # We allow the `#step_done` method to manage progressing the recovery_point to the next step,
27
+ # and then calling `process_run` to restart the main workflow on the next step.
28
+ # We pass the `step_result` so that the async callback called after the step-parallel-jobs complete
29
+ # can move on to the appropriate next stage in the workflow.
30
+ enqueue_awaited_jobs(awaited_jobs, step_result)
31
+ # after processing the current step, break the processing loop
32
+ # and stop this method from blocking in the primary worker
33
+ # as it will continue once the background workers all succeed
34
+ # so we want to keep the primary worker queue free to process new work
35
+ # this CANNOT ever be `break` as that wouldn't exit the parent job,
36
+ # only this step in the workflow, blocking as it awaits the next step
37
+ return true
38
+ else
39
+ @workflow.execute_current_step
40
+ @workflow.progress_to_next_step
41
+ end
42
+ end
43
+ AcidicJob.logger.log_run_event("Processed #{@run.current_step_name}.", @job, @run)
44
+
45
+ @run.succeeded?
46
+ end
47
+
48
+ private
49
+
50
+ def enqueue_awaited_jobs(jobs_or_jobs_getter, step_result)
51
+ awaited_jobs = jobs_from(jobs_or_jobs_getter)
52
+
53
+ AcidicJob.logger.log_run_event("Enqueuing #{awaited_jobs.count} awaited jobs...", @job, @run)
54
+ # All jobs created in the block are pushed atomically at the end of the block.
55
+ AcidicJob::Run.transaction do
56
+ awaited_jobs.each do |awaited_job|
57
+ worker_class, args = job_args_and_kwargs(awaited_job)
58
+
59
+ job = worker_class.new(*args)
60
+
61
+ AcidicJob::Run.await!(job, by: @run, return_to: step_result)
62
+ end
63
+ end
64
+ AcidicJob.logger.log_run_event("Enqueued #{awaited_jobs.count} awaited jobs.", @job, @run)
65
+ end
66
+
67
+ def jobs_from(jobs_or_jobs_getter)
68
+ case jobs_or_jobs_getter
69
+ when Array
70
+ jobs_or_jobs_getter
71
+ when Symbol, String
72
+ if @job.respond_to?(jobs_or_jobs_getter)
73
+ @job.method(jobs_or_jobs_getter).call
74
+ else
75
+ raise UnknownAwaitedJob,
76
+ "Invalid `awaits`; unknown method `#{jobs_or_jobs_getter}` for this job"
77
+ end
78
+ else
79
+ raise UnknownAwaitedJob,
80
+ "Invalid `awaits`; must be either an jobs Array or method name, was: #{jobs_or_jobs_getter.class.name}"
81
+ end
82
+ end
83
+
84
+ def job_args_and_kwargs(job)
85
+ case job
86
+ when Class
87
+ [job, []]
88
+ else
89
+ [
90
+ job.class,
91
+ job.arguments
92
+ ]
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/railtie"
4
+
5
+ module AcidicJob
6
+ class Rails < ::Rails::Railtie
7
+ initializer "acidic_job.action_mailer_extension" do
8
+ ActiveSupport.on_load(:action_mailer) do
9
+ # Add `deliver_acidicly` to ActionMailer
10
+ if defined?(::ActionMailer)
11
+ ::ActionMailer::Parameterized::MessageDelivery.include(::AcidicJob::Extensions::ActionMailer)
12
+ end
13
+ ::ActionMailer::MessageDelivery.include(::AcidicJob::Extensions::ActionMailer) if defined?(::ActionMailer)
14
+ end
15
+ end
16
+
17
+ generators do
18
+ require "generators/acidic_job/install_generator"
19
+ end
20
+
21
+ # This hook happens after all initializers are run, just before returning
22
+ config.after_initialize do
23
+ if defined?(::Noticed)
24
+ # Add `deliver_acidicly` to Noticed
25
+ ::Noticed::Base.include(Extensions::Noticed)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -4,15 +4,14 @@
4
4
  # return from an #atomic_phase block.
5
5
  module AcidicJob
6
6
  class RecoveryPoint
7
- attr_accessor :name
7
+ attr_reader :name
8
8
 
9
9
  def initialize(name)
10
- self.name = name
10
+ @name = name
11
11
  end
12
12
 
13
- def call(key:)
14
- # Skip AR callbacks as there are none on the model
15
- key.update_column(:recovery_point, name)
13
+ def call(run:)
14
+ run.recover_to!(@name)
16
15
  end
17
16
  end
18
17
  end
@@ -0,0 +1,299 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record"
4
+ require "global_id"
5
+ require "base64"
6
+ require "active_support/core_ext/object/with_options"
7
+ require "active_support/core_ext/module/concerning"
8
+ require "active_support/concern"
9
+
10
+ module AcidicJob
11
+ class Run < ActiveRecord::Base
12
+ include GlobalID::Identification
13
+
14
+ FINISHED_RECOVERY_POINT = "FINISHED"
15
+ STAGED_JOB_ID_PREFIX = "STG"
16
+ STAGED_JOB_ID_DELIMITER = "__"
17
+ IDEMPOTENCY_KEY_LOCK_TIMEOUT_SECONDS = 2
18
+
19
+ self.table_name = "acidic_job_runs"
20
+
21
+ validates :idempotency_key, presence: true
22
+ validate :not_awaited_but_unstaged
23
+
24
+ def self.clear_finished
25
+ # over-write any pre-existing relation queries on `recovery_point` and/or `error_object`
26
+ to_purge = finished
27
+
28
+ count = to_purge.count
29
+
30
+ return 0 if count.zero?
31
+
32
+ AcidicJob.logger.info("Deleting #{count} finished AcidicJob runs")
33
+ to_purge.delete_all
34
+ end
35
+
36
+ def succeeded?
37
+ finished? && !errored?
38
+ end
39
+
40
+ concerning :Awaitable do
41
+ included do
42
+ belongs_to :awaited_by, class_name: "AcidicJob::Run", optional: true
43
+ has_many :batched_runs, class_name: "AcidicJob::Run", foreign_key: "awaited_by_id"
44
+
45
+ scope :awaited, -> { where.not(awaited_by: nil) }
46
+ scope :unawaited, -> { where(awaited_by: nil) }
47
+
48
+ after_update_commit :proceed_with_parent, if: :finished?
49
+
50
+ serialize :returning_to, AcidicJob::Serializer
51
+ end
52
+
53
+ class_methods do
54
+ def await!(job, by:, return_to:)
55
+ create!(
56
+ staged: true,
57
+ awaited_by: by,
58
+ job_class: job.class.name,
59
+ serialized_job: job.serialize,
60
+ idempotency_key: job.idempotency_key
61
+ )
62
+ by.update(returning_to: return_to)
63
+ end
64
+ end
65
+
66
+ def awaited?
67
+ awaited_by.present?
68
+ end
69
+
70
+ private
71
+
72
+ def proceed_with_parent
73
+ return unless finished?
74
+ return unless awaited_by.present?
75
+ return if awaited_by.batched_runs.outstanding.any?
76
+
77
+ AcidicJob.logger.log_run_event("Proceeding with parent job...", job, self)
78
+ awaited_by.unlock!
79
+ awaited_by.proceed
80
+ AcidicJob.logger.log_run_event("Proceeded with parent job.", job, self)
81
+ end
82
+
83
+ protected
84
+
85
+ def proceed
86
+ # this needs to be explicitly set so that `was_workflow_job?` appropriately returns `true`
87
+ # TODO: replace this with some way to check the type of the job directly
88
+ # either via class method or explicit module inclusion
89
+ job.instance_variable_set(:@acidic_job_run, self)
90
+
91
+ workflow = Workflow.new(self, job, returning_to)
92
+ # TODO: WRITE REGRESSION TESTS FOR PARALLEL JOB FAILING AND RETRYING THE ORIGINAL STEP
93
+ workflow.progress_to_next_step
94
+
95
+ # when a batch of jobs for a step succeeds, we begin processing the `AcidicJob::Run` record again
96
+ return if finished?
97
+
98
+ AcidicJob.logger.log_run_event("Re-enqueuing parent job...", job, self)
99
+ enqueue_job
100
+ AcidicJob.logger.log_run_event("Re-enqueued parent job.", job, self)
101
+ end
102
+ end
103
+
104
+ concerning :Stageable do
105
+ included do
106
+ after_create_commit :enqueue_job, if: :staged?
107
+
108
+ validates :staged, inclusion: { in: [true, false] } # uses database default
109
+
110
+ scope :staged, -> { where(staged: true) }
111
+ scope :unstaged, -> { where(staged: false) }
112
+ end
113
+
114
+ class_methods do
115
+ def stage!(job)
116
+ create!(
117
+ staged: true,
118
+ job_class: job.class.name,
119
+ serialized_job: job.serialize,
120
+ idempotency_key: job.try(:idempotency_key) || job.job_id
121
+ )
122
+ end
123
+ end
124
+
125
+ private
126
+
127
+ def job_id
128
+ return idempotency_key unless staged?
129
+
130
+ # encode the identifier for this record in the job ID
131
+ global_id = to_global_id.to_s.remove("gid://")
132
+ # base64 encoding for minimal security
133
+ encoded_global_id = Base64.encode64(global_id).strip
134
+
135
+ [
136
+ STAGED_JOB_ID_PREFIX,
137
+ idempotency_key,
138
+ encoded_global_id
139
+ ].join(STAGED_JOB_ID_DELIMITER)
140
+ end
141
+ end
142
+
143
+ concerning :Workflowable do
144
+ included do
145
+ serialize :workflow, AcidicJob::Serializer
146
+ serialize :error_object, AcidicJob::Serializer
147
+ store :attr_accessors, coder: AcidicJob::Serializer
148
+
149
+ with_options unless: :staged? do
150
+ validates :last_run_at, presence: true
151
+ validates :recovery_point, presence: true
152
+ validates :workflow, presence: true
153
+ end
154
+ end
155
+
156
+ def workflow?
157
+ self[:workflow].present?
158
+ end
159
+
160
+ def attr_accessors
161
+ self[:attr_accessors] || {}
162
+ end
163
+
164
+ def current_step_name
165
+ recovery_point
166
+ end
167
+
168
+ def current_step_hash
169
+ workflow[current_step_name]
170
+ end
171
+
172
+ def next_step_name
173
+ current_step_hash.fetch("then")
174
+ end
175
+
176
+ def next_step_finishes?
177
+ next_step_name.to_s == FINISHED_RECOVERY_POINT
178
+ end
179
+
180
+ def current_step_finished?
181
+ current_step_name.to_s == FINISHED_RECOVERY_POINT
182
+ end
183
+ end
184
+
185
+ concerning :Jobbable do
186
+ included do
187
+ serialize :serialized_job, JSON
188
+
189
+ validates :serialized_job, presence: true
190
+ validates :job_class, presence: true
191
+ end
192
+
193
+ def job
194
+ return @job if defined? @job
195
+
196
+ serialized_job_for_run = serialized_job.merge("job_id" => job_id)
197
+ job_class_for_run = job_class.constantize
198
+
199
+ @job = job_class_for_run.deserialize(serialized_job_for_run)
200
+ end
201
+
202
+ def enqueue_job
203
+ job.enqueue
204
+
205
+ # NOTE: record will be deleted after the job has successfully been performed
206
+ true
207
+ end
208
+ end
209
+
210
+ concerning :Finishable do
211
+ included do
212
+ scope :finished, -> { where(recovery_point: FINISHED_RECOVERY_POINT) }
213
+ scope :outstanding, lambda {
214
+ where.not(recovery_point: FINISHED_RECOVERY_POINT).or(where(recovery_point: [nil, ""]))
215
+ }
216
+ end
217
+
218
+ def finish!
219
+ finish and unlock and save!
220
+ end
221
+
222
+ def finish
223
+ self.recovery_point = FINISHED_RECOVERY_POINT
224
+ self
225
+ end
226
+
227
+ def finished?
228
+ recovery_point.to_s == FINISHED_RECOVERY_POINT
229
+ end
230
+ end
231
+
232
+ concerning :Unlockable do
233
+ included do
234
+ scope :unlocked, -> { where(locked_at: nil) }
235
+ scope :locked, -> { where.not(locked_at: nil) }
236
+ end
237
+
238
+ def unlock!
239
+ unlock and save!
240
+ end
241
+
242
+ def unlock
243
+ self.locked_at = nil
244
+ self
245
+ end
246
+
247
+ def locked?
248
+ locked_at.present?
249
+ end
250
+
251
+ def lock_active?
252
+ return false if locked_at.nil?
253
+
254
+ locked_at > Time.current - IDEMPOTENCY_KEY_LOCK_TIMEOUT_SECONDS
255
+ end
256
+ end
257
+
258
+ concerning :ErrorStoreable do
259
+ included do
260
+ scope :unerrored, -> { where(error_object: nil) }
261
+ scope :errored, -> { where.not(error_object: nil) }
262
+ end
263
+
264
+ def store_error!(error)
265
+ reload and unlock and store_error(error) and save!
266
+ end
267
+
268
+ def store_error(error)
269
+ self.error_object = error
270
+ self
271
+ end
272
+
273
+ def errored?
274
+ error_object.present?
275
+ end
276
+ end
277
+
278
+ concerning :Recoverable do
279
+ def recover_to!(point)
280
+ recover_to(point) and save!
281
+ end
282
+
283
+ def recover_to(point)
284
+ self.recovery_point = point
285
+ self
286
+ end
287
+
288
+ def known_recovery_point?
289
+ workflow.key?(recovery_point)
290
+ end
291
+ end
292
+
293
+ def not_awaited_but_unstaged
294
+ return true unless awaited? && !staged?
295
+
296
+ errors.add(:base, "cannot be awaited by another job but not staged")
297
+ end
298
+ end
299
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module AcidicJob
6
+ class Serializer
7
+ # Used for `serialize` method in ActiveRecord
8
+ class << self
9
+ def load(json)
10
+ return if json.nil? || json.empty?
11
+
12
+ data = JSON.parse(json)
13
+ Arguments.deserialize(data).first
14
+ end
15
+
16
+ def dump(obj)
17
+ data = Arguments.serialize [obj]
18
+ data.to_json
19
+ rescue ActiveJob::SerializationError
20
+ raise UnserializableValue
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_job/serializers/object_serializer"
4
+
5
+ module AcidicJob
6
+ module Serializers
7
+ class ExceptionSerializer < ::ActiveJob::Serializers::ObjectSerializer
8
+ def serialize(exception)
9
+ hash = {
10
+ "class" => exception.class.name,
11
+ "message" => exception.message,
12
+ "cause" => exception.cause,
13
+ "backtrace" => {}
14
+ }
15
+
16
+ exception.backtrace.map do |trace|
17
+ path, _, location = trace.rpartition("/")
18
+
19
+ next if hash["backtrace"].key?(path)
20
+
21
+ hash["backtrace"][path] = location
22
+ end
23
+
24
+ super(hash)
25
+ end
26
+
27
+ def deserialize(hash)
28
+ exception_class = hash["class"].constantize
29
+ exception = exception_class.new(hash["message"])
30
+ exception.set_backtrace(hash["backtrace"].map do |path, location|
31
+ [path, location].join("/")
32
+ end)
33
+ exception
34
+ end
35
+
36
+ def serialize?(argument)
37
+ defined?(Exception) && argument.is_a?(Exception)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_job/serializers/object_serializer"
4
+
5
+ module AcidicJob
6
+ module Serializers
7
+ class FinishedPointSerializer < ::ActiveJob::Serializers::ObjectSerializer
8
+ def serialize(finished_point)
9
+ super(
10
+ "class" => finished_point.class.name
11
+ )
12
+ end
13
+
14
+ def deserialize(hash)
15
+ finished_point_class = hash["class"].constantize
16
+ finished_point_class.new
17
+ end
18
+
19
+ def serialize?(argument)
20
+ defined?(::AcidicJob::FinishedPoint) && argument.is_a?(::AcidicJob::FinishedPoint)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_job/serializers/object_serializer"
4
+
5
+ module AcidicJob
6
+ module Serializers
7
+ class JobSerializer < ::ActiveJob::Serializers::ObjectSerializer
8
+ def serialize(job)
9
+ super(job.serialize)
10
+ end
11
+
12
+ def deserialize(hash)
13
+ job = ActiveJob::Base.deserialize(hash)
14
+ job.send(:deserialize_arguments_if_needed)
15
+ # this is a shim to ensure we can work with Ruby 2.7 as well as 3.0+
16
+ # :nocov:
17
+ if job.arguments.last.is_a?(Hash)
18
+ *args, kwargs = job.arguments
19
+ else
20
+ args = job.arguments
21
+ kwargs = {}
22
+ end
23
+ # :nocov:
24
+ job.instance_variable_set(:@__acidic_job_args, args)
25
+ job.instance_variable_set(:@__acidic_job_kwargs, kwargs)
26
+
27
+ job
28
+ end
29
+
30
+ def serialize?(argument)
31
+ defined?(::ActiveJob::Base) && argument.class < ::ActiveJob::Base
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_job/serializers/object_serializer"
4
+
5
+ # :nocov:
6
+ module AcidicJob
7
+ module Serializers
8
+ class RangeSerializer < ::ActiveJob::Serializers::ObjectSerializer
9
+ KEYS = %w[begin end exclude_end].freeze
10
+
11
+ def serialize(range)
12
+ args = Arguments.serialize([range.begin, range.end, range.exclude_end?])
13
+ super(KEYS.zip(args).to_h)
14
+ end
15
+
16
+ def deserialize(hash)
17
+ klass.new(*Arguments.deserialize(hash.values_at(*KEYS)))
18
+ end
19
+
20
+ private
21
+
22
+ def klass
23
+ ::Range
24
+ end
25
+ end
26
+ end
27
+ end
28
+ # :nocov:
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_job/serializers/object_serializer"
4
+
5
+ module AcidicJob
6
+ module Serializers
7
+ class RecoveryPointSerializer < ::ActiveJob::Serializers::ObjectSerializer
8
+ def serialize(recovery_point)
9
+ super(
10
+ "class" => recovery_point.class.name,
11
+ "name" => recovery_point.name
12
+ )
13
+ end
14
+
15
+ def deserialize(hash)
16
+ recovery_point_class = hash["class"].constantize
17
+ recovery_point_class.new(hash["name"])
18
+ end
19
+
20
+ def serialize?(argument)
21
+ defined?(::AcidicJob::RecoveryPoint) && argument.is_a?(::AcidicJob::RecoveryPoint)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_job/serializers/object_serializer"
4
+
5
+ # :nocov:
6
+ module AcidicJob
7
+ module Serializers
8
+ class WorkerSerializer < ::ActiveJob::Serializers::ObjectSerializer
9
+ def serialize(worker)
10
+ super(
11
+ "job_class" => worker.class.name,
12
+ "arguments" => worker.arguments,
13
+ )
14
+ end
15
+
16
+ def deserialize(hash)
17
+ worker_class = hash["job_class"].constantize
18
+ worker_class.new(*hash["arguments"])
19
+ end
20
+
21
+ def serialize?(argument)
22
+ defined?(::Sidekiq) && argument.class.include?(::Sidekiq::Worker)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ # :nocov: