acidic_job 1.0.0.pre29 → 1.0.0.rc2

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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/.codacy.yml +4 -0
  3. data/.github/FUNDING.yml +13 -0
  4. data/.github/workflows/main.yml +12 -15
  5. data/.gitignore +3 -1
  6. data/.rubocop.yml +50 -5
  7. data/.ruby-version +1 -0
  8. data/Gemfile.lock +134 -193
  9. data/README.md +164 -246
  10. data/TODO +77 -0
  11. data/acidic_job.gemspec +10 -10
  12. data/app/models/acidic_job/entry.rb +19 -0
  13. data/app/models/acidic_job/execution.rb +50 -0
  14. data/app/models/acidic_job/record.rb +11 -0
  15. data/app/models/acidic_job/value.rb +7 -0
  16. data/bin/console +5 -2
  17. data/bin/test_all +26 -0
  18. data/gemfiles/rails_7.0.gemfile +4 -1
  19. data/gemfiles/rails_7.1.gemfile +11 -0
  20. data/gemfiles/rails_7.2.gemfile +11 -0
  21. data/gemfiles/rails_8.0.gemfile +11 -0
  22. data/lib/acidic_job/arguments.rb +31 -0
  23. data/lib/acidic_job/builder.rb +29 -0
  24. data/lib/acidic_job/context.rb +46 -0
  25. data/lib/acidic_job/engine.rb +46 -0
  26. data/lib/acidic_job/errors.rb +87 -12
  27. data/lib/acidic_job/log_subscriber.rb +50 -0
  28. data/lib/acidic_job/serializers/exception_serializer.rb +31 -0
  29. data/lib/acidic_job/serializers/job_serializer.rb +27 -0
  30. data/lib/acidic_job/serializers/new_record_serializer.rb +25 -0
  31. data/lib/acidic_job/serializers/range_serializer.rb +28 -0
  32. data/lib/acidic_job/testing.rb +8 -12
  33. data/lib/acidic_job/version.rb +1 -1
  34. data/lib/acidic_job/workflow.rb +182 -0
  35. data/lib/acidic_job.rb +15 -284
  36. data/lib/generators/acidic_job/install_generator.rb +3 -3
  37. data/lib/generators/acidic_job/templates/create_acidic_job_tables_migration.rb.erb +33 -0
  38. metadata +51 -95
  39. data/.ruby_version +0 -1
  40. data/.tool-versions +0 -1
  41. data/gemfiles/rails_6.1.gemfile +0 -8
  42. data/lib/acidic_job/awaiting.rb +0 -102
  43. data/lib/acidic_job/extensions/action_mailer.rb +0 -29
  44. data/lib/acidic_job/extensions/active_job.rb +0 -40
  45. data/lib/acidic_job/extensions/noticed.rb +0 -54
  46. data/lib/acidic_job/extensions/sidekiq.rb +0 -111
  47. data/lib/acidic_job/finished_point.rb +0 -16
  48. data/lib/acidic_job/idempotency_key.rb +0 -82
  49. data/lib/acidic_job/perform_wrapper.rb +0 -22
  50. data/lib/acidic_job/recovery_point.rb +0 -18
  51. data/lib/acidic_job/rspec_configuration.rb +0 -31
  52. data/lib/acidic_job/run.rb +0 -100
  53. data/lib/acidic_job/serializer.rb +0 -163
  54. data/lib/acidic_job/staging.rb +0 -38
  55. data/lib/acidic_job/step.rb +0 -104
  56. data/lib/acidic_job/test_case.rb +0 -9
  57. data/lib/acidic_job/upgrade_service.rb +0 -118
  58. data/lib/generators/acidic_job/drop_tables_generator.rb +0 -26
  59. data/lib/generators/acidic_job/templates/create_acidic_job_runs_migration.rb.erb +0 -19
  60. data/lib/generators/acidic_job/templates/drop_acidic_job_keys_migration.rb.erb +0 -27
@@ -1,82 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module AcidicJob
4
- class IdempotencyKey
5
- def initialize(identifier = :job_id)
6
- @identifier = identifier
7
- end
8
-
9
- def value_for(hash_or_job, *args, **kwargs)
10
- value = case @identifier
11
- when Proc
12
- value_from_proc(hash_or_job, *args, **kwargs)
13
- when :job_args
14
- value_from_job_args(hash_or_job, *args, **kwargs)
15
- else
16
- if hash_or_job.is_a?(Hash)
17
- value_from_job_id_for_hash(hash_or_job)
18
- else
19
- value_from_job_id_for_obj(hash_or_job)
20
- end
21
- end
22
-
23
- result = value || value_from_job_args(hash_or_job, *args, **kwargs)
24
-
25
- if result.start_with?("STG__")
26
- # "STG__#{idempotency_key}__#{encoded_global_id}"
27
- _prefix, idempotency_key, _encoded_global_id = result.split("__")
28
- idempotency_key
29
- else
30
- result
31
- end
32
- end
33
-
34
- private
35
-
36
- def value_from_job_id_for_hash(hash)
37
- if hash.key?("job_id")
38
- return if hash["job_id"].nil?
39
- return if hash["job_id"].empty?
40
-
41
- hash["job_id"]
42
- elsif hash.key?("jid")
43
- return if hash["jid"].nil?
44
- return if hash["jid"].empty?
45
-
46
- hash["jid"]
47
- end
48
- end
49
-
50
- def value_from_job_id_for_obj(obj)
51
- if obj.respond_to?(:job_id)
52
- return if obj.job_id.nil?
53
- return if obj.job_id.empty?
54
-
55
- obj.job_id
56
- elsif obj.respond_to?(:jid)
57
- return if obj.jid.nil?
58
- return if obj.jid.empty?
59
-
60
- obj.jid
61
- end
62
- end
63
-
64
- def value_from_job_args(hash_or_job, *args, **kwargs)
65
- worker_class = case hash_or_job
66
- when Hash
67
- hash_or_job["worker"] || hash_or_job["job_class"]
68
- else
69
- hash_or_job.class.name
70
- end
71
-
72
- Digest::SHA1.hexdigest [worker_class, args, kwargs].flatten.join
73
- end
74
-
75
- def value_from_proc(_hash_or_job, *args, **kwargs)
76
- return if args.empty? && kwargs.empty?
77
-
78
- idempotency_args = Array(@identifier.call(*args, **kwargs))
79
- Digest::SHA1.hexdigest idempotency_args.flatten.join
80
- end
81
- end
82
- end
@@ -1,22 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module AcidicJob
4
- # NOTE: it is essential that this be a bare module and not an ActiveSupport::Concern
5
- module PerformWrapper
6
- def perform(*args, **kwargs)
7
- @__acidic_job_args = args
8
- @__acidic_job_kwargs = kwargs
9
-
10
- # we don't want to run the `perform` callbacks twice, since ActiveJob already handles that for us
11
- if defined?(ActiveJob) && self.class < ActiveJob::Base
12
- super(*args, **kwargs)
13
- elsif defined?(Sidekiq) && self.class.include?(Sidekiq::Worker)
14
- run_callbacks :perform do
15
- super(*args, **kwargs)
16
- end
17
- else
18
- raise UnknownJobAdapter
19
- end
20
- end
21
- end
22
- end
@@ -1,18 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Represents an action to set a new recovery point. One possible option for a
4
- # return from an #atomic_phase block.
5
- module AcidicJob
6
- class RecoveryPoint
7
- attr_accessor :name
8
-
9
- def initialize(name)
10
- @name = name
11
- end
12
-
13
- def call(run:)
14
- # Skip AR callbacks as there are none on the model
15
- run.update_column(:recovery_point, @name)
16
- end
17
- end
18
- end
@@ -1,31 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "rspec"
4
- require "database_cleaner/active_record"
5
-
6
- # see https://github.com/DatabaseCleaner/database_cleaner#how-to-use
7
- RSpec.configure do |config|
8
- config.use_transactional_fixtures = false
9
-
10
- config.before(:suite) do
11
- DatabaseCleaner.clean_with :truncation
12
-
13
- # Here we are defaulting to :transaction but swapping to deletion for some specs;
14
- # if your spec or its code-under-test uses
15
- # nested transactions then specify :transactional e.g.:
16
- # describe "SomeWorker", :transactional do
17
- #
18
- DatabaseCleaner.strategy = :transaction
19
-
20
- config.before(:context, transactional: true) { DatabaseCleaner.strategy = :deletion }
21
- config.after(:context, transactional: true) { DatabaseCleaner.strategy = :transaction }
22
- config.before(:context, type: :system) { DatabaseCleaner.strategy = :deletion }
23
- config.after(:context, type: :system) { DatabaseCleaner.strategy = :transaction }
24
- end
25
-
26
- config.around(:each) do |example|
27
- DatabaseCleaner.cleaning do
28
- example.run
29
- end
30
- end
31
- end
@@ -1,100 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "active_record"
4
- require "global_id"
5
- require "active_support/core_ext/object/with_options"
6
- require_relative "./serializer"
7
-
8
- module AcidicJob
9
- class Run < ActiveRecord::Base
10
- include GlobalID::Identification
11
-
12
- FINISHED_RECOVERY_POINT = "FINISHED"
13
-
14
- self.table_name = "acidic_job_runs"
15
-
16
- belongs_to :awaited_by, class_name: "AcidicJob::Run", optional: true
17
- has_many :batched_runs, class_name: "AcidicJob::Run", foreign_key: "awaited_by_id"
18
-
19
- after_create_commit :enqueue_staged_job, if: :staged?
20
-
21
- serialize :serialized_job, JSON
22
- serialize :error_object, Serializer
23
- serialize :workflow, Serializer
24
- serialize :returning_to, Serializer
25
- store :attr_accessors, coder: Serializer
26
-
27
- validates :staged, inclusion: { in: [true, false] } # uses database default
28
- validates :serialized_job, presence: true
29
- validates :idempotency_key, presence: true, uniqueness: true
30
- validates :job_class, presence: true
31
-
32
- scope :staged, -> { where(staged: true) }
33
- scope :unstaged, -> { where(staged: false) }
34
- scope :finished, -> { where(recovery_point: FINISHED_RECOVERY_POINT) }
35
- scope :outstanding, lambda {
36
- where.not(recovery_point: FINISHED_RECOVERY_POINT).or(where(recovery_point: [nil, ""]))
37
- }
38
-
39
- with_options unless: :staged? do
40
- validates :last_run_at, presence: true
41
- validates :recovery_point, presence: true
42
- validates :workflow, presence: true
43
- end
44
-
45
- def self.purge
46
- successfully_completed = where(
47
- recovery_point: FINISHED_RECOVERY_POINT,
48
- error_object: nil
49
- )
50
- count = successfully_completed.count
51
-
52
- return 0 if count.zero?
53
-
54
- Rails.logger.info("Deleting #{count} successfully completed AcidicJob runs")
55
- successfully_completed.delete_all
56
- end
57
-
58
- def finished?
59
- recovery_point == FINISHED_RECOVERY_POINT
60
- end
61
-
62
- def succeeded?
63
- finished? && !failed?
64
- end
65
-
66
- def failed?
67
- error_object.present?
68
- end
69
-
70
- def staged_job_id
71
- # encode the identifier for this record in the job ID
72
- # base64 encoding for minimal security
73
- global_id = to_global_id.to_s.remove("gid://")
74
- encoded_global_id = Base64.encode64(global_id).strip
75
-
76
- "STG__#{idempotency_key}__#{encoded_global_id}"
77
- end
78
-
79
- private
80
-
81
- def enqueue_staged_job
82
- return unless staged?
83
-
84
- serialized_staged_job = if serialized_job.key?("jid")
85
- serialized_job.merge("jid" => staged_job_id)
86
- elsif serialized_job.key?("job_id")
87
- serialized_job.merge("job_id" => staged_job_id)
88
- else
89
- raise UnknownSerializedJobIdentifier
90
- end
91
-
92
- job = job_class.constantize.deserialize(serialized_staged_job)
93
-
94
- job.enqueue
95
-
96
- # NOTE: record will be deleted after the job has successfully been performed
97
- true
98
- end
99
- end
100
- end
@@ -1,163 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "active_job/serializers"
4
- require "active_job/arguments"
5
- require "json"
6
-
7
- class WorkerSerializer < ActiveJob::Serializers::ObjectSerializer
8
- def serialize(worker)
9
- super(
10
- "class" => worker.class.name,
11
- "args" => worker.instance_variable_get(:@__acidic_job_args),
12
- "kwargs" => worker.instance_variable_get(:@__acidic_job_kwargs)
13
- )
14
- end
15
-
16
- def deserialize(hash)
17
- worker_class = hash["class"].constantize
18
- worker_class.new(*hash["args"], **hash["kwargs"])
19
- end
20
-
21
- def serialize?(argument)
22
- defined?(::Sidekiq) && argument.class.include?(::Sidekiq::Worker)
23
- end
24
- end
25
-
26
- class JobSerializer < ActiveJob::Serializers::ObjectSerializer
27
- def serialize(job)
28
- super(job.serialize)
29
- end
30
-
31
- def deserialize(hash)
32
- job = ActiveJob::Base.deserialize(hash)
33
- job.send(:deserialize_arguments_if_needed)
34
- if job.arguments.last.is_a?(Hash)
35
- *args, kwargs = job.arguments
36
- else
37
- args = job.arguments
38
- kwargs = {}
39
- end
40
- job.instance_variable_set(:@__acidic_job_args, args)
41
- job.instance_variable_set(:@__acidic_job_kwargs, kwargs)
42
-
43
- job
44
- end
45
-
46
- def serialize?(argument)
47
- defined?(::ActiveJob::Base) && argument.class < ::ActiveJob::Base
48
- end
49
- end
50
-
51
- class ExceptionSerializer < ActiveJob::Serializers::ObjectSerializer
52
- def serialize(exception)
53
- hash = {
54
- "class" => exception.class.name,
55
- "message" => exception.message,
56
- "cause" => exception.cause,
57
- "backtrace" => {}
58
- }
59
-
60
- exception.backtrace.map do |trace|
61
- path, _, location = trace.rpartition("/")
62
-
63
- next if hash["backtrace"].key?(path)
64
-
65
- hash["backtrace"][path] = location
66
- end
67
-
68
- super(hash)
69
- end
70
-
71
- def deserialize(hash)
72
- exception_class = hash["class"].constantize
73
- exception = exception_class.new(hash["message"])
74
- exception.set_backtrace(hash["backtrace"].map do |path, location|
75
- [path, location].join("/")
76
- end)
77
- exception
78
- end
79
-
80
- def serialize?(argument)
81
- defined?(Exception) && argument.is_a?(Exception)
82
- end
83
- end
84
-
85
- class FinishedPointSerializer < ActiveJob::Serializers::ObjectSerializer
86
- def serialize(finished_point)
87
- super(
88
- "class" => finished_point.class.name
89
- )
90
- end
91
-
92
- def deserialize(hash)
93
- finished_point_class = hash["class"].constantize
94
- finished_point_class.new
95
- end
96
-
97
- def serialize?(argument)
98
- defined?(::AcidicJob::FinishedPoint) && argument.is_a?(::AcidicJob::FinishedPoint)
99
- end
100
- end
101
-
102
- class RecoveryPointSerializer < ActiveJob::Serializers::ObjectSerializer
103
- def serialize(recovery_point)
104
- super(
105
- "class" => recovery_point.class.name,
106
- "name" => recovery_point.name
107
- )
108
- end
109
-
110
- def deserialize(hash)
111
- recovery_point_class = hash["class"].constantize
112
- recovery_point_class.new(hash["name"])
113
- end
114
-
115
- def serialize?(argument)
116
- defined?(::AcidicJob::RecoveryPoint) && argument.is_a?(::AcidicJob::RecoveryPoint)
117
- end
118
- end
119
-
120
- ActiveJob::Serializers.add_serializers(
121
- WorkerSerializer,
122
- JobSerializer,
123
- ExceptionSerializer,
124
- FinishedPointSerializer,
125
- RecoveryPointSerializer
126
- )
127
-
128
- # ...
129
- module AcidicJob
130
- module Arguments
131
- include ActiveJob::Arguments
132
- extend self # rubocop:disable Style/ModuleFunction
133
-
134
- # `ActiveJob` will throw an error if it tries to deserialize a GlobalID record.
135
- # However, this isn't the behavior that we want for our custom `ActiveRecord` serializer.
136
- # Since `ActiveRecord` does _not_ reset instance record state to its pre-transactional state
137
- # on a transaction ROLLBACK, we can have GlobalID entries in a serialized column that point to
138
- # non-persisted records. This is ok. We should simply return `nil` for that portion of the
139
- # serialized field.
140
- def deserialize_global_id(hash)
141
- GlobalID::Locator.locate hash[GLOBALID_KEY]
142
- rescue ActiveRecord::RecordNotFound
143
- nil
144
- end
145
- end
146
-
147
- class Serializer
148
- # Used for `serialize` method in ActiveRecord
149
- class << self
150
- def load(json)
151
- return if json.nil? || json.empty?
152
-
153
- data = JSON.parse(json)
154
- Arguments.deserialize(data).first
155
- end
156
-
157
- def dump(obj)
158
- data = Arguments.serialize [obj]
159
- data.to_json
160
- end
161
- end
162
- end
163
- end
@@ -1,38 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "active_support/concern"
4
- require "global_id/locator"
5
-
6
- module AcidicJob
7
- module Staging
8
- extend ActiveSupport::Concern
9
-
10
- private
11
-
12
- def was_staged_job?
13
- identifier.start_with? "STG__"
14
- end
15
-
16
- def staged_job_run
17
- # "STG_#{idempotency_key}__#{encoded_global_id}"
18
- encoded_global_id = identifier.split("__").last
19
- staged_job_gid = "gid://#{Base64.decode64(encoded_global_id)}"
20
-
21
- GlobalID::Locator.locate(staged_job_gid)
22
- rescue ActiveRecord::RecordNotFound
23
- nil
24
- end
25
-
26
- def identifier
27
- return jid if defined?(jid) && !jid.nil?
28
- return job_id if defined?(job_id) && !job_id.nil?
29
-
30
- # might be defined already in `with_acidity` method
31
- acidic_identifier = self.class.acidic_identifier
32
- @__acidic_job_idempotency_key ||= IdempotencyKey.new(acidic_identifier)
33
- .value_for(self, *@__acidic_job_args, **@__acidic_job_kwargs)
34
-
35
- @__acidic_job_idempotency_key
36
- end
37
- end
38
- end
@@ -1,104 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module AcidicJob
4
- # Each AcidicJob::Step requires two phases: [1] execution and [2] progression
5
- class Step
6
- def initialize(step, run, job, step_result = nil)
7
- @step = step
8
- @run = run
9
- @job = job
10
- @step_result = step_result
11
- end
12
-
13
- # The execution phase performs the work of the defined step
14
- def execute
15
- rescued_error = false
16
- step_callable = wrap_step_as_acidic_callable @step
17
-
18
- begin
19
- @run.with_lock do
20
- @step_result = step_callable.call(@run)
21
- end
22
- # QUESTION: Can an error not inherit from StandardError
23
- rescue StandardError => e
24
- rescued_error = e
25
- raise e
26
- ensure
27
- if rescued_error
28
- # If we're leaving under an error condition, try to unlock the job
29
- # run right away so that another request can try again.
30
- begin
31
- @run.update_columns(locked_at: nil, error_object: rescued_error)
32
- rescue StandardError => e
33
- # We're already inside an error condition, so swallow any additional
34
- # errors from here and just send them to logs.
35
- # TODO: implement and use a logger here
36
- puts "Failed to unlock AcidicJob::Run #{@run.id} because of #{e}."
37
- end
38
- end
39
- end
40
- end
41
-
42
- # The progression phase advances the job run state machine onto the next step
43
- def progress
44
- @run.with_lock do
45
- if @step_result.is_a?(FinishedPoint)
46
- @job.run_callbacks :finish do
47
- @step_result.call(run: @run)
48
- end
49
- else
50
- @step_result.call(run: @run)
51
- end
52
- end
53
- end
54
-
55
- private
56
-
57
- def wrap_step_as_acidic_callable(step)
58
- # {"does" => :enqueue_step, "then" => :next_step, "awaits" => [WorkerWithEnqueueStep::FirstWorker]}
59
- current_step = step["does"]
60
- next_step = step["then"]
61
- # to support iteration within steps
62
- iterable_key = step["for_each"]
63
- iterated_key = "processed_#{current_step}_#{iterable_key}"
64
- iterables = @run.attr_accessors.fetch(iterable_key, []) || []
65
- iterateds = @run.attr_accessors.fetch(iterated_key, []) || []
66
- next_item = iterables.reject { |item| iterateds.include? item }.first
67
-
68
- # jobs can have no-op steps, especially so that they can use only the async/await mechanism for that step
69
- callable = if @job.respond_to?(current_step, _include_private = true)
70
- @job.method(current_step)
71
- else
72
- proc {}
73
- end
74
-
75
- # return a callable Proc with a consistent interface for the execution phase
76
- proc do |run|
77
- result = if iterable_key.present? && next_item.present?
78
- callable.call(next_item)
79
- elsif iterable_key.present? && next_item.nil?
80
- true
81
- elsif callable.arity.zero?
82
- callable.call
83
- elsif callable.arity == 1
84
- callable.call(run)
85
- else
86
- raise TooManyParametersForStepMethod
87
- end
88
-
89
- if result.is_a?(FinishedPoint)
90
- result
91
- elsif next_item.present?
92
- iterateds << next_item
93
- @run.attr_accessors[iterated_key] = iterateds
94
- @run.save!(validate: false)
95
- RecoveryPoint.new(current_step)
96
- elsif next_step.to_s == Run::FINISHED_RECOVERY_POINT
97
- FinishedPoint.new
98
- else
99
- RecoveryPoint.new(next_step)
100
- end
101
- end
102
- end
103
- end
104
- end
@@ -1,9 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "./testing"
4
-
5
- module AcidicJob
6
- class TestCase < ActiveJob::TestCase
7
- include AcidicJob::Testing
8
- end
9
- end
@@ -1,118 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "active_support/concern"
4
-
5
- module AcidicJob
6
- # recreate the original `Key` model
7
- class Key < ::ActiveRecord::Base
8
- RECOVERY_POINT_FINISHED = "FINISHED"
9
-
10
- self.table_name = "acidic_job_keys"
11
-
12
- serialize :error_object
13
- serialize :job_args
14
- serialize :workflow
15
- store :attr_accessors
16
- end
17
-
18
- # recreate the original `Staged` model
19
- class Staged < ActiveRecord::Base
20
- self.table_name = "staged_acidic_jobs"
21
-
22
- serialize :job_args
23
-
24
- after_create_commit :enqueue_job
25
-
26
- private
27
-
28
- def enqueue_job
29
- gid = { "staged_job_gid" => to_global_id.to_s }
30
-
31
- if job_args.is_a?(Hash) && job_args.key?("arguments")
32
- job_args["arguments"].concat([gid])
33
- else
34
- job_args.concat([gid])
35
- end
36
-
37
- case adapter
38
- when "activejob"
39
- ::ActiveJob::Base.deserialize(job_args).enqueue
40
- when "sidekiq"
41
- job_name.constantize.perform_async(*job_args)
42
- else
43
- raise UnknownJobAdapter.new(adapter: adapter)
44
- end
45
-
46
- # NOTE: record will be deleted after the job has successfully been performed
47
- true
48
- end
49
- end
50
-
51
- module UpgradeService
52
- def self.execute
53
- # prepare an array to hold the attribute hashes to be passed to `insert_all`
54
- run_attributes = []
55
- # prepare an array to hold any `Key` records that we couldn't successfully map to `Run` records
56
- errored_keys = []
57
-
58
- # iterate over all `AcidicJob::Key` records in batches,
59
- # preparing a `Run` attribute hash to be passed to `insert_all`
60
- ::AcidicJob::Key.find_each do |key|
61
- # map all of the simple attributes directly
62
- attributes = {
63
- id: key.id,
64
- staged: false,
65
- idempotency_key: key.idempotency_key,
66
- job_class: key.job_name,
67
- last_run_at: key.last_run_at,
68
- locked_at: key.locked_at,
69
- recovery_point: key.recovery_point,
70
- error_object: key.error_object,
71
- attr_accessors: key.attr_accessors,
72
- workflow: key.workflow,
73
- created_at: key.created_at,
74
- updated_at: key.updated_at
75
- }
76
-
77
- # prepare the more complicated `job_args` -> `serialized_job` translation
78
- job_class = key.job_name.constantize
79
- if defined?(::Sidekiq) && job_class.include?(::Sidekiq::Worker)
80
- unless job_class.include?(::AcidicJob::Extensions::Sidekiq)
81
- job_class.include(::AcidicJob::Extensions::Sidekiq)
82
- end
83
- job_instance = job_class.new
84
- serialized_job = job_instance.serialize_job(*key.job_args)
85
- elsif defined?(::ActiveJob) && job_class < ::ActiveJob::Base
86
- unless job_class.include?(::AcidicJob::Extensions::ActiveJob)
87
- job_class.include(::AcidicJob::Extensions::ActiveJob)
88
- end
89
- job_args = begin
90
- ::ActiveJob::Arguments.deserialize(key.job_args)
91
- rescue ::ActiveJob::DeserializationError
92
- key.job_args
93
- end
94
- job_instance = job_class.new(*job_args)
95
- serialized_job = job_instance.serialize_job
96
- end
97
-
98
- attributes[:serialized_job] = serialized_job
99
- run_attributes << attributes
100
- rescue StandardError => e
101
- errored_keys << [e, key]
102
- end
103
-
104
- # insert all of the `Run` records
105
- ::AcidicJob::Run.insert_all(run_attributes)
106
-
107
- # delete all successfully migrated `Key` record
108
- ::AcidicJob::Key.where(id: ::AcidicJob::Run.select(:id)).delete_all
109
-
110
- # return a report of the upgrade migration
111
- {
112
- run_records: ::AcidicJob::Run.count,
113
- key_records: ::AcidicJob::Key.count,
114
- errored_keys: errored_keys
115
- }
116
- end
117
- end
118
- end