acidic_job 0.7.6 → 1.0.0.beta.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) 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 +259 -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/recovery_point.rb +4 -5
  32. data/lib/acidic_job/run.rb +299 -0
  33. data/lib/acidic_job/serializer.rb +24 -0
  34. data/lib/acidic_job/serializers/exception_serializer.rb +41 -0
  35. data/lib/acidic_job/serializers/finished_point_serializer.rb +24 -0
  36. data/lib/acidic_job/serializers/job_serializer.rb +35 -0
  37. data/lib/acidic_job/serializers/range_serializer.rb +28 -0
  38. data/lib/acidic_job/serializers/recovery_point_serializer.rb +25 -0
  39. data/lib/acidic_job/serializers/worker_serializer.rb +27 -0
  40. data/lib/acidic_job/testing.rb +73 -0
  41. data/lib/acidic_job/version.rb +1 -1
  42. data/lib/acidic_job/workflow.rb +78 -0
  43. data/lib/acidic_job/workflow_builder.rb +35 -0
  44. data/lib/acidic_job/workflow_step.rb +103 -0
  45. data/lib/acidic_job.rb +31 -334
  46. data/lib/generators/acidic_job/drop_tables_generator.rb +26 -0
  47. data/lib/generators/acidic_job/install_generator.rb +27 -0
  48. data/lib/generators/acidic_job/templates/create_acidic_job_runs_migration.rb.erb +19 -0
  49. data/lib/generators/{templates/create_acidic_job_keys_migration.rb.erb → acidic_job/templates/drop_acidic_job_keys_migration.rb.erb} +10 -3
  50. metadata +213 -20
  51. data/.ruby_version +0 -1
  52. data/lib/acidic_job/deliver_transactionally_extension.rb +0 -26
  53. data/lib/acidic_job/key.rb +0 -33
  54. data/lib/acidic_job/no_op.rb +0 -11
  55. data/lib/acidic_job/perform_transactionally_extension.rb +0 -33
  56. data/lib/acidic_job/sidekiq_callbacks.rb +0 -45
  57. data/lib/acidic_job/staged.rb +0 -50
  58. data/lib/generators/acidic_job_generator.rb +0 -44
  59. data/lib/generators/templates/create_staged_acidic_jobs_migration.rb.erb +0 -10
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AcidicJob
4
- VERSION = "0.7.6"
4
+ VERSION = "1.0.0.beta.2"
5
5
  end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AcidicJob
4
+ class Workflow
5
+ # { "step 1": { does: "step 1", awaits: [], then: "step 2" }, ... }
6
+ def initialize(run, job, step_result = nil)
7
+ @run = run
8
+ @job = job
9
+ @step_result = step_result
10
+ end
11
+
12
+ def execute_current_step
13
+ rescued_error = false
14
+
15
+ begin
16
+ run_current_step
17
+ rescue StandardError => e
18
+ rescued_error = e
19
+ raise e
20
+ ensure
21
+ if rescued_error
22
+ begin
23
+ @run.store_error!(rescued_error)
24
+ rescue StandardError => e
25
+ # We're already inside an error condition, so swallow any additional
26
+ # errors from here and just send them to logs.
27
+ AcidicJob.logger.error("Failed to unlock AcidicJob::Run #{@run.id} because of #{e}.")
28
+ end
29
+ end
30
+ end
31
+
32
+ # be sure to return the `step_result`
33
+ # which is set by `run_current_step`
34
+ # which runs the (wrapped) current step method
35
+ @step_result
36
+ end
37
+
38
+ def progress_to_next_step
39
+ return if @run.current_step_finished?
40
+ return run_step_result unless @run.next_step_finishes?
41
+
42
+ @job.run_callbacks :finish do
43
+ run_step_result
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def run_current_step
50
+ wrapped_method = WorkflowStep.new(run: @run, job: @job).wrapped
51
+ current_step = @run.current_step_name
52
+
53
+ # can't reproduce yet, but saw a bug in production where
54
+ # nested awaits workflows had an unsaved `workflow` attribute
55
+ @run.save! if @run.has_changes_to_save?
56
+
57
+ AcidicJob.logger.log_run_event("Executing #{current_step}...", @job, @run)
58
+ @run.with_lock do
59
+ @step_result = wrapped_method.call(@run)
60
+ end
61
+ AcidicJob.logger.log_run_event("Executed #{current_step}.", @job, @run)
62
+ end
63
+
64
+ def run_step_result
65
+ next_step = @run.next_step_name
66
+
67
+ # can't reproduce yet, but saw a bug in production where
68
+ # nested awaits workflows had an unsaved `workflow` attribute
69
+ @run.save! if @run.has_changes_to_save?
70
+
71
+ AcidicJob.logger.log_run_event("Progressing to #{next_step}...", @job, @run)
72
+ @run.with_lock do
73
+ @step_result.call(run: @run)
74
+ end
75
+ AcidicJob.logger.log_run_event("Progressed to #{next_step}.", @job, @run)
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AcidicJob
4
+ class WorkflowBuilder
5
+ attr_reader :steps
6
+
7
+ def initialize
8
+ @steps = []
9
+ end
10
+
11
+ def step(method_name, awaits: [], for_each: nil)
12
+ @steps << {
13
+ "does" => method_name.to_s,
14
+ "awaits" => awaits,
15
+ "for_each" => for_each
16
+ }
17
+
18
+ @steps
19
+ end
20
+ alias_method "✅", :step
21
+
22
+ def define_workflow
23
+ # [ { does: "step 1", awaits: [] }, { does: "step 2", awaits: [] }, ... ]
24
+ @steps << { "does" => Run::FINISHED_RECOVERY_POINT.to_s }
25
+
26
+ {}.tap do |workflow|
27
+ @steps.each_cons(2).map do |enter_step, exit_step|
28
+ enter_name = enter_step["does"]
29
+ workflow[enter_name] = enter_step.merge("then" => exit_step["does"])
30
+ end
31
+ end
32
+ # { "step 1": { does: "step 1", awaits: [], then: "step 2" }, ... }
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AcidicJob
4
+ class WorkflowStep
5
+ def initialize(run:, job:)
6
+ @run = run
7
+ @job = job
8
+ end
9
+
10
+ def wrapped
11
+ # return a callable Proc with a consistent interface for the execution phase
12
+ proc do |_run|
13
+ current_step_result = process_current_step
14
+
15
+ if current_step_result.is_a?(FinishedPoint)
16
+ current_step_result
17
+ elsif next_item.present?
18
+ @run.attr_accessors[iterated_key] = prev_iterateds + [next_item]
19
+ @run.save!(validate: false)
20
+ RecoveryPoint.new(current_step_name)
21
+ elsif @run.next_step_finishes?
22
+ FinishedPoint.new
23
+ else
24
+ RecoveryPoint.new(@run.next_step_name)
25
+ end
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def process_current_step
32
+ result = nil
33
+
34
+ if iterable_key.present? && next_item.present? # have an item to iterate over, so pass it to the step method
35
+ result = current_callable.call(next_item)
36
+ elsif iterable_key.present? && next_item.nil? # have iterated over all items
37
+ result = true
38
+ elsif current_callable.arity.zero?
39
+ result = current_callable.call
40
+ else
41
+ raise TooManyParametersForStepMethod
42
+ end
43
+
44
+ result
45
+ end
46
+
47
+ def current_callable
48
+ return @job.method(current_step_name) if @job.respond_to?(current_step_name, _include_private = true)
49
+ # jobs can have no-op steps, especially so that they can use only the async/await mechanism for that step
50
+ return proc {} if @run.current_step_hash["awaits"].present?
51
+
52
+ raise UndefinedStepMethod
53
+ end
54
+
55
+ def iterable_key
56
+ # the `iterable_key` represents the name of the collection accessor
57
+ # that must be present in `@run.attr_accessors`; that is,
58
+ # it must have been passed to `persisting` when calling `with_acidic_workflow`
59
+ for_each = @run.current_step_hash["for_each"]
60
+
61
+ return unless for_each.present?
62
+
63
+ return for_each if @run.attr_accessors.key?(for_each)
64
+
65
+ raise UnknownForEachCollection
66
+ end
67
+
68
+ def iterated_key
69
+ # in order to ensure we don't iterate over successfully iterated values in previous runs,
70
+ # we need to store the collection of already processed values.
71
+ # we store this collection under a key bound to the current step to ensure multiple steps
72
+ # can iterate over the same collection.
73
+ "processed_#{current_step_name}_#{iterable_key}"
74
+ end
75
+
76
+ def prev_iterables
77
+ # The collection of values to iterate over
78
+ iterables = @run.attr_accessors.fetch(iterable_key, [])
79
+
80
+ return Array(iterables) if iterables.is_a?(Enumerable)
81
+
82
+ raise UniterableForEachCollection
83
+ end
84
+
85
+ def prev_iterateds
86
+ # The collection of values already iterated over
87
+ iterateds = @run.attr_accessors.fetch(iterated_key, [])
88
+
89
+ Array(iterateds)
90
+ end
91
+
92
+ def next_item
93
+ # The collection of values to iterate over now
94
+ curr_iterables = prev_iterables - prev_iterateds
95
+
96
+ curr_iterables.first
97
+ end
98
+
99
+ def current_step_name
100
+ @run.current_step_name
101
+ end
102
+ end
103
+ end
data/lib/acidic_job.rb CHANGED
@@ -2,342 +2,39 @@
2
2
 
3
3
  require_relative "acidic_job/version"
4
4
  require_relative "acidic_job/errors"
5
- require_relative "acidic_job/no_op"
5
+ require_relative "acidic_job/logger"
6
+ require_relative "acidic_job/arguments"
7
+ require_relative "acidic_job/serializer"
8
+ require_relative "acidic_job/workflow_builder"
9
+ require_relative "acidic_job/idempotency_key"
6
10
  require_relative "acidic_job/recovery_point"
7
- require_relative "acidic_job/response"
8
- require_relative "acidic_job/key"
9
- require_relative "acidic_job/staged"
11
+ require_relative "acidic_job/finished_point"
12
+ require_relative "acidic_job/run"
13
+ require_relative "acidic_job/workflow_step"
14
+ require_relative "acidic_job/workflow"
15
+ require_relative "acidic_job/processor"
10
16
  require_relative "acidic_job/perform_wrapper"
11
- require_relative "acidic_job/perform_transactionally_extension"
12
- require_relative "acidic_job/deliver_transactionally_extension"
13
- require_relative "acidic_job/sidekiq_callbacks"
14
- require "active_support/concern"
17
+ require_relative "acidic_job/mixin"
18
+ require_relative "acidic_job/base"
19
+ require_relative "acidic_job/active_kiq"
20
+ require_relative "acidic_job/extensions/action_mailer"
21
+ require_relative "acidic_job/extensions/noticed"
22
+
23
+ require_relative "acidic_job/serializers/exception_serializer"
24
+ require_relative "acidic_job/serializers/finished_point_serializer"
25
+ require_relative "acidic_job/serializers/job_serializer"
26
+ require_relative "acidic_job/serializers/range_serializer"
27
+ require_relative "acidic_job/serializers/recovery_point_serializer"
28
+ require_relative "acidic_job/serializers/worker_serializer"
29
+ require "active_job/serializers"
15
30
 
16
- # rubocop:disable Metrics/ModuleLength, Metrics/AbcSize, Metrics/MethodLength
17
31
  module AcidicJob
18
- extend ActiveSupport::Concern
19
-
20
- def self.wire_everything_up(klass)
21
- klass.attr_reader :key
22
- klass.attr_reader :staged_job_gid
23
- klass.attr_reader :arguments_for_perform
24
-
25
- # Extend ActiveJob with `perform_transactionally` class method
26
- klass.include PerformTransactionallyExtension
27
-
28
- ActionMailer::Parameterized::MessageDelivery.include DeliverTransactionallyExtension if defined?(ActionMailer)
29
-
30
- # Ensure our `perform` method always runs first to gather parameters
31
- klass.prepend PerformWrapper
32
-
33
- klass.prepend SidekiqCallbacks unless klass.respond_to?(:after_perform)
34
-
35
- klass.after_perform :delete_staged_job_record, if: :staged_job_gid
36
- end
37
-
38
- included do
39
- AcidicJob.wire_everything_up(self)
40
- end
41
-
42
- class_methods do
43
- def inherited(subclass)
44
- AcidicJob.wire_everything_up(subclass)
45
- super
46
- end
47
-
48
- def initiate(*args)
49
- operation = Sidekiq::Batch.new
50
- operation.on(:success, self, *args)
51
- operation.jobs do
52
- perform_async
53
- end
54
- end
55
- end
56
-
57
- # Number of seconds passed which we consider a held idempotency key lock to be
58
- # defunct and eligible to be locked again by a different job run. We try to
59
- # unlock keys on our various failure conditions, but software is buggy, and
60
- # this might not happen 100% of the time, so this is a hedge against it.
61
- IDEMPOTENCY_KEY_LOCK_TIMEOUT = 90
62
-
63
- # takes a block
64
- def with_acidity(given:)
65
- # execute the block to gather the info on what steps are defined for this job workflow
66
- steps = yield || []
67
-
68
- raise NoDefinedSteps if steps.empty?
69
-
70
- # convert the array of steps into a hash of recovery_points and next steps
71
- workflow = define_workflow(steps)
72
-
73
- # find or create a Key record (our idempotency key) to store all information about this job
74
- #
75
- # A key concept here is that if two requests try to insert or update within
76
- # close proximity, one of the two will be aborted by Postgres because we're
77
- # using a transaction with SERIALIZABLE isolation level. It may not look
78
- # it, but this code is safe from races.
79
- key = ensure_idempotency_key_record(idempotency_key_value, workflow, given)
80
-
81
- # begin the workflow
82
- process_key(key)
83
- end
84
-
85
- # DEPRECATED
86
- def idempotently(with:)
87
- with_acidity(given: with)
88
- end
89
-
90
- def process_key(key)
91
- @key = key
92
-
93
- # if the key record is already marked as finished, immediately return its result
94
- return @key.succeeded? if @key.finished?
95
-
96
- # otherwise, we will enter a loop to process each step of the workflow
97
- @key.workflow.size.times do
98
- recovery_point = @key.recovery_point.to_s
99
- current_step = @key.workflow[recovery_point]
100
-
101
- if recovery_point == Key::RECOVERY_POINT_FINISHED.to_s # rubocop:disable Style/GuardClause
102
- break
103
- elsif current_step.nil?
104
- raise UnknownRecoveryPoint, "Defined workflow does not reference this step: #{recovery_point}"
105
- elsif (jobs = current_step.fetch("awaits", [])).any?
106
- acidic_step @key, current_step
107
- # THIS MUST BE DONE AFTER THE KEY RECOVERY POINT HAS BEEN UPDATED
108
- enqueue_step_parallel_jobs(jobs)
109
- # after processing the current step, break the processing loop
110
- # and stop this method from blocking in the primary worker
111
- # as it will continue once the background workers all succeed
112
- # so we want to keep the primary worker queue free to process new work
113
- # this CANNOT ever be `break` as that wouldn't exit the parent job,
114
- # only this step in the workflow, blocking as it awaits the next step
115
- return true
116
- else
117
- acidic_step @key, current_step
118
- end
119
- end
120
-
121
- # the loop will break once the job is finished, so simply report the status
122
- @key.succeeded?
123
- end
124
-
125
- def step(method_name, awaits: [])
126
- @_steps ||= []
127
-
128
- @_steps << {
129
- "does" => method_name.to_s,
130
- "awaits" => awaits
131
- }
132
-
133
- @_steps
134
- end
135
-
136
- def safely_finish_acidic_job
137
- # Short circuits execution by sending execution right to 'finished'.
138
- # So, ends the job "successfully"
139
- AcidicJob::Response.new
140
- end
141
-
142
- private
143
-
144
- def delete_staged_job_record
145
- return unless staged_job_gid
146
-
147
- staged_job = GlobalID::Locator.locate(staged_job_gid)
148
- staged_job.delete
149
- true
150
- rescue ActiveRecord::RecordNotFound
151
- true
152
- end
153
-
154
- def define_workflow(steps)
155
- steps << { "does" => Key::RECOVERY_POINT_FINISHED }
156
-
157
- {}.tap do |workflow|
158
- steps.each_cons(2).map do |enter_step, exit_step|
159
- enter_name = enter_step["does"]
160
- workflow[enter_name] = {
161
- "then" => exit_step["does"]
162
- }.merge(enter_step)
163
- end
164
- end
165
- end
166
-
167
- def ensure_idempotency_key_record(key_val, workflow, accessors)
168
- isolation_level = case ActiveRecord::Base.connection.adapter_name.downcase.to_sym
169
- when :sqlite
170
- :read_uncommitted
171
- else
172
- :serializable
173
- end
174
-
175
- ActiveRecord::Base.transaction(isolation: isolation_level) do
176
- key = Key.find_by(idempotency_key: key_val)
177
-
178
- if key.present?
179
- # Programs enqueuing multiple jobs with different parameters but the
180
- # same idempotency key is a bug.
181
- raise MismatchedIdempotencyKeyAndJobArguments if key.job_args != @arguments_for_perform
182
-
183
- # Only acquire a lock if the key is unlocked or its lock has expired
184
- # because the original job was long enough ago.
185
- raise LockedIdempotencyKey if key.locked_at && key.locked_at > Time.current - IDEMPOTENCY_KEY_LOCK_TIMEOUT
186
-
187
- # Lock the key and update latest run unless the job is already finished.
188
- key.update!(last_run_at: Time.current, locked_at: Time.current, workflow: workflow) unless key.finished?
189
- else
190
- key = Key.create!(
191
- idempotency_key: key_val,
192
- locked_at: Time.current,
193
- last_run_at: Time.current,
194
- recovery_point: workflow.first.first,
195
- job_name: self.class.name,
196
- job_args: @arguments_for_perform,
197
- workflow: workflow
198
- )
199
- end
200
-
201
- # set accessors for each argument passed in to ensure they are available
202
- # to the step methods the job will have written
203
- define_accessors_for_passed_arguments(accessors, key)
204
-
205
- # NOTE: we must return the `key` object from this transaction block
206
- # so that it can be returned from this method to the caller
207
- key
208
- end
209
- end
210
-
211
- def acidic_step(key, step)
212
- rescued_error = false
213
- step_callable = wrap_step_as_acidic_callable step
214
-
215
- begin
216
- key.with_lock do
217
- step_result = step_callable.call(key)
218
-
219
- step_result.call(key: key)
220
- end
221
- # QUESTION: Can an error not inherit from StandardError
222
- rescue StandardError => e
223
- rescued_error = e
224
- raise e
225
- ensure
226
- if rescued_error
227
- # If we're leaving under an error condition, try to unlock the idempotency
228
- # key right away so that another request can try again.3
229
- begin
230
- key.update_columns(locked_at: nil, error_object: rescued_error)
231
- rescue StandardError => e
232
- # We're already inside an error condition, so swallow any additional
233
- # errors from here and just send them to logs.
234
- puts "Failed to unlock key #{key.id} because of #{e}."
235
- end
236
- end
237
- end
238
- end
239
-
240
- def define_accessors_for_passed_arguments(passed_arguments, key)
241
- # first, get the current state of all accessors for both previously persisted and initialized values
242
- current_accessors = passed_arguments.stringify_keys.merge(key.attr_accessors)
243
-
244
- # next, ensure that `Key#attr_accessors` is populated with initial values
245
- key.update_column(:attr_accessors, current_accessors)
246
-
247
- current_accessors.each do |accessor, value|
248
- # the reader method may already be defined
249
- self.class.attr_reader accessor unless respond_to?(accessor)
250
- # but we should always update the value to match the current value
251
- instance_variable_set("@#{accessor}", value)
252
- # and we overwrite the setter to ensure any updates to an accessor update the `Key` stored value
253
- # Note: we must define the singleton method on the instance to avoid overwriting setters on other
254
- # instances of the same class
255
- define_singleton_method("#{accessor}=") do |current_value|
256
- instance_variable_set("@#{accessor}", current_value)
257
- key.attr_accessors[accessor] = current_value
258
- key.save!(validate: false)
259
- current_value
260
- end
261
- end
262
-
263
- true
264
- end
265
-
266
- # rubocop:disable Metrics/PerceivedComplexity
267
- def wrap_step_as_acidic_callable(step)
268
- # {:then=>:next_step, :does=>:enqueue_step, :awaits=>[WorkerWithEnqueueStep::FirstWorker]}
269
- current_step = step["does"]
270
- next_step = step["then"]
271
-
272
- callable = if respond_to? current_step, _include_private = true
273
- method(current_step)
274
- else
275
- proc {} # no-op
276
- end
277
-
278
- proc do |key|
279
- result = if callable.arity.zero?
280
- callable.call
281
- elsif callable.arity == 1
282
- callable.call(key)
283
- else
284
- raise TooManyParametersForStepMethod
285
- end
286
-
287
- if result.is_a?(Response)
288
- result
289
- elsif next_step.to_s == Key::RECOVERY_POINT_FINISHED
290
- Response.new
291
- else
292
- RecoveryPoint.new(next_step)
293
- end
294
- end
295
- end
296
- # rubocop:enable Metrics/PerceivedComplexity
297
-
298
- def enqueue_step_parallel_jobs(jobs)
299
- # TODO: GIVE PROPER ERROR
300
- # `batch` is available from Sidekiq::Pro
301
- raise SidekiqBatchRequired unless defined?(Sidekiq::Batch)
302
-
303
- batch.jobs do
304
- step_batch = Sidekiq::Batch.new
305
- # step_batch.description = "AcidicJob::Workflow Step: #{step}"
306
- step_batch.on(
307
- :success,
308
- "#{self.class.name}#step_done",
309
- # NOTE: options are marshalled through JSON so use only basic types.
310
- { "key_id" => @key.id }
311
- )
312
- # NOTE: The jobs method is atomic.
313
- # All jobs created in the block are actually pushed atomically at the end of the block.
314
- # If an error is raised, none of the jobs will go to Redis.
315
- step_batch.jobs do
316
- jobs.each do |worker_name|
317
- worker = worker_name.is_a?(String) ? worker_name.constantize : worker_name
318
- if worker.instance_method(:perform).arity.zero?
319
- worker.perform_async
320
- elsif worker.instance_method(:perform).arity == 1
321
- worker.perform_async(key.id)
322
- else
323
- raise TooManyParametersForParallelJob
324
- end
325
- end
326
- end
327
- end
328
- end
329
-
330
- def idempotency_key_value
331
- return job_id if defined?(job_id) && !job_id.nil?
332
- return jid if defined?(jid) && !jid.nil?
333
-
334
- Digest::SHA1.hexdigest [self.class.name, arguments_for_perform].flatten.join
335
- end
336
-
337
- def step_done(_status, options)
338
- key = Key.find(options["key_id"])
339
- # when a batch of jobs for a step succeeds, we begin the key processing again
340
- process_key(key)
341
- end
32
+ ::ActiveJob::Serializers.add_serializers(
33
+ Serializers::ExceptionSerializer,
34
+ Serializers::FinishedPointSerializer,
35
+ Serializers::JobSerializer,
36
+ Serializers::RangeSerializer,
37
+ Serializers::RecoveryPointSerializer,
38
+ Serializers::WorkerSerializer
39
+ )
342
40
  end
343
- # rubocop:enable Metrics/ModuleLength, Metrics/AbcSize, Metrics/MethodLength
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators/active_record"
4
+
5
+ module AcidicJob
6
+ module Generators
7
+ class DropTablesGenerator < ::Rails::Generators::Base
8
+ include ActiveRecord::Generators::Migration
9
+ source_root File.expand_path("templates", __dir__)
10
+
11
+ desc "Drops the pre-1.0 tables for the AcidicJob::Key and AcidicJob::Staged models."
12
+
13
+ def copy_migration
14
+ migration_template "drop_acidic_job_keys_migration.rb.erb",
15
+ "db/migrate/drop_old_acidic_job_tables.rb",
16
+ migration_version: migration_version
17
+ end
18
+
19
+ protected
20
+
21
+ def migration_version
22
+ "[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators/active_record"
4
+
5
+ module AcidicJob
6
+ module Generators
7
+ class InstallGenerator < ::Rails::Generators::Base
8
+ include ActiveRecord::Generators::Migration
9
+ source_root File.expand_path("templates", __dir__)
10
+
11
+ desc "Generates a migration for the AcidicJob::Run table."
12
+
13
+ # Copies the migration template to db/migrate.
14
+ def copy_acidic_job_runs_migration_files
15
+ migration_template "create_acidic_job_runs_migration.rb.erb",
16
+ "db/migrate/create_acidic_job_runs.rb",
17
+ migration_version: migration_version
18
+ end
19
+
20
+ protected
21
+
22
+ def migration_version
23
+ "[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,19 @@
1
+ class <%= migration_class_name %> < ActiveRecord::Migration<%= migration_version %>
2
+ def change
3
+ create_table :acidic_job_runs do |t|
4
+ t.boolean :staged, null: false, default: -> { false }
5
+ t.string :idempotency_key, null: false, index: { unique: true }
6
+ t.text :serialized_job, null: false
7
+ t.string :job_class, null: false
8
+ t.datetime :last_run_at, null: true, default: -> { "CURRENT_TIMESTAMP" }
9
+ t.datetime :locked_at, null: true
10
+ t.string :recovery_point, null: true
11
+ t.text :error_object, null: true
12
+ t.text :attr_accessors, null: true
13
+ t.text :workflow, null: true
14
+ t.references :awaited_by, null: true, index: true
15
+ t.text :returning_to, null: true
16
+ t.timestamps
17
+ end
18
+ end
19
+ end