acidic_job 0.7.7 → 1.0.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/acidic_job.rb CHANGED
@@ -2,37 +2,49 @@
2
2
 
3
3
  require_relative "acidic_job/version"
4
4
  require_relative "acidic_job/errors"
5
- require_relative "acidic_job/no_op"
6
5
  require_relative "acidic_job/recovery_point"
7
- require_relative "acidic_job/response"
8
- require_relative "acidic_job/key"
9
- require_relative "acidic_job/staged"
6
+ require_relative "acidic_job/finished_point"
7
+ require_relative "acidic_job/run"
8
+ require_relative "acidic_job/step"
9
+ require_relative "acidic_job/staging"
10
+ require_relative "acidic_job/awaiting"
10
11
  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"
12
+ require_relative "acidic_job/idempotency_key"
13
+ require_relative "acidic_job/extensions/sidekiq"
14
+ require_relative "acidic_job/extensions/action_mailer"
15
+ require_relative "acidic_job/extensions/active_job"
16
+ require_relative "acidic_job/extensions/noticed"
17
+
14
18
  require "active_support/concern"
15
19
 
16
- # rubocop:disable Metrics/ModuleLength, Metrics/AbcSize, Metrics/MethodLength
17
20
  module AcidicJob
18
21
  extend ActiveSupport::Concern
19
22
 
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)
23
+ IDEMPOTENCY_KEY_LOCK_TIMEOUT = 90
29
24
 
25
+ def self.wire_everything_up(klass)
30
26
  # Ensure our `perform` method always runs first to gather parameters
31
27
  klass.prepend PerformWrapper
32
28
 
33
- klass.prepend SidekiqCallbacks unless klass.respond_to?(:after_perform)
29
+ klass.include Staging
30
+ klass.include Awaiting
31
+
32
+ # Add `deliver_acidicly` to ActionMailer
33
+ ActionMailer::Parameterized::MessageDelivery.include Extensions::ActionMailer if defined?(ActionMailer)
34
+ # Add `deliver_acidicly` to Noticed
35
+ Noticed::Base.include Extensions::Noticed if defined?(Noticed)
36
+
37
+ if defined?(ActiveJob) && klass < ActiveJob::Base
38
+ klass.send(:include, Extensions::ActiveJob)
39
+ elsif defined?(Sidekiq) && klass.include?(Sidekiq::Worker)
40
+ klass.send(:include, Extensions::Sidekiq)
41
+ klass.include ActiveSupport::Callbacks
42
+ klass.define_callbacks :perform
43
+ else
44
+ raise UnknownJobAdapter
45
+ end
34
46
 
35
- klass.after_perform :delete_staged_job_record, if: :staged_job_gid
47
+ klass.set_callback :perform, :after, :delete_staged_job_record, if: :was_staged_job?
36
48
  end
37
49
 
38
50
  included do
@@ -44,68 +56,77 @@ module AcidicJob
44
56
  AcidicJob.wire_everything_up(subclass)
45
57
  super
46
58
  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
59
  end
56
60
 
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:)
61
+ def with_acidity(given: {})
65
62
  # execute the block to gather the info on what steps are defined for this job workflow
63
+ @__acidic_job_steps = []
66
64
  steps = yield || []
67
65
 
68
- raise NoDefinedSteps if steps.empty?
66
+ # check that the block actually defined at least one step
67
+ # TODO: WRITE TESTS FOR FAULTY BLOCK VALUES
68
+ raise NoDefinedSteps if @__acidic_job_steps.nil? || @__acidic_job_steps.empty?
69
69
 
70
70
  # convert the array of steps into a hash of recovery_points and next steps
71
71
  workflow = define_workflow(steps)
72
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)
73
+ # determine the idempotency key value for this job run (`job_id` or `jid`)
74
+ # might be defined already in `identifier` method
75
+ # TODO: allow idempotency to be defined by args OR job id
76
+ @__acidic_job_idempotency_key ||= IdempotencyKey.value_for(self, @__acidic_job_args, @__acidic_job_kwargs)
77
+
78
+ @run = ensure_run_record(@__acidic_job_idempotency_key, workflow, given)
80
79
 
81
80
  # begin the workflow
82
- process_key(key)
81
+ process_run(@run)
83
82
  end
84
-
83
+
85
84
  # DEPRECATED
86
85
  def idempotently(with:, &blk)
87
86
  with_acidity(given: with, &blk)
88
- end
87
+ end
89
88
 
90
- def process_key(key)
91
- @key = key
89
+ def safely_finish_acidic_job
90
+ # Short circuits execution by sending execution right to 'finished'.
91
+ # So, ends the job "successfully"
92
+ FinishedPoint.new
93
+ end
94
+
95
+ # rubocop:disable Naming/MemoizedInstanceVariableName
96
+ def idempotency_key
97
+ return @__acidic_job_idempotency_key if defined? @__acidic_job_idempotency_key
98
+
99
+ @__acidic_job_idempotency_key ||= IdempotencyKey.value_for(self, @__acidic_job_args, @__acidic_job_kwargs)
100
+ end
101
+ # rubocop:enable Naming/MemoizedInstanceVariableName
92
102
 
93
- # if the key record is already marked as finished, immediately return its result
94
- return @key.succeeded? if @key.finished?
103
+ private
104
+
105
+ def process_run(run)
106
+ # if the run record is already marked as finished, immediately return its result
107
+ return run.succeeded? if run.finished?
95
108
 
96
109
  # 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]
110
+ run.workflow.size.times do
111
+ recovery_point = run.recovery_point.to_s
112
+ current_step = run.workflow[recovery_point]
100
113
 
101
- if recovery_point == Key::RECOVERY_POINT_FINISHED.to_s # rubocop:disable Style/GuardClause
114
+ # if any step calls `safely_finish_acidic_job` or the workflow has simply completed,
115
+ # be sure to break out of the loop
116
+ if recovery_point == Run::FINISHED_RECOVERY_POINT.to_s # rubocop:disable Style/GuardClause
102
117
  break
103
118
  elsif current_step.nil?
104
119
  raise UnknownRecoveryPoint, "Defined workflow does not reference this step: #{recovery_point}"
105
120
  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)
121
+ step = Step.new(current_step, run, self)
122
+ # Only execute the current step, without yet progressing the recovery_point to the next step.
123
+ # This ensures that any failures in parallel jobs will have this step retried in the main workflow
124
+ step_result = step.execute
125
+ # We allow the `#step_done` method to manage progressing the recovery_point to the next step,
126
+ # and then calling `process_run` to restart the main workflow on the next step.
127
+ # We pass the `step_result` so that the async callback called after the step-parallel-jobs complete
128
+ # can move on to the appropriate next stage in the workflow.
129
+ enqueue_step_parallel_jobs(jobs, run, step_result)
109
130
  # after processing the current step, break the processing loop
110
131
  # and stop this method from blocking in the primary worker
111
132
  # as it will continue once the background workers all succeed
@@ -114,57 +135,42 @@ module AcidicJob
114
135
  # only this step in the workflow, blocking as it awaits the next step
115
136
  return true
116
137
  else
117
- acidic_step @key, current_step
138
+ step = Step.new(current_step, run, self)
139
+ step.execute
140
+ # As this step does not await any parallel jobs, we can immediately progress to the next step
141
+ step.progress
118
142
  end
119
143
  end
120
144
 
121
145
  # the loop will break once the job is finished, so simply report the status
122
- @key.succeeded?
146
+ run.succeeded?
123
147
  end
124
148
 
125
149
  def step(method_name, awaits: [])
126
- @_steps ||= []
150
+ @__acidic_job_steps ||= []
127
151
 
128
- @_steps << {
152
+ @__acidic_job_steps << {
129
153
  "does" => method_name.to_s,
130
154
  "awaits" => awaits
131
155
  }
132
156
 
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
157
+ @__acidic_job_steps
152
158
  end
153
159
 
154
160
  def define_workflow(steps)
155
- steps << { "does" => Key::RECOVERY_POINT_FINISHED }
161
+ # [ { does: "step 1", awaits: [] }, { does: "step 2", awaits: [] }, ... ]
162
+ steps << { "does" => Run::FINISHED_RECOVERY_POINT }
156
163
 
157
164
  {}.tap do |workflow|
158
165
  steps.each_cons(2).map do |enter_step, exit_step|
159
166
  enter_name = enter_step["does"]
160
- workflow[enter_name] = {
161
- "then" => exit_step["does"]
162
- }.merge(enter_step)
167
+ workflow[enter_name] = enter_step.merge("then" => exit_step["does"])
163
168
  end
164
169
  end
170
+ # { "step 1": { does: "step 1", awaits: [], then: "step 2" }, ... }
165
171
  end
166
172
 
167
- def ensure_idempotency_key_record(key_val, workflow, accessors)
173
+ def ensure_run_record(key_val, workflow, accessors)
168
174
  isolation_level = case ActiveRecord::Base.connection.adapter_name.downcase.to_sym
169
175
  when :sqlite
170
176
  :read_uncommitted
@@ -173,76 +179,53 @@ module AcidicJob
173
179
  end
174
180
 
175
181
  ActiveRecord::Base.transaction(isolation: isolation_level) do
176
- key = Key.find_by(idempotency_key: key_val)
182
+ run = Run.find_by(idempotency_key: key_val)
183
+ serialized_job = serialize_job(@__acidic_job_args, @__acidic_job_kwargs)
177
184
 
178
- if key.present?
185
+ if run.present?
179
186
  # Programs enqueuing multiple jobs with different parameters but the
180
187
  # same idempotency key is a bug.
181
- raise MismatchedIdempotencyKeyAndJobArguments if key.job_args != @arguments_for_perform
188
+ # NOTE: WOULD THE ENQUEUED_AT OR CREATED_AT FIELD BE NECESSARILY DIFFERENT?
189
+ if run.serialized_job.except("jid", "job_id",
190
+ "enqueued_at") != serialized_job.except("jid", "job_id", "enqueued_at")
191
+ raise MismatchedIdempotencyKeyAndJobArguments
192
+ end
182
193
 
183
194
  # Only acquire a lock if the key is unlocked or its lock has expired
184
195
  # because the original job was long enough ago.
185
- raise LockedIdempotencyKey if key.locked_at && key.locked_at > Time.current - IDEMPOTENCY_KEY_LOCK_TIMEOUT
196
+ raise LockedIdempotencyKey if run.locked_at && run.locked_at > Time.current - IDEMPOTENCY_KEY_LOCK_TIMEOUT
186
197
 
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?
198
+ # Lock the run and update latest run unless the job is already finished.
199
+ run.update!(last_run_at: Time.current, locked_at: Time.current, workflow: workflow) unless run.finished?
189
200
  else
190
- key = Key.create!(
201
+ run = Run.create!(
202
+ staged: false,
191
203
  idempotency_key: key_val,
204
+ job_class: self.class.name,
192
205
  locked_at: Time.current,
193
206
  last_run_at: Time.current,
194
207
  recovery_point: workflow.first.first,
195
- job_name: self.class.name,
196
- job_args: @arguments_for_perform,
197
- workflow: workflow
208
+ workflow: workflow,
209
+ serialized_job: serialized_job
198
210
  )
199
211
  end
200
212
 
201
213
  # set accessors for each argument passed in to ensure they are available
202
214
  # to the step methods the job will have written
203
- define_accessors_for_passed_arguments(accessors, key)
215
+ define_accessors_for_passed_arguments(accessors, run)
204
216
 
205
217
  # NOTE: we must return the `key` object from this transaction block
206
218
  # 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
219
+ run
237
220
  end
238
221
  end
239
222
 
240
- def define_accessors_for_passed_arguments(passed_arguments, key)
223
+ def define_accessors_for_passed_arguments(passed_arguments, run)
241
224
  # 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)
225
+ current_accessors = passed_arguments.stringify_keys.merge(run.attr_accessors)
243
226
 
244
- # next, ensure that `Key#attr_accessors` is populated with initial values
245
- key.update_column(:attr_accessors, current_accessors)
227
+ # next, ensure that `Run#attr_accessors` is populated with initial values
228
+ run.update_column(:attr_accessors, current_accessors)
246
229
 
247
230
  current_accessors.each do |accessor, value|
248
231
  # the reader method may already be defined
@@ -254,8 +237,8 @@ module AcidicJob
254
237
  # instances of the same class
255
238
  define_singleton_method("#{accessor}=") do |current_value|
256
239
  instance_variable_set("@#{accessor}", current_value)
257
- key.attr_accessors[accessor] = current_value
258
- key.save!(validate: false)
240
+ run.attr_accessors[accessor] = current_value
241
+ run.save!(validate: false)
259
242
  current_value
260
243
  end
261
244
  end
@@ -263,81 +246,13 @@ module AcidicJob
263
246
  true
264
247
  end
265
248
 
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?
249
+ def identifier
332
250
  return jid if defined?(jid) && !jid.nil?
251
+ return job_id if defined?(job_id) && !job_id.nil?
333
252
 
334
- Digest::SHA1.hexdigest [self.class.name, arguments_for_perform].flatten.join
335
- end
253
+ # might be defined already in `with_acidity` method
254
+ @__acidic_job_idempotency_key ||= IdempotencyKey.value_for(self, @__acidic_job_args, @__acidic_job_kwargs)
336
255
 
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)
256
+ @__acidic_job_idempotency_key
341
257
  end
342
258
  end
343
- # rubocop:enable Metrics/ModuleLength, Metrics/AbcSize, Metrics/MethodLength
@@ -6,13 +6,13 @@ require "rails/generators/active_record"
6
6
  class AcidicJobGenerator < ActiveRecord::Generators::Base
7
7
  # ActiveRecord::Generators::Base inherits from Rails::Generators::NamedBase
8
8
  # which requires a NAME parameter for the new table name.
9
- # Our generator always uses "acidic_job_keys", so we just set a random name here.
9
+ # Our generator always uses "acidic_job_runs", so we just set a random name here.
10
10
  argument :name, type: :string, default: "random_name"
11
11
 
12
12
  source_root File.expand_path("templates", __dir__)
13
13
 
14
14
  def self.next_migration_number(_path)
15
- if instance_variable_defined?("@prev_migration_nr")
15
+ if instance_variable_defined?("@prev_migration_nr") # :nocov:
16
16
  @prev_migration_nr += 1
17
17
  else
18
18
  @prev_migration_nr = Time.now.utc.strftime("%Y%m%d%H%M%S").to_i
@@ -22,23 +22,14 @@ class AcidicJobGenerator < ActiveRecord::Generators::Base
22
22
  end
23
23
 
24
24
  # Copies the migration template to db/migrate.
25
- def copy_acidic_job_keys_migration_files
26
- migration_template "create_acidic_job_keys_migration.rb.erb",
27
- "db/migrate/create_acidic_job_keys.rb"
28
- end
29
-
30
- def copy_staged_acidic_jobs_migration_files
31
- migration_template "create_staged_acidic_jobs_migration.rb.erb",
32
- "db/migrate/create_staged_acidic_jobs.rb"
25
+ def copy_acidic_job_runs_migration_files
26
+ migration_template "create_acidic_job_runs_migration.rb.erb",
27
+ "db/migrate/create_acidic_job_runs.rb"
33
28
  end
34
29
 
35
30
  protected
36
31
 
37
32
  def migration_class
38
- if ActiveRecord::VERSION::MAJOR >= 5
39
- ActiveRecord::Migration["#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}"]
40
- else
41
- ActiveRecord::Migration
42
- end
33
+ ActiveRecord::Migration["#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}"]
43
34
  end
44
35
  end
@@ -0,0 +1,19 @@
1
+ class CreateAcidicJobKeys < <%= migration_class %>
2
+ def change
3
+ create_table :acidic_job_runs, force: true do |t|
4
+ t.boolean :staged, null: false, default: -> { false }
5
+ t.string :idempotency_key, null: false
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.timestamps
15
+
16
+ t.index :idempotency_key, unique: true
17
+ end
18
+ end
19
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: acidic_job
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.7
4
+ version: 1.0.0.pre1
5
5
  platform: ruby
6
6
  authors:
7
7
  - fractaledmind
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-11-19 00:00:00.000000000 Z
11
+ date: 2022-03-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 4.0.0
19
+ version: 6.1.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 4.0.0
26
+ version: 6.1.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: activesupport
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -44,14 +44,14 @@ dependencies:
44
44
  requirements:
45
45
  - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: '4.0'
47
+ version: 6.1.0
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
- version: '4.0'
54
+ version: 6.1.0
55
55
  description: Idempotent operations for Rails apps, built on top of ActiveJob.
56
56
  email:
57
57
  - stephen.margheim@gmail.com
@@ -73,20 +73,22 @@ files:
73
73
  - bin/setup
74
74
  - blog_post.md
75
75
  - lib/acidic_job.rb
76
- - lib/acidic_job/deliver_transactionally_extension.rb
76
+ - lib/acidic_job/awaiting.rb
77
77
  - lib/acidic_job/errors.rb
78
- - lib/acidic_job/key.rb
79
- - lib/acidic_job/no_op.rb
80
- - lib/acidic_job/perform_transactionally_extension.rb
78
+ - lib/acidic_job/extensions/action_mailer.rb
79
+ - lib/acidic_job/extensions/active_job.rb
80
+ - lib/acidic_job/extensions/noticed.rb
81
+ - lib/acidic_job/extensions/sidekiq.rb
82
+ - lib/acidic_job/finished_point.rb
83
+ - lib/acidic_job/idempotency_key.rb
81
84
  - lib/acidic_job/perform_wrapper.rb
82
85
  - lib/acidic_job/recovery_point.rb
83
- - lib/acidic_job/response.rb
84
- - lib/acidic_job/sidekiq_callbacks.rb
85
- - lib/acidic_job/staged.rb
86
+ - lib/acidic_job/run.rb
87
+ - lib/acidic_job/staging.rb
88
+ - lib/acidic_job/step.rb
86
89
  - lib/acidic_job/version.rb
87
90
  - lib/generators/acidic_job_generator.rb
88
- - lib/generators/templates/create_acidic_job_keys_migration.rb.erb
89
- - lib/generators/templates/create_staged_acidic_jobs_migration.rb.erb
91
+ - lib/generators/templates/create_acidic_job_runs_migration.rb.erb
90
92
  homepage: https://github.com/fractaledmind/acidic_job
91
93
  licenses:
92
94
  - MIT
@@ -105,9 +107,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
105
107
  version: 2.7.0
106
108
  required_rubygems_version: !ruby/object:Gem::Requirement
107
109
  requirements:
108
- - - ">="
110
+ - - ">"
109
111
  - !ruby/object:Gem::Version
110
- version: '0'
112
+ version: 1.3.1
111
113
  requirements: []
112
114
  rubygems_version: 3.2.22
113
115
  signing_key:
@@ -1,26 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module AcidicJob
4
- module DeliverTransactionallyExtension
5
- # rubocop:disable Metrics/MethodLength
6
- def deliver_transactionally(_options = {})
7
- job = delivery_job_class
8
-
9
- attributes = {
10
- adapter: "activejob",
11
- job_name: job.name
12
- }
13
-
14
- job_args = if job <= ActionMailer::Parameterized::MailDeliveryJob
15
- [@mailer_class.name, @action.to_s, "deliver_now", { params: @params, args: @args }]
16
- else
17
- [@mailer_class.name, @action.to_s, "deliver_now", @params, *@args]
18
- end
19
-
20
- attributes[:job_args] = job.new(job_args).serialize
21
-
22
- AcidicJob::Staged.create!(attributes)
23
- end
24
- # rubocop:enable Metrics/MethodLength
25
- end
26
- end
@@ -1,33 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "active_record"
4
-
5
- module AcidicJob
6
- class Key < ActiveRecord::Base
7
- RECOVERY_POINT_FINISHED = "FINISHED"
8
-
9
- self.table_name = "acidic_job_keys"
10
-
11
- serialize :error_object
12
- serialize :job_args
13
- serialize :workflow
14
- store :attr_accessors
15
-
16
- validates :idempotency_key, presence: true, uniqueness: { scope: %i[job_name job_args] }
17
- validates :job_name, presence: true
18
- validates :last_run_at, presence: true
19
- validates :recovery_point, presence: true
20
-
21
- def finished?
22
- recovery_point == RECOVERY_POINT_FINISHED
23
- end
24
-
25
- def succeeded?
26
- finished? && !failed?
27
- end
28
-
29
- def failed?
30
- error_object.present?
31
- end
32
- end
33
- end