acidic_job 0.7.7 → 1.0.0.pre3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +21 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +99 -3
- data/README.md +47 -14
- data/UPGRADE_GUIDE.md +71 -0
- data/acidic_job.gemspec +2 -2
- data/bin/console +3 -2
- data/lib/acidic_job/awaiting.rb +68 -0
- data/lib/acidic_job/errors.rb +2 -0
- data/lib/acidic_job/extensions/action_mailer.rb +27 -0
- data/lib/acidic_job/extensions/active_job.rb +39 -0
- data/lib/acidic_job/extensions/noticed.rb +52 -0
- data/lib/acidic_job/extensions/sidekiq.rb +101 -0
- data/lib/acidic_job/{response.rb → finished_point.rb} +4 -4
- data/lib/acidic_job/idempotency_key.rb +24 -0
- data/lib/acidic_job/perform_wrapper.rb +34 -20
- data/lib/acidic_job/recovery_point.rb +3 -3
- data/lib/acidic_job/run.rb +77 -0
- data/lib/acidic_job/staging.rb +30 -0
- data/lib/acidic_job/step.rb +83 -0
- data/lib/acidic_job/upgrade_service.rb +115 -0
- data/lib/acidic_job/version.rb +1 -1
- data/lib/acidic_job.rb +121 -205
- data/lib/generators/acidic_job/drop_tables_generator.rb +31 -0
- data/lib/generators/acidic_job_generator.rb +5 -24
- data/lib/generators/templates/create_acidic_job_runs_migration.rb.erb +19 -0
- data/lib/generators/templates/{create_acidic_job_keys_migration.rb.erb → drop_acidic_job_keys_migration.rb.erb} +10 -3
- metadata +23 -17
- data/lib/acidic_job/deliver_transactionally_extension.rb +0 -26
- data/lib/acidic_job/key.rb +0 -33
- data/lib/acidic_job/no_op.rb +0 -11
- data/lib/acidic_job/perform_transactionally_extension.rb +0 -33
- data/lib/acidic_job/sidekiq_callbacks.rb +0 -45
- data/lib/acidic_job/staged.rb +0 -50
- data/lib/generators/templates/create_staged_acidic_jobs_migration.rb.erb +0 -10
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AcidicJob
|
4
|
+
module Extensions
|
5
|
+
module Noticed
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
class_methods do
|
9
|
+
def deliver_acidicly(recipients)
|
10
|
+
new.deliver_acidicly(recipients)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def deliver_acidicly(recipients)
|
15
|
+
# THIS IS A HACK THAT COPIES AND PASTES KEY PARTS OF THE `Noticed::Base` CODE
|
16
|
+
# IN ORDER TO ALLOW US TO TRANSACTIONALLY DELIVER NOTIFICATIONS
|
17
|
+
# THIS IS THUS LIABLE TO BREAK WHENEVER THAT GEM IS UPDATED
|
18
|
+
delivery_methods = self.class.delivery_methods.dup
|
19
|
+
|
20
|
+
Array.wrap(recipients).uniq.each do |recipient|
|
21
|
+
if (index = delivery_methods.find_index { |m| m[:name] == :database })
|
22
|
+
database_delivery_method = delivery_methods.delete_at(index)
|
23
|
+
self.record = run_delivery_method(database_delivery_method,
|
24
|
+
recipient: recipient,
|
25
|
+
enqueue: false,
|
26
|
+
record: nil)
|
27
|
+
end
|
28
|
+
|
29
|
+
delivery_methods.map do |delivery_method|
|
30
|
+
job_class = delivery_method_for(delivery_method[:name], delivery_method[:options])
|
31
|
+
args = {
|
32
|
+
notification_class: self.class.name,
|
33
|
+
options: delivery_method[:options],
|
34
|
+
params: params,
|
35
|
+
recipient: recipient,
|
36
|
+
record: record
|
37
|
+
}
|
38
|
+
serialized_job = job_class.send(:job_or_instantiate, args).serialize
|
39
|
+
|
40
|
+
AcidicJob::Run.create!(
|
41
|
+
staged: true,
|
42
|
+
job_class: job_class.name,
|
43
|
+
serialized_job: serialized_job,
|
44
|
+
idempotency_key: IdempotencyKey.value_for(serialized_job)
|
45
|
+
)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
alias deliver_transactionally deliver_acidicly
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/concern"
|
4
|
+
require "active_support/callbacks"
|
5
|
+
require "active_support/core_ext/module/concerning"
|
6
|
+
|
7
|
+
module AcidicJob
|
8
|
+
module Extensions
|
9
|
+
module Sidekiq
|
10
|
+
extend ActiveSupport::Concern
|
11
|
+
|
12
|
+
concerning :Serialization do
|
13
|
+
class_methods do
|
14
|
+
# called only from `AcidicJob::Run#enqueue_staged_job`
|
15
|
+
def deserialize(serialized_job_hash)
|
16
|
+
klass = serialized_job_hash["class"].constantize
|
17
|
+
worker = klass.new
|
18
|
+
worker.jid = serialized_job_hash["jid"]
|
19
|
+
worker.instance_variable_set(:@args, serialized_job_hash["args"])
|
20
|
+
|
21
|
+
worker
|
22
|
+
end
|
23
|
+
|
24
|
+
# called only from `AcidicJob::PerformAcidicly#perform_acidicly`
|
25
|
+
# and `AcidicJob::DeliverAcidicly#deliver_acidicly`
|
26
|
+
def serialize_with_arguments(args = [], _kwargs = nil)
|
27
|
+
# THIS IS A HACK THAT ESSENTIALLY COPIES THE CODE FROM THE SIDEKIQ CODEBASE TO MIMIC THE BEHAVIOR
|
28
|
+
args = Array[args]
|
29
|
+
normalized_args = ::Sidekiq.load_json(::Sidekiq.dump_json(args))
|
30
|
+
item = { "class" => self, "args" => normalized_args }
|
31
|
+
dummy_sidekiq_client = ::Sidekiq::Client.new
|
32
|
+
normed = dummy_sidekiq_client.send :normalize_item, item
|
33
|
+
dummy_sidekiq_client.send :process_single, item["class"], normed
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def serialize_job(args = [], _kwargs = nil)
|
38
|
+
# `@args` is only set via `deserialize`; it is not a standard Sidekiq thing
|
39
|
+
arguments = args || @args
|
40
|
+
normalized_args = ::Sidekiq.load_json(::Sidekiq.dump_json(arguments))
|
41
|
+
item = { "class" => self.class, "args" => normalized_args, "jid" => jid }
|
42
|
+
sidekiq_options = sidekiq_options_hash || {}
|
43
|
+
|
44
|
+
sidekiq_options.merge(item)
|
45
|
+
end
|
46
|
+
|
47
|
+
# called only from `AcidicJob::Run#enqueue_staged_job`
|
48
|
+
def enqueue
|
49
|
+
::Sidekiq::Client.push(
|
50
|
+
"class" => self.class,
|
51
|
+
"args" => @args,
|
52
|
+
"jid" => @jid
|
53
|
+
)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
concerning :PerformAcidicly do
|
58
|
+
class_methods do
|
59
|
+
def perform_acidicly(*args, **kwargs)
|
60
|
+
serialized_job = serialize_with_arguments(*args, **kwargs)
|
61
|
+
|
62
|
+
AcidicJob::Run.create!(
|
63
|
+
staged: true,
|
64
|
+
job_class: name,
|
65
|
+
serialized_job: serialized_job,
|
66
|
+
idempotency_key: IdempotencyKey.value_for(serialized_job)
|
67
|
+
)
|
68
|
+
end
|
69
|
+
alias_method :perform_transactionally, :perform_acidicly
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# to balance `perform_async` class method
|
74
|
+
concerning :PerformSync do
|
75
|
+
class_methods do
|
76
|
+
def perform_sync(*args, **kwargs)
|
77
|
+
new.perform(*args, **kwargs)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Following approach used by ActiveJob
|
83
|
+
# https://github.com/rails/rails/blob/93c9534c9871d4adad4bc33b5edc355672b59c61/activejob/lib/active_job/callbacks.rb
|
84
|
+
concerning :Callbacks do
|
85
|
+
class_methods do
|
86
|
+
def around_perform(*filters, &blk)
|
87
|
+
set_callback(:perform, :around, *filters, &blk)
|
88
|
+
end
|
89
|
+
|
90
|
+
def before_perform(*filters, &blk)
|
91
|
+
set_callback(:perform, :before, *filters, &blk)
|
92
|
+
end
|
93
|
+
|
94
|
+
def after_perform(*filters, &blk)
|
95
|
+
set_callback(:perform, :after, *filters, &blk)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -4,12 +4,12 @@
|
|
4
4
|
# idempotency key). One possible option for a return from an #atomic_phase
|
5
5
|
# block.
|
6
6
|
module AcidicJob
|
7
|
-
class
|
8
|
-
def call(
|
7
|
+
class FinishedPoint
|
8
|
+
def call(run:)
|
9
9
|
# Skip AR callbacks as there are none on the model
|
10
|
-
|
10
|
+
run.update_columns(
|
11
11
|
locked_at: nil,
|
12
|
-
recovery_point:
|
12
|
+
recovery_point: Run::FINISHED_RECOVERY_POINT
|
13
13
|
)
|
14
14
|
end
|
15
15
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AcidicJob
|
4
|
+
class IdempotencyKey
|
5
|
+
def self.value_for(hash_or_job, *args, **kwargs)
|
6
|
+
return hash_or_job.job_id if hash_or_job.respond_to?(:job_id) && !hash_or_job.job_id.nil?
|
7
|
+
return hash_or_job.jid if hash_or_job.respond_to?(:jid) && !hash_or_job.jid.nil?
|
8
|
+
|
9
|
+
if hash_or_job.is_a?(Hash) && hash_or_job.key?("job_id") && !hash_or_job["job_id"].nil?
|
10
|
+
return hash_or_job["job_id"]
|
11
|
+
end
|
12
|
+
return hash_or_job["jid"] if hash_or_job.is_a?(Hash) && hash_or_job.key?("jid") && !hash_or_job["jid"].nil?
|
13
|
+
|
14
|
+
worker_class = case hash_or_job
|
15
|
+
when Hash
|
16
|
+
hash_or_job["worker"] || hash_or_job["job_class"]
|
17
|
+
else
|
18
|
+
hash_or_job.class.name
|
19
|
+
end
|
20
|
+
|
21
|
+
Digest::SHA1.hexdigest [worker_class, args, kwargs].flatten.join
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -1,35 +1,49 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module AcidicJob
|
4
|
+
# NOTE: it is essential that this be a bare module and not an ActiveSupport::Concern
|
4
5
|
module PerformWrapper
|
5
6
|
def perform(*args, **kwargs)
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
if
|
10
|
-
args
|
11
|
-
|
7
|
+
super_method = method(:perform).super_method
|
8
|
+
|
9
|
+
# we don't want to run the `perform` callbacks twice, since ActiveJob already handles that for us
|
10
|
+
if aj_job?
|
11
|
+
__acidic_job_perform_for_aj(super_method, *args, **kwargs)
|
12
|
+
elsif sk_job?
|
13
|
+
__acidic_job_perform_for_sk(super_method, *args, **kwargs)
|
14
|
+
else
|
15
|
+
raise UnknownJobAdapter
|
12
16
|
end
|
17
|
+
end
|
13
18
|
|
14
|
-
|
19
|
+
def sk_job?
|
20
|
+
defined?(Sidekiq) && self.class.include?(Sidekiq::Worker)
|
21
|
+
end
|
15
22
|
|
16
|
-
|
23
|
+
def aj_job?
|
24
|
+
defined?(ActiveJob) && self.class < ActiveJob::Base
|
17
25
|
end
|
18
26
|
|
19
27
|
private
|
20
28
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
29
|
+
# don't run `perform` callbacks, as ActiveJob already does this
|
30
|
+
def __acidic_job_perform_for_aj(super_method, *args, **kwargs)
|
31
|
+
__acidic_job_perform_base(super_method, *args, **kwargs)
|
32
|
+
end
|
33
|
+
|
34
|
+
# ensure to run `perform` callbacks
|
35
|
+
def __acidic_job_perform_for_sk(super_method, *args, **kwargs)
|
36
|
+
run_callbacks :perform do
|
37
|
+
__acidic_job_perform_base(super_method, *args, **kwargs)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# capture arguments passed to `perform` to be used by AcidicJob later
|
42
|
+
def __acidic_job_perform_base(super_method, *args, **kwargs)
|
43
|
+
@__acidic_job_args = args
|
44
|
+
@__acidic_job_kwargs = kwargs
|
45
|
+
|
46
|
+
super_method.call(*args, **kwargs)
|
33
47
|
end
|
34
48
|
end
|
35
49
|
end
|
@@ -7,12 +7,12 @@ module AcidicJob
|
|
7
7
|
attr_accessor :name
|
8
8
|
|
9
9
|
def initialize(name)
|
10
|
-
|
10
|
+
@name = name
|
11
11
|
end
|
12
12
|
|
13
|
-
def call(
|
13
|
+
def call(run:)
|
14
14
|
# Skip AR callbacks as there are none on the model
|
15
|
-
|
15
|
+
run.update_column(:recovery_point, @name)
|
16
16
|
end
|
17
17
|
end
|
18
18
|
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_record"
|
4
|
+
require "global_id"
|
5
|
+
require "active_support/core_ext/object/with_options"
|
6
|
+
|
7
|
+
module AcidicJob
|
8
|
+
class Run < ActiveRecord::Base
|
9
|
+
include GlobalID::Identification
|
10
|
+
|
11
|
+
FINISHED_RECOVERY_POINT = "FINISHED"
|
12
|
+
|
13
|
+
self.table_name = "acidic_job_runs"
|
14
|
+
|
15
|
+
after_create_commit :enqueue_staged_job, if: :staged?
|
16
|
+
|
17
|
+
serialize :error_object
|
18
|
+
serialize :serialized_job
|
19
|
+
serialize :workflow
|
20
|
+
store :attr_accessors
|
21
|
+
|
22
|
+
validates :staged, inclusion: { in: [true, false] } # uses database default
|
23
|
+
validates :serialized_job, presence: true
|
24
|
+
validates :idempotency_key, presence: true, uniqueness: true
|
25
|
+
validates :job_class, presence: true
|
26
|
+
|
27
|
+
scope :staged, -> { where(staged: true) }
|
28
|
+
scope :unstaged, -> { where(staged: false) }
|
29
|
+
scope :finished, -> { where(recovery_point: FINISHED_RECOVERY_POINT) }
|
30
|
+
scope :running, -> { where.not(recovery_point: FINISHED_RECOVERY_POINT) }
|
31
|
+
|
32
|
+
with_options unless: :staged? do
|
33
|
+
validates :last_run_at, presence: true
|
34
|
+
validates :recovery_point, presence: true
|
35
|
+
validates :workflow, presence: true
|
36
|
+
end
|
37
|
+
|
38
|
+
def finished?
|
39
|
+
recovery_point == FINISHED_RECOVERY_POINT
|
40
|
+
end
|
41
|
+
|
42
|
+
def succeeded?
|
43
|
+
finished? && !failed?
|
44
|
+
end
|
45
|
+
|
46
|
+
def failed?
|
47
|
+
error_object.present?
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def enqueue_staged_job
|
53
|
+
return unless staged?
|
54
|
+
|
55
|
+
# encode the identifier for this record in the job ID
|
56
|
+
# base64 encoding for minimal security
|
57
|
+
global_id = to_global_id.to_s.remove("gid://")
|
58
|
+
encoded_global_id = Base64.encode64(global_id).strip
|
59
|
+
staged_job_id = "STG_#{idempotency_key}__#{encoded_global_id}"
|
60
|
+
|
61
|
+
serialized_staged_job = if serialized_job.key?("jid")
|
62
|
+
serialized_job.merge("jid" => staged_job_id)
|
63
|
+
elsif serialized_job.key?("job_id")
|
64
|
+
serialized_job.merge("job_id" => staged_job_id)
|
65
|
+
else
|
66
|
+
raise UnknownSerializedJobIdentifier
|
67
|
+
end
|
68
|
+
|
69
|
+
job = job_class.constantize.deserialize(serialized_staged_job)
|
70
|
+
|
71
|
+
job.enqueue
|
72
|
+
|
73
|
+
# NOTE: record will be deleted after the job has successfully been performed
|
74
|
+
true
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/concern"
|
4
|
+
|
5
|
+
module AcidicJob
|
6
|
+
module Staging
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
def delete_staged_job_record
|
10
|
+
return unless was_staged_job?
|
11
|
+
|
12
|
+
staged_job_run.delete
|
13
|
+
true
|
14
|
+
rescue ActiveRecord::RecordNotFound
|
15
|
+
true
|
16
|
+
end
|
17
|
+
|
18
|
+
def was_staged_job?
|
19
|
+
identifier.start_with? "STG_"
|
20
|
+
end
|
21
|
+
|
22
|
+
def staged_job_run
|
23
|
+
# "STG_#{idempotency_key}__#{encoded_global_id}"
|
24
|
+
encoded_global_id = identifier.split("__").last
|
25
|
+
staged_job_gid = "gid://#{Base64.decode64(encoded_global_id)}"
|
26
|
+
|
27
|
+
GlobalID::Locator.locate(staged_job_gid)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,83 @@
|
|
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
|
+
@step_result.call(run: @run)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def wrap_step_as_acidic_callable(step)
|
52
|
+
# {"does" => :enqueue_step, "then" => :next_step, "awaits" => [WorkerWithEnqueueStep::FirstWorker]}
|
53
|
+
current_step = step["does"]
|
54
|
+
next_step = step["then"]
|
55
|
+
|
56
|
+
# jobs can have no-op steps, especially so that they can use only the async/await mechanism for that step
|
57
|
+
callable = if @job.respond_to?(current_step, _include_private = true)
|
58
|
+
@job.method(current_step)
|
59
|
+
else
|
60
|
+
proc {}
|
61
|
+
end
|
62
|
+
|
63
|
+
# return a callable Proc with a consistent interface for the execution phase
|
64
|
+
proc do |run|
|
65
|
+
result = if callable.arity.zero?
|
66
|
+
callable.call
|
67
|
+
elsif callable.arity == 1
|
68
|
+
callable.call(run)
|
69
|
+
else
|
70
|
+
raise TooManyParametersForStepMethod
|
71
|
+
end
|
72
|
+
|
73
|
+
if result.is_a?(FinishedPoint)
|
74
|
+
result
|
75
|
+
elsif next_step.to_s == Run::FINISHED_RECOVERY_POINT
|
76
|
+
FinishedPoint.new
|
77
|
+
else
|
78
|
+
RecoveryPoint.new(next_step)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,115 @@
|
|
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
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
29
|
+
def enqueue_job
|
30
|
+
gid = { "staged_job_gid" => to_global_id.to_s }
|
31
|
+
|
32
|
+
if job_args.is_a?(Hash) && job_args.key?("arguments")
|
33
|
+
job_args["arguments"].concat([gid])
|
34
|
+
else
|
35
|
+
job_args.concat([gid])
|
36
|
+
end
|
37
|
+
|
38
|
+
case adapter
|
39
|
+
when "activejob"
|
40
|
+
::ActiveJob::Base.deserialize(job_args).enqueue
|
41
|
+
when "sidekiq"
|
42
|
+
job_name.constantize.perform_async(*job_args)
|
43
|
+
else
|
44
|
+
raise UnknownJobAdapter.new(adapter: adapter)
|
45
|
+
end
|
46
|
+
|
47
|
+
# NOTE: record will be deleted after the job has successfully been performed
|
48
|
+
true
|
49
|
+
end
|
50
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
51
|
+
end
|
52
|
+
|
53
|
+
module UpgradeService
|
54
|
+
def self.execute()
|
55
|
+
# prepare an array to hold the attribute hashes to be passed to `insert_all`
|
56
|
+
run_attributes = []
|
57
|
+
# prepare an array to hold any `Key` records that we couldn't successfully map to `Run` records
|
58
|
+
errored_keys = []
|
59
|
+
|
60
|
+
# iterate over all `AcidicJob::Key` records in batches, preparing a `Run` attribute hash to be passed to `insert_all`
|
61
|
+
::AcidicJob::Key.find_each do |key|
|
62
|
+
# map all of the simple attributes directly
|
63
|
+
attributes = {
|
64
|
+
id: key.id,
|
65
|
+
staged: false,
|
66
|
+
idempotency_key: key.idempotency_key,
|
67
|
+
job_class: key.job_name,
|
68
|
+
last_run_at: key.last_run_at,
|
69
|
+
locked_at: key.locked_at,
|
70
|
+
recovery_point: key.recovery_point,
|
71
|
+
error_object: key.error_object,
|
72
|
+
attr_accessors: key.attr_accessors,
|
73
|
+
workflow: key.workflow,
|
74
|
+
created_at: key.created_at,
|
75
|
+
updated_at: key.updated_at
|
76
|
+
}
|
77
|
+
|
78
|
+
# prepare the more complicated `job_args` -> `serialized_job` translation
|
79
|
+
job_class = key.job_name.constantize
|
80
|
+
if defined?(::Sidekiq) && job_class.include?(::Sidekiq::Worker)
|
81
|
+
job_class.include(::AcidicJob::Extensions::Sidekiq) unless job_class.include?(::AcidicJob::Extensions::Sidekiq)
|
82
|
+
job_instance = job_class.new
|
83
|
+
serialized_job = job_instance.serialize_job(*key.job_args)
|
84
|
+
elsif defined?(::ActiveJob) && job_class < ::ActiveJob::Base
|
85
|
+
job_class.include(::AcidicJob::Extensions::ActiveJob) unless job_class.include?(::AcidicJob::Extensions::ActiveJob)
|
86
|
+
job_args = begin
|
87
|
+
::ActiveJob::Arguments.deserialize(key.job_args)
|
88
|
+
rescue ::ActiveJob::DeserializationError
|
89
|
+
key.job_args
|
90
|
+
end
|
91
|
+
job_instance = job_class.new(*job_args)
|
92
|
+
serialized_job = job_instance.serialize_job()
|
93
|
+
end
|
94
|
+
|
95
|
+
attributes[:serialized_job] = serialized_job
|
96
|
+
run_attributes << attributes
|
97
|
+
rescue StandardError => exception
|
98
|
+
errored_keys << [exception, key]
|
99
|
+
end
|
100
|
+
|
101
|
+
# insert all of the `Run` records
|
102
|
+
::AcidicJob::Run.insert_all(run_attributes)
|
103
|
+
|
104
|
+
# delete all successfully migrated `Key` record
|
105
|
+
::AcidicJob::Key.where(id: ::AcidicJob::Run.select(:id)).delete_all
|
106
|
+
|
107
|
+
# return a report of the upgrade migration
|
108
|
+
{
|
109
|
+
run_records: ::AcidicJob::Run.count,
|
110
|
+
key_records: ::AcidicJob::Key.count,
|
111
|
+
errored_keys: errored_keys
|
112
|
+
}
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
data/lib/acidic_job/version.rb
CHANGED