acidic_job 1.0.0.beta.10 → 1.0.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/main.yml +12 -36
  3. data/.gitignore +0 -5
  4. data/.ruby_version +1 -0
  5. data/Gemfile +31 -0
  6. data/Gemfile.lock +130 -136
  7. data/README.md +58 -278
  8. data/acidic_job.gemspec +2 -15
  9. data/bin/console +2 -4
  10. data/lib/acidic_job/awaiting.rb +68 -0
  11. data/lib/acidic_job/errors.rb +19 -11
  12. data/lib/acidic_job/extensions/action_mailer.rb +11 -3
  13. data/lib/acidic_job/extensions/active_job.rb +39 -0
  14. data/lib/acidic_job/extensions/noticed.rb +11 -5
  15. data/lib/acidic_job/extensions/sidekiq.rb +101 -0
  16. data/lib/acidic_job/finished_point.rb +5 -3
  17. data/lib/acidic_job/idempotency_key.rb +15 -18
  18. data/lib/acidic_job/perform_wrapper.rb +36 -9
  19. data/lib/acidic_job/recovery_point.rb +3 -2
  20. data/lib/acidic_job/run.rb +42 -268
  21. data/lib/acidic_job/staging.rb +30 -0
  22. data/lib/acidic_job/step.rb +83 -0
  23. data/lib/acidic_job/version.rb +1 -1
  24. data/lib/acidic_job.rb +244 -20
  25. data/lib/generators/acidic_job_generator.rb +35 -0
  26. data/lib/generators/templates/create_acidic_job_runs_migration.rb.erb +19 -0
  27. metadata +15 -209
  28. data/.github/FUNDING.yml +0 -13
  29. data/.tool-versions +0 -1
  30. data/UPGRADE_GUIDE.md +0 -81
  31. data/combustion/log/test.log +0 -0
  32. data/gemfiles/rails_6.1_sidekiq_6.4.gemfile +0 -10
  33. data/gemfiles/rails_6.1_sidekiq_6.5.gemfile +0 -10
  34. data/gemfiles/rails_7.0_sidekiq_6.4.gemfile +0 -10
  35. data/gemfiles/rails_7.0_sidekiq_6.5.gemfile +0 -10
  36. data/gemfiles/rails_7.1_sidekiq_6.4.gemfile +0 -10
  37. data/gemfiles/rails_7.1_sidekiq_6.5.gemfile +0 -10
  38. data/lib/acidic_job/active_kiq.rb +0 -114
  39. data/lib/acidic_job/arguments.rb +0 -22
  40. data/lib/acidic_job/base.rb +0 -11
  41. data/lib/acidic_job/logger.rb +0 -31
  42. data/lib/acidic_job/mixin.rb +0 -250
  43. data/lib/acidic_job/processor.rb +0 -95
  44. data/lib/acidic_job/rails.rb +0 -40
  45. data/lib/acidic_job/serializer.rb +0 -24
  46. data/lib/acidic_job/serializers/exception_serializer.rb +0 -41
  47. data/lib/acidic_job/serializers/finished_point_serializer.rb +0 -24
  48. data/lib/acidic_job/serializers/job_serializer.rb +0 -27
  49. data/lib/acidic_job/serializers/range_serializer.rb +0 -28
  50. data/lib/acidic_job/serializers/recovery_point_serializer.rb +0 -25
  51. data/lib/acidic_job/serializers/worker_serializer.rb +0 -27
  52. data/lib/acidic_job/test_case.rb +0 -9
  53. data/lib/acidic_job/testing.rb +0 -73
  54. data/lib/acidic_job/workflow.rb +0 -70
  55. data/lib/acidic_job/workflow_builder.rb +0 -35
  56. data/lib/acidic_job/workflow_step.rb +0 -103
  57. data/lib/generators/acidic_job/drop_tables_generator.rb +0 -26
  58. data/lib/generators/acidic_job/install_generator.rb +0 -27
  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,250 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "active_support/concern"
4
-
5
- module AcidicJob
6
- module Mixin
7
- extend ActiveSupport::Concern
8
-
9
- def self.wire_up(other)
10
- # Ensure our `perform` method always runs first to gather parameters
11
- # and run perform callbacks for Sidekiq workers
12
- other.prepend PerformWrapper
13
-
14
- # By default, we unique job runs by the `job_id`
15
- other.instance_variable_set(:@acidic_identifier, :job_id)
16
- # However, you can customize this behavior on a per job class level
17
- other.define_singleton_method(:acidic_by_job_identifier) { @acidic_identifier = :job_id }
18
- # You could unique job runs by the arguments passed to the job (e.g. memoization)
19
- other.define_singleton_method(:acidic_by_job_arguments) { @acidic_identifier = :job_arguments }
20
- # Or, you could unique jobs run by any logic you'd like using a block
21
- other.define_singleton_method(:acidic_by) { |&block| @acidic_identifier = block }
22
-
23
- # We add a callback to ensure that staged, non-workflow jobs are "finished" after they are "performed".
24
- # This allows us to ensure that we can always inspect whether a run is finished and get correct data
25
- other.set_callback :perform, :after, :finish_staged_job, if: -> { was_staged_job? && !was_workflow_job? }
26
- # We also allow you to write any of your own callbacks keyed to the "finish" event.
27
- # The "finish" event is notably different from the "perform" event for acidic jobs,
28
- # as any acidic job can have one or more "perform" event (retries or a resume after awaiting jobs),
29
- # but will only ever have one "finish" event (when the run successfully completes)
30
- other.define_callbacks :finish
31
- end
32
-
33
- included do
34
- Mixin.wire_up(self)
35
- end
36
-
37
- class_methods do
38
- def inherited(subclass)
39
- Mixin.wire_up(subclass)
40
- super
41
- end
42
-
43
- # `perform_now` runs a job synchronously and immediately
44
- # `perform_later` runs a job asynchronously and queues it immediately
45
- # `perform_acidicly` run a job asynchronously and queues it after a successful database commit
46
- def perform_acidicly(*args)
47
- job = new(*args)
48
-
49
- Run.stage!(job)
50
- end
51
-
52
- # Instantiate an instance of a job ready for serialization
53
- def with(...)
54
- # New delegation syntax (...) was introduced in Ruby 2.7.
55
- # https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0/
56
- job = new(...)
57
- # force the job to resolve the `queue_name`, so that we don't try to serialize a Proc into ActiveRecord
58
- job.queue_name
59
- job
60
- end
61
- end
62
-
63
- def idempotency_key
64
- IdempotencyKey.new(self).value(acidic_by: acidic_identifier)
65
- end
66
-
67
- protected
68
-
69
- # Short circuits execution by sending execution right to 'finished'.
70
- # So, ends the job "successfully"
71
- def safely_finish_acidic_job
72
- FinishedPoint.new
73
- end
74
-
75
- def with_acidic_workflow(persisting: {}, &block)
76
- raise UnknownJobAdapter unless (defined?(::AcidicJob::Base) && self.class < ::AcidicJob::Base) ||
77
- (defined?(::AcidicJob::ActiveKiq) && self.class < ::AcidicJob::ActiveKiq)
78
-
79
- raise RedefiningWorkflow if defined? @workflow_builder
80
-
81
- @workflow_builder = WorkflowBuilder.new
82
-
83
- raise MissingWorkflowBlock, "A block must be passed to `with_acidic_workflow`" unless block_given?
84
- raise MissingBlockArgument, "An argument must be passed to the `with_acidic_workflow` block" if block.arity.zero?
85
-
86
- block.call @workflow_builder
87
-
88
- raise NoDefinedSteps if @workflow_builder.steps.empty?
89
-
90
- # convert the array of steps into a hash of recovery_points and next steps
91
- workflow = @workflow_builder.define_workflow
92
-
93
- ensure_run_record_and_process(workflow, persisting)
94
- rescue LocalJumpError
95
- raise MissingWorkflowBlock, "A block must be passed to `with_acidic_workflow`"
96
- end
97
-
98
- # DEPRECATED
99
- # second attempt at the DSL, but `providing` suggested you needed to serialize
100
- # any data that would be used in step methods, but simply instance variables work.
101
- def with_acidity(providing: {}, &block)
102
- ::ActiveSupport::Deprecation.new("1.0", "AcidicJob").deprecation_warning(:with_acidity)
103
-
104
- @workflow_builder = WorkflowBuilder.new
105
- @workflow_builder.instance_exec(&block)
106
-
107
- raise NoDefinedSteps if @workflow_builder.steps.empty?
108
-
109
- # convert the array of steps into a hash of recovery_points and next steps
110
- workflow = @workflow_builder.define_workflow
111
-
112
- ensure_run_record_and_process(workflow, providing)
113
- rescue LocalJumpError
114
- raise MissingWorkflowBlock, "A block must be passed to `with_acidity`"
115
- end
116
-
117
- # DEPRECATED
118
- # first attempt at the DSL, but `idempotently` and `with` are too distant from the gem name.
119
- def idempotently(with: {}, &block)
120
- ::ActiveSupport::Deprecation.new("1.0", "AcidicJob").deprecation_warning(:idempotently)
121
-
122
- @workflow_builder = WorkflowBuilder.new
123
- @workflow_builder.instance_exec(&block)
124
-
125
- raise NoDefinedSteps if @workflow_builder.steps.empty?
126
-
127
- # convert the array of steps into a hash of recovery_points and next steps
128
- workflow = @workflow_builder.define_workflow
129
-
130
- ensure_run_record_and_process(workflow, with)
131
- rescue LocalJumpError
132
- raise MissingWorkflowBlock, "A block must be passed to `idempotently`"
133
- end
134
-
135
- private
136
-
137
- # You can always retrieve the unique identifier from within the job instance itself
138
- def acidic_identifier
139
- self.class.instance_variable_get(:@acidic_identifier)
140
- end
141
-
142
- def ensure_run_record_and_process(workflow, persisting)
143
- ::AcidicJob.logger.log_run_event("Initializing run...", self, nil)
144
- @acidic_job_run = ::ActiveRecord::Base.transaction(isolation: acidic_isolation_level) do
145
- run = Run.find_by(idempotency_key: idempotency_key)
146
- serialized_job = serialize
147
-
148
- if run.present?
149
- # Programs enqueuing multiple jobs with different parameters but the
150
- # same idempotency key is a bug.
151
- if run.serialized_job["arguments"] != serialized_job["arguments"]
152
- raise MismatchedIdempotencyKeyAndJobArguments
153
- end
154
-
155
- # Only acquire a lock if the key is unlocked or its lock has expired
156
- # because the original job was long enough ago.
157
- raise LockedIdempotencyKey if run.lock_active?
158
-
159
- run.update!(
160
- last_run_at: Time.current,
161
- locked_at: Time.current,
162
- # staged workflow jobs won't have the `workflow` or `recovery_point` stored when staged,
163
- # so we need to persist both here.
164
- workflow: workflow,
165
- recovery_point: run.recovery_point || workflow.keys.first
166
- )
167
- else
168
- run = Run.create!(
169
- staged: false,
170
- idempotency_key: idempotency_key,
171
- job_class: self.class.name,
172
- locked_at: Time.current,
173
- last_run_at: Time.current,
174
- workflow: workflow,
175
- recovery_point: workflow.keys.first,
176
- serialized_job: serialize
177
- )
178
- end
179
-
180
- # persist `persisting` values and set accessors for each
181
- # first, get the current state of all accessors for both previously persisted and initialized values
182
- current_accessors = persisting.stringify_keys.merge(run.attr_accessors)
183
-
184
- # next, ensure that `Run#attr_accessors` is populated with initial values
185
- # skip validations for this call to ensure a write
186
- run.update_column(:attr_accessors, current_accessors) if current_accessors != run.attr_accessors
187
-
188
- # finally, set reader and writer methods
189
- current_accessors.each do |accessor, value|
190
- # the reader method may already be defined
191
- self.class.attr_reader accessor unless respond_to?(accessor)
192
- # but we should always update the value to match the current value
193
- instance_variable_set("@#{accessor}", value)
194
- # and we overwrite the setter to ensure any updates to an accessor update the `Run` stored value
195
- # Note: we must define the singleton method on the instance to avoid overwriting setters on other
196
- # instances of the same class
197
- define_singleton_method("#{accessor}=") do |updated_value|
198
- instance_variable_set("@#{accessor}", updated_value)
199
- run.attr_accessors[accessor] = updated_value
200
- run.save!(validate: false)
201
- updated_value
202
- end
203
- end
204
-
205
- # ensure that we return the `run` record so that the result of this block is the record
206
- run
207
- end
208
- ::AcidicJob.logger.log_run_event("Initialized run.", self, @acidic_job_run)
209
-
210
- Processor.new(@acidic_job_run, self).process_run
211
- end
212
-
213
- def was_staged_job?
214
- job_id.start_with? Run::STAGED_JOB_ID_PREFIX
215
- end
216
-
217
- def was_workflow_job?
218
- return false unless defined? @acidic_job_run
219
-
220
- @acidic_job_run.present?
221
- end
222
-
223
- def staged_job_run
224
- return unless was_staged_job?
225
- # memoize so we don't have to make unnecessary database calls
226
- return @staged_job_run if defined? @staged_job_run
227
-
228
- # "STG__#{idempotency_key}__#{encoded_global_id}"
229
- _prefix, _idempotency_key, encoded_global_id = job_id.split("__")
230
- staged_job_gid = "gid://#{::Base64.urlsafe_decode64(encoded_global_id)}"
231
-
232
- @staged_job_run = ::GlobalID::Locator.locate(staged_job_gid)
233
- end
234
-
235
- def finish_staged_job
236
- staged_job_run.finish!
237
- end
238
-
239
- def acidic_isolation_level
240
- case ::ActiveRecord::Base.connection.adapter_name.downcase.to_sym
241
- # SQLite doesn't support `serializable` transactions,
242
- # so we use the strictest isolation_level is does support
243
- when :sqlite
244
- :read_uncommitted
245
- else
246
- :serializable
247
- end
248
- end
249
- end
250
- end
@@ -1,95 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module AcidicJob
4
- class Processor
5
- def initialize(run, job)
6
- @run = run
7
- @job = job
8
- @workflow = Workflow.new(run, job)
9
- end
10
-
11
- def process_run
12
- # if the run record is already marked as finished, immediately return its result
13
- return @run.succeeded? if @run.finished?
14
-
15
- AcidicJob.logger.log_run_event("Processing #{@run.current_step_name}...", @job, @run)
16
- loop do
17
- break if @run.finished?
18
-
19
- if !@run.known_recovery_point?
20
- raise UnknownRecoveryPoint,
21
- "Defined workflow does not reference this step: #{@run.current_step_name.inspect}"
22
- elsif (awaited_jobs = jobs_from(@run.current_step_awaits)).any?
23
- # We only execute the current step, without progressing to the next step.
24
- # This ensures that any failures in parallel jobs will have this step retried in the main workflow
25
- step_result = @workflow.execute_current_step
26
- # We allow the `#step_done` method to manage progressing the recovery_point to the next step,
27
- # and then calling `process_run` to restart the main workflow on the next step.
28
- # We pass the `step_result` so that the async callback called after the step-parallel-jobs complete
29
- # can move on to the appropriate next stage in the workflow.
30
- enqueue_awaited_jobs(awaited_jobs, step_result)
31
- # after processing the current step, break the processing loop
32
- # and stop this method from blocking in the primary worker
33
- # as it will continue once the background workers all succeed
34
- # so we want to keep the primary worker queue free to process new work
35
- # this CANNOT ever be `break` as that wouldn't exit the parent job,
36
- # only this step in the workflow, blocking as it awaits the next step
37
- return true
38
- else
39
- @workflow.execute_current_step
40
- @workflow.progress_to_next_step
41
- end
42
- end
43
- AcidicJob.logger.log_run_event("Processed #{@run.current_step_name}.", @job, @run)
44
-
45
- @run.succeeded?
46
- end
47
-
48
- private
49
-
50
- def enqueue_awaited_jobs(awaited_jobs, step_result)
51
- AcidicJob.logger.log_run_event("Enqueuing #{awaited_jobs.count} awaited jobs...", @job, @run)
52
- # All jobs created in the block are pushed atomically at the end of the block.
53
- AcidicJob::Run.transaction do
54
- awaited_jobs.each do |awaited_job|
55
- worker_class, args = job_and_args(awaited_job)
56
-
57
- job = worker_class.new(*args)
58
-
59
- AcidicJob::Run.await!(job, by: @run, return_to: step_result)
60
- end
61
- end
62
- AcidicJob.logger.log_run_event("Enqueued #{awaited_jobs.count} awaited jobs.", @job, @run)
63
- end
64
-
65
- def jobs_from(jobs_or_jobs_getter)
66
- case jobs_or_jobs_getter
67
- when Array
68
- jobs_or_jobs_getter.compact
69
- when Symbol, String
70
- if @job.respond_to?(jobs_or_jobs_getter, _include_private = true)
71
- jobs = @job.method(jobs_or_jobs_getter).call
72
- Array(jobs).compact
73
- else
74
- raise UnknownAwaitedJob,
75
- "Invalid `awaits`; unknown method `#{jobs_or_jobs_getter}` for this job"
76
- end
77
- else
78
- raise UnknownAwaitedJob,
79
- "Invalid `awaits`; must be either an jobs Array or method name, was: #{jobs_or_jobs_getter.class.name}"
80
- end
81
- end
82
-
83
- def job_and_args(job)
84
- case job
85
- when Class
86
- [job, []]
87
- else
88
- [
89
- job.class,
90
- job.arguments
91
- ]
92
- end
93
- end
94
- end
95
- end
@@ -1,40 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "rails/railtie"
4
-
5
- module AcidicJob
6
- class Rails < ::Rails::Railtie
7
- initializer "acidic_job.action_mailer_extension" do
8
- ::ActiveSupport.on_load(:action_mailer) do
9
- # Add `deliver_acidicly` to ActionMailer
10
- ::ActionMailer::Parameterized::MessageDelivery.include(Extensions::ActionMailer)
11
- ::ActionMailer::MessageDelivery.include(Extensions::ActionMailer)
12
- end
13
- end
14
-
15
- initializer "acidic_job.active_job_serializers" do
16
- ::ActiveSupport.on_load(:active_job) do
17
- ::ActiveJob::Serializers.add_serializers(
18
- Serializers::ExceptionSerializer,
19
- Serializers::FinishedPointSerializer,
20
- Serializers::JobSerializer,
21
- Serializers::RangeSerializer,
22
- Serializers::RecoveryPointSerializer,
23
- Serializers::WorkerSerializer
24
- )
25
- end
26
- end
27
-
28
- generators do
29
- require "generators/acidic_job/install_generator"
30
- end
31
-
32
- # This hook happens after all initializers are run, just before returning
33
- config.after_initialize do
34
- if defined?(::Noticed)
35
- # Add `deliver_acidicly` to Noticed
36
- ::Noticed::Base.include(Extensions::Noticed)
37
- end
38
- end
39
- end
40
- end
@@ -1,24 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "json"
4
-
5
- module AcidicJob
6
- class Serializer
7
- # Used for `serialize` method in ActiveRecord
8
- class << self
9
- def load(json)
10
- return if json.nil? || json.empty?
11
-
12
- data = JSON.parse(json)
13
- Arguments.send :deserialize_argument, data
14
- end
15
-
16
- def dump(obj)
17
- data = Arguments.send :serialize_argument, obj
18
- data.to_json
19
- rescue ActiveJob::SerializationError
20
- raise UnserializableValue
21
- end
22
- end
23
- end
24
- end
@@ -1,41 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "active_job/serializers/object_serializer"
4
-
5
- module AcidicJob
6
- module Serializers
7
- class ExceptionSerializer < ::ActiveJob::Serializers::ObjectSerializer
8
- def serialize(exception)
9
- hash = {
10
- "class" => exception.class.name,
11
- "message" => exception.message,
12
- "cause" => exception.cause,
13
- "backtrace" => {}
14
- }
15
-
16
- exception.backtrace.map do |trace|
17
- path, _, location = trace.rpartition("/")
18
-
19
- next if hash["backtrace"].key?(path)
20
-
21
- hash["backtrace"][path] = location
22
- end
23
-
24
- super(hash)
25
- end
26
-
27
- def deserialize(hash)
28
- exception_class = hash["class"].constantize
29
- exception = exception_class.new(hash["message"])
30
- exception.set_backtrace(hash["backtrace"].map do |path, location|
31
- [path, location].join("/")
32
- end)
33
- exception
34
- end
35
-
36
- def serialize?(argument)
37
- defined?(Exception) && argument.is_a?(Exception)
38
- end
39
- end
40
- end
41
- end
@@ -1,24 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "active_job/serializers/object_serializer"
4
-
5
- module AcidicJob
6
- module Serializers
7
- class FinishedPointSerializer < ::ActiveJob::Serializers::ObjectSerializer
8
- def serialize(finished_point)
9
- super(
10
- "class" => finished_point.class.name
11
- )
12
- end
13
-
14
- def deserialize(hash)
15
- finished_point_class = hash["class"].constantize
16
- finished_point_class.new
17
- end
18
-
19
- def serialize?(argument)
20
- defined?(::AcidicJob::FinishedPoint) && argument.is_a?(::AcidicJob::FinishedPoint)
21
- end
22
- end
23
- end
24
- end
@@ -1,27 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "active_job/serializers/object_serializer"
4
-
5
- module AcidicJob
6
- module Serializers
7
- class JobSerializer < ::ActiveJob::Serializers::ObjectSerializer
8
- def serialize(job)
9
- # don't serialize the `enqueued_at` value, as ActiveRecord will check if the Run record has changed
10
- # by comparing the deserialized database value with a temporary in-memory generated value.
11
- # That temporary in-memory generated value can sometimes have an `enqueued_at` value that is 1 second off
12
- # from the original. In this case, ActiveRecord will think the record has unsaved changes and block the lock.
13
- super(job.as_json.merge("job_class" => job.class.name))
14
- end
15
-
16
- def deserialize(hash)
17
- job = ::ActiveJob::Base.deserialize(hash)
18
- job.send(:deserialize_arguments_if_needed)
19
- job
20
- end
21
-
22
- def serialize?(argument)
23
- defined?(::ActiveJob::Base) && argument.class < ::ActiveJob::Base
24
- end
25
- end
26
- end
27
- end
@@ -1,28 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "active_job/serializers/object_serializer"
4
-
5
- # :nocov:
6
- module AcidicJob
7
- module Serializers
8
- class RangeSerializer < ::ActiveJob::Serializers::ObjectSerializer
9
- KEYS = %w[begin end exclude_end].freeze
10
-
11
- def serialize(range)
12
- args = Arguments.serialize([range.begin, range.end, range.exclude_end?])
13
- super(KEYS.zip(args).to_h)
14
- end
15
-
16
- def deserialize(hash)
17
- klass.new(*Arguments.deserialize(hash.values_at(*KEYS)))
18
- end
19
-
20
- private
21
-
22
- def klass
23
- ::Range
24
- end
25
- end
26
- end
27
- end
28
- # :nocov:
@@ -1,25 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "active_job/serializers/object_serializer"
4
-
5
- module AcidicJob
6
- module Serializers
7
- class RecoveryPointSerializer < ::ActiveJob::Serializers::ObjectSerializer
8
- def serialize(recovery_point)
9
- super(
10
- "class" => recovery_point.class.name,
11
- "name" => recovery_point.name
12
- )
13
- end
14
-
15
- def deserialize(hash)
16
- recovery_point_class = hash["class"].constantize
17
- recovery_point_class.new(hash["name"])
18
- end
19
-
20
- def serialize?(argument)
21
- defined?(::AcidicJob::RecoveryPoint) && argument.is_a?(::AcidicJob::RecoveryPoint)
22
- end
23
- end
24
- end
25
- end
@@ -1,27 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "active_job/serializers/object_serializer"
4
-
5
- # :nocov:
6
- module AcidicJob
7
- module Serializers
8
- class WorkerSerializer < ::ActiveJob::Serializers::ObjectSerializer
9
- def serialize(worker)
10
- super(
11
- "job_class" => worker.class.name,
12
- "arguments" => worker.arguments,
13
- )
14
- end
15
-
16
- def deserialize(hash)
17
- worker_class = hash["job_class"].constantize
18
- worker_class.new(*hash["arguments"])
19
- end
20
-
21
- def serialize?(argument)
22
- defined?(::Sidekiq) && argument.class.include?(::Sidekiq::Worker)
23
- end
24
- end
25
- end
26
- end
27
- # :nocov:
@@ -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,73 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "database_cleaner"
4
-
5
- module AcidicJob
6
- module Testing
7
- def self.included(mod)
8
- mod.class_eval "self.use_transactional_tests = false if respond_to?(:use_transactional_tests)", __FILE__, __LINE__
9
- end
10
-
11
- def before_setup
12
- @connection = ::ActiveRecord::Base.connection
13
- @original_cleaners = ::DatabaseCleaner.cleaners
14
- ::DatabaseCleaner.cleaners = transaction_free_cleaners_for(@original_cleaners)
15
- super
16
- ::DatabaseCleaner.start
17
- end
18
-
19
- def after_teardown
20
- ::DatabaseCleaner.clean
21
- super
22
- ::DatabaseCleaner.cleaners = @original_cleaners
23
- end
24
-
25
- private
26
-
27
- # Ensure that the system's original DatabaseCleaner configuration is maintained, options included,
28
- # except that any `transaction` strategies for any ORMs are replaced with a `deletion` strategy.
29
- def transaction_free_cleaners_for(original_cleaners)
30
- non_transaction_cleaners = original_cleaners.dup.to_h do |(orm, opts), cleaner|
31
- [[orm, opts], ensure_no_transaction_strategies_for(cleaner)]
32
- end
33
- ::DatabaseCleaner::Cleaners.new(non_transaction_cleaners)
34
- end
35
-
36
- def ensure_no_transaction_strategies_for(cleaner)
37
- return cleaner unless strategy_name_for(cleaner) == "transaction"
38
-
39
- cleaner.strategy = deletion_strategy_for(cleaner)
40
- cleaner
41
- end
42
-
43
- def strategy_name_for(cleaner)
44
- cleaner # <DatabaseCleaner::Cleaner>
45
- .strategy # <DatabaseCleaner::ActiveRecord::Truncation>
46
- .class # DatabaseCleaner::ActiveRecord::Truncation
47
- .name # "DatabaseCleaner::ActiveRecord::Truncation"
48
- .rpartition("::") # ["DatabaseCleaner::ActiveRecord", "::", "Truncation"]
49
- .last # "Truncation"
50
- .downcase # "truncation"
51
- end
52
-
53
- def deletion_strategy_for(cleaner)
54
- strategy = cleaner.strategy
55
- strategy_namespace = strategy # <DatabaseCleaner::ActiveRecord::Truncation>
56
- .class # DatabaseCleaner::ActiveRecord::Truncation
57
- .name # "DatabaseCleaner::ActiveRecord::Truncation"
58
- .rpartition("::") # ["DatabaseCleaner::ActiveRecord", "::", "Truncation"]
59
- .first # "DatabaseCleaner::ActiveRecord"
60
- deletion_strategy_class_name = [strategy_namespace, "::", "Deletion"].join
61
- deletion_strategy_class = deletion_strategy_class_name.constantize
62
- instance_variable_hash = strategy.instance_variables.to_h do |var|
63
- [
64
- var.to_s.remove("@"),
65
- strategy.instance_variable_get(var)
66
- ]
67
- end
68
- options = instance_variable_hash.except("db", "connection_class")
69
-
70
- deletion_strategy_class.new(**options)
71
- end
72
- end
73
- end