activejob 8.0.3 → 8.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +74 -31
  3. data/README.md +7 -5
  4. data/lib/active_job/arguments.rb +47 -44
  5. data/lib/active_job/base.rb +3 -4
  6. data/lib/active_job/configured_job.rb +6 -1
  7. data/lib/active_job/continuable.rb +102 -0
  8. data/lib/active_job/continuation/step.rb +83 -0
  9. data/lib/active_job/continuation/test_helper.rb +89 -0
  10. data/lib/active_job/continuation/validation.rb +50 -0
  11. data/lib/active_job/continuation.rb +332 -0
  12. data/lib/active_job/core.rb +11 -1
  13. data/lib/active_job/enqueue_after_transaction_commit.rb +1 -26
  14. data/lib/active_job/enqueuing.rb +8 -4
  15. data/lib/active_job/exceptions.rb +17 -8
  16. data/lib/active_job/execution_state.rb +11 -0
  17. data/lib/active_job/gem_version.rb +2 -2
  18. data/lib/active_job/instrumentation.rb +12 -12
  19. data/lib/active_job/log_subscriber.rb +61 -6
  20. data/lib/active_job/queue_adapters/abstract_adapter.rb +6 -0
  21. data/lib/active_job/queue_adapters/async_adapter.rb +5 -1
  22. data/lib/active_job/queue_adapters/sidekiq_adapter.rb +19 -0
  23. data/lib/active_job/queue_adapters/test_adapter.rb +5 -1
  24. data/lib/active_job/railtie.rb +9 -19
  25. data/lib/active_job/serializers/action_controller_parameters_serializer.rb +25 -0
  26. data/lib/active_job/serializers/big_decimal_serializer.rb +3 -4
  27. data/lib/active_job/serializers/date_serializer.rb +3 -4
  28. data/lib/active_job/serializers/date_time_serializer.rb +3 -4
  29. data/lib/active_job/serializers/duration_serializer.rb +5 -6
  30. data/lib/active_job/serializers/module_serializer.rb +3 -4
  31. data/lib/active_job/serializers/object_serializer.rb +11 -14
  32. data/lib/active_job/serializers/range_serializer.rb +9 -9
  33. data/lib/active_job/serializers/symbol_serializer.rb +4 -5
  34. data/lib/active_job/serializers/time_serializer.rb +3 -4
  35. data/lib/active_job/serializers/time_with_zone_serializer.rb +3 -4
  36. data/lib/active_job/serializers.rb +54 -15
  37. data/lib/active_job/structured_event_subscriber.rb +220 -0
  38. data/lib/active_job.rb +3 -0
  39. metadata +14 -9
  40. data/lib/active_job/queue_adapters/sucker_punch_adapter.rb +0 -56
  41. data/lib/active_job/timezones.rb +0 -13
  42. data/lib/active_job/translation.rb +0 -13
@@ -18,6 +18,25 @@ module ActiveJob
18
18
  #
19
19
  # Rails.application.config.active_job.queue_adapter = :sidekiq
20
20
  class SidekiqAdapter < AbstractAdapter
21
+ def initialize(*) # :nodoc:
22
+ @stopping = false
23
+
24
+ Sidekiq.configure_server do |config|
25
+ config.on(:quiet) { @stopping = true }
26
+ end
27
+
28
+ Sidekiq.configure_client do |config|
29
+ config.on(:quiet) { @stopping = true }
30
+ end
31
+ end
32
+
33
+ def check_adapter
34
+ ActiveJob.deprecator.warn <<~MSG.squish
35
+ The built-in `sidekiq` adapter is deprecated and will be removed in Rails 8.2.
36
+ Please upgrade `sidekiq` gem to version 7.3.3 or later to use the `sidekiq` gem's adapter.
37
+ MSG
38
+ end
39
+
21
40
  def enqueue(job) # :nodoc:
22
41
  job.provider_job_id = JobWrapper.set(
23
42
  wrapped: job.class,
@@ -12,7 +12,7 @@ module ActiveJob
12
12
  #
13
13
  # Rails.application.config.active_job.queue_adapter = :test
14
14
  class TestAdapter < AbstractAdapter
15
- attr_accessor(:perform_enqueued_jobs, :perform_enqueued_at_jobs, :filter, :reject, :queue, :at)
15
+ attr_accessor(:perform_enqueued_jobs, :perform_enqueued_at_jobs, :filter, :reject, :queue, :at, :stopping)
16
16
  attr_writer(:enqueued_jobs, :performed_jobs)
17
17
 
18
18
  # Provides a store of all the enqueued jobs with the TestAdapter so you can check them.
@@ -35,6 +35,10 @@ module ActiveJob
35
35
  perform_or_enqueue(perform_enqueued_at_jobs && !filtered?(job), job, job_data)
36
36
  end
37
37
 
38
+ def stopping?
39
+ @stopping.is_a?(Proc) ? @stopping.call : @stopping
40
+ end
41
+
38
42
  private
39
43
  def job_to_hash(job, extras = {})
40
44
  job.serialize.tap do |job_data|
@@ -19,7 +19,7 @@ module ActiveJob
19
19
  end
20
20
 
21
21
  initializer "active_job.custom_serializers" do |app|
22
- config.after_initialize do
22
+ ActiveSupport.on_load(:active_job) do
23
23
  custom_serializers = app.config.active_job.custom_serializers
24
24
  ActiveJob::Serializers.add_serializers custom_serializers
25
25
  end
@@ -29,25 +29,14 @@ module ActiveJob
29
29
  ActiveSupport.on_load(:active_job) do
30
30
  ActiveSupport.on_load(:active_record) do
31
31
  ActiveJob::Base.include EnqueueAfterTransactionCommit
32
+ end
33
+ end
34
+ end
32
35
 
33
- if app.config.active_job.key?(:enqueue_after_transaction_commit)
34
- ActiveJob.deprecator.warn(<<~MSG.squish)
35
- `config.active_job.enqueue_after_transaction_commit` is deprecated and will be removed in Rails 8.1.
36
- This configuration can still be set on individual jobs using `self.enqueue_after_transaction_commit=`,
37
- but due the nature of this behavior, it is not recommended to be set globally.
38
- MSG
39
-
40
- value = case app.config.active_job.enqueue_after_transaction_commit
41
- when :always
42
- true
43
- when :never
44
- false
45
- else
46
- false
47
- end
48
-
49
- ActiveJob::Base.enqueue_after_transaction_commit = value
50
- end
36
+ initializer "active_job.action_controller_parameters" do |app|
37
+ ActiveSupport.on_load(:active_job) do
38
+ ActiveSupport.on_load(:action_controller) do
39
+ ActiveJob::Serializers.add_serializers ActiveJob::Serializers::ActionControllerParametersSerializer
51
40
  end
52
41
  end
53
42
  end
@@ -70,6 +59,7 @@ module ActiveJob
70
59
  options = options.except(
71
60
  :log_query_tags_around_perform,
72
61
  :custom_serializers,
62
+ # This config can't be applied globally, so we need to remove otherwise it will be applied to `ActiveJob::Base`.
73
63
  :enqueue_after_transaction_commit
74
64
  )
75
65
 
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveJob
4
+ module Serializers
5
+ class ActionControllerParametersSerializer < ObjectSerializer
6
+ def serialize(argument)
7
+ Arguments.serialize_argument(argument.to_h.with_indifferent_access)
8
+ end
9
+
10
+ def deserialize(hash)
11
+ raise NotImplementedError # Serialized as a HashWithIndifferentAccess
12
+ end
13
+
14
+ def serialize?(argument)
15
+ argument.respond_to?(:permitted?) && argument.respond_to?(:to_h)
16
+ end
17
+
18
+ def klass
19
+ if defined?(ActionController::Parameters)
20
+ ActionController::Parameters
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -13,10 +13,9 @@ module ActiveJob
13
13
  BigDecimal(hash["value"])
14
14
  end
15
15
 
16
- private
17
- def klass
18
- BigDecimal
19
- end
16
+ def klass
17
+ BigDecimal
18
+ end
20
19
  end
21
20
  end
22
21
  end
@@ -11,10 +11,9 @@ module ActiveJob
11
11
  Date.iso8601(hash["value"])
12
12
  end
13
13
 
14
- private
15
- def klass
16
- Date
17
- end
14
+ def klass
15
+ Date
16
+ end
18
17
  end
19
18
  end
20
19
  end
@@ -7,10 +7,9 @@ module ActiveJob
7
7
  DateTime.iso8601(hash["value"])
8
8
  end
9
9
 
10
- private
11
- def klass
12
- DateTime
13
- end
10
+ def klass
11
+ DateTime
12
+ end
14
13
  end
15
14
  end
16
15
  end
@@ -6,20 +6,19 @@ module ActiveJob
6
6
  def serialize(duration)
7
7
  # Ideally duration.parts would be wrapped in an array before passing to Arguments.serialize,
8
8
  # but we continue passing the bare hash for backwards compatibility:
9
- super("value" => duration.value, "parts" => Arguments.serialize(duration.parts))
9
+ super("value" => duration.value, "parts" => Arguments.serialize(duration.parts.to_a))
10
10
  end
11
11
 
12
12
  def deserialize(hash)
13
13
  value = hash["value"]
14
- parts = Arguments.deserialize(hash["parts"])
14
+ parts = Arguments.deserialize(hash["parts"].to_h)
15
15
  # `parts` is originally a hash, but will have been flattened to an array by Arguments.serialize
16
16
  klass.new(value, parts.to_h)
17
17
  end
18
18
 
19
- private
20
- def klass
21
- ActiveSupport::Duration
22
- end
19
+ def klass
20
+ ActiveSupport::Duration
21
+ end
23
22
  end
24
23
  end
25
24
  end
@@ -12,10 +12,9 @@ module ActiveJob
12
12
  hash["value"].constantize
13
13
  end
14
14
 
15
- private
16
- def klass
17
- Module
18
- end
15
+ def klass
16
+ Module
17
+ end
19
18
  end
20
19
  end
21
20
  end
@@ -17,11 +17,9 @@ module ActiveJob
17
17
  # Money.new(hash["amount"], hash["currency"])
18
18
  # end
19
19
  #
20
- # private
21
- #
22
- # def klass
23
- # Money
24
- # end
20
+ # def klass
21
+ # Money
22
+ # end
25
23
  # end
26
24
  class ObjectSerializer
27
25
  include Singleton
@@ -30,6 +28,11 @@ module ActiveJob
30
28
  delegate :serialize?, :serialize, :deserialize, to: :instance
31
29
  end
32
30
 
31
+ def initialize
32
+ super
33
+ @template = { Arguments::OBJECT_SERIALIZER_KEY => self.class.name }.freeze
34
+ end
35
+
33
36
  # Determines if an argument should be serialized by a serializer.
34
37
  def serialize?(argument)
35
38
  argument.is_a?(klass)
@@ -37,19 +40,13 @@ module ActiveJob
37
40
 
38
41
  # Serializes an argument to a JSON primitive type.
39
42
  def serialize(hash)
40
- { Arguments::OBJECT_SERIALIZER_KEY => self.class.name }.merge!(hash)
43
+ @template.merge(hash)
41
44
  end
42
45
 
43
46
  # Deserializes an argument from a JSON primitive type.
44
- def deserialize(json)
45
- raise NotImplementedError
47
+ def deserialize(hash)
48
+ raise NotImplementedError, "#{self.class.name} should implement a public #deserialize(hash) method"
46
49
  end
47
-
48
- private
49
- # The class of the object that will be serialized.
50
- def klass # :doc:
51
- raise NotImplementedError
52
- end
53
50
  end
54
51
  end
55
52
  end
@@ -3,21 +3,21 @@
3
3
  module ActiveJob
4
4
  module Serializers
5
5
  class RangeSerializer < ObjectSerializer
6
- KEYS = %w[begin end exclude_end].freeze
7
-
8
6
  def serialize(range)
9
- args = Arguments.serialize([range.begin, range.end, range.exclude_end?])
10
- super(KEYS.zip(args).to_h)
7
+ super(
8
+ "begin" => Arguments.serialize_argument(range.begin),
9
+ "end" => Arguments.serialize_argument(range.end),
10
+ "exclude_end" => range.exclude_end?, # Always boolean, no need to serialize
11
+ )
11
12
  end
12
13
 
13
14
  def deserialize(hash)
14
- klass.new(*Arguments.deserialize(hash.values_at(*KEYS)))
15
+ Range.new(*Arguments.deserialize([hash["begin"], hash["end"]]), hash["exclude_end"])
15
16
  end
16
17
 
17
- private
18
- def klass
19
- ::Range
20
- end
18
+ def klass
19
+ ::Range
20
+ end
21
21
  end
22
22
  end
23
23
  end
@@ -4,17 +4,16 @@ module ActiveJob
4
4
  module Serializers
5
5
  class SymbolSerializer < ObjectSerializer # :nodoc:
6
6
  def serialize(argument)
7
- super("value" => argument.to_s)
7
+ super("value" => argument.name)
8
8
  end
9
9
 
10
10
  def deserialize(argument)
11
11
  argument["value"].to_sym
12
12
  end
13
13
 
14
- private
15
- def klass
16
- Symbol
17
- end
14
+ def klass
15
+ Symbol
16
+ end
18
17
  end
19
18
  end
20
19
  end
@@ -7,10 +7,9 @@ module ActiveJob
7
7
  Time.iso8601(hash["value"])
8
8
  end
9
9
 
10
- private
11
- def klass
12
- Time
13
- end
10
+ def klass
11
+ Time
12
+ end
14
13
  end
15
14
  end
16
15
  end
@@ -16,10 +16,9 @@ module ActiveJob
16
16
  Time.iso8601(hash["value"]).in_time_zone(hash["time_zone"] || Time.zone)
17
17
  end
18
18
 
19
- private
20
- def klass
21
- ActiveSupport::TimeWithZone
22
- end
19
+ def klass
20
+ ActiveSupport::TimeWithZone
21
+ end
23
22
  end
24
23
  end
25
24
  end
@@ -19,16 +19,17 @@ module ActiveJob
19
19
  autoload :ModuleSerializer
20
20
  autoload :RangeSerializer
21
21
  autoload :BigDecimalSerializer
22
+ autoload :ActionControllerParametersSerializer
22
23
 
23
- mattr_accessor :_additional_serializers
24
- self._additional_serializers = Set.new
24
+ @serializers = Set.new
25
+ @serializers_index = {}
25
26
 
26
27
  class << self
27
28
  # Returns serialized representative of the passed object.
28
29
  # Will look up through all known serializers.
29
30
  # Raises ActiveJob::SerializationError if it can't find a proper serializer.
30
31
  def serialize(argument)
31
- serializer = serializers.detect { |s| s.serialize?(argument) }
32
+ serializer = @serializers_index[argument.class] || serializers.find { |s| s.serialize?(argument) }
32
33
  raise SerializationError.new("Unsupported argument type: #{argument.class.name}") unless serializer
33
34
  serializer.serialize(argument)
34
35
  end
@@ -47,24 +48,62 @@ module ActiveJob
47
48
  end
48
49
 
49
50
  # Returns list of known serializers.
50
- def serializers
51
- self._additional_serializers
51
+ attr_reader :serializers
52
+
53
+ def serializers=(serializers)
54
+ @serializers = serializers
55
+ index_serializers
52
56
  end
53
57
 
54
58
  # Adds new serializers to a list of known serializers.
55
59
  def add_serializers(*new_serializers)
56
- self._additional_serializers += new_serializers.flatten
60
+ new_serializers = new_serializers.flatten
61
+ new_serializers.map! do |s|
62
+ if s.is_a?(Class) && s < ObjectSerializer
63
+ s.instance
64
+ else
65
+ s
66
+ end
67
+ end
68
+
69
+ @serializers += new_serializers
70
+ index_serializers
71
+ @serializers
57
72
  end
73
+
74
+ private
75
+ def index_serializers
76
+ @serializers_index.clear
77
+ serializers.each do |s|
78
+ if s.respond_to?(:klass)
79
+ @serializers_index[s.klass] = s
80
+ elsif s.respond_to?(:klass, true)
81
+ klass = s.send(:klass)
82
+ ActiveJob.deprecator.warn(<<~MSG.squish)
83
+ #{s.class.name}#klass method should be public.
84
+ This will raise an error in Rails 8.2.
85
+ MSG
86
+ @serializers_index[klass] = s
87
+ else
88
+ ActiveJob.deprecator.warn(
89
+ <<~MSG.squish
90
+ #{s.class.name} should implement a public #klass method.
91
+ This will raise an error in Rails 8.2.
92
+ MSG
93
+ )
94
+ end
95
+ end
96
+ end
58
97
  end
59
98
 
60
- add_serializers SymbolSerializer,
61
- DurationSerializer,
62
- DateTimeSerializer,
63
- DateSerializer,
64
- TimeWithZoneSerializer,
65
- TimeSerializer,
66
- ModuleSerializer,
67
- RangeSerializer,
68
- BigDecimalSerializer
99
+ add_serializers SymbolSerializer.instance,
100
+ DurationSerializer.instance,
101
+ DateTimeSerializer.instance,
102
+ DateSerializer.instance,
103
+ TimeWithZoneSerializer.instance,
104
+ TimeSerializer.instance,
105
+ ModuleSerializer.instance,
106
+ RangeSerializer.instance,
107
+ BigDecimalSerializer.instance
69
108
  end
70
109
  end
@@ -0,0 +1,220 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/structured_event_subscriber"
4
+
5
+ module ActiveJob
6
+ class StructuredEventSubscriber < ActiveSupport::StructuredEventSubscriber # :nodoc:
7
+ def enqueue(event)
8
+ job = event.payload[:job]
9
+ adapter = event.payload[:adapter]
10
+ exception = event.payload[:exception_object] || job.enqueue_error
11
+ payload = {
12
+ job_class: job.class.name,
13
+ job_id: job.job_id,
14
+ queue: job.queue_name,
15
+ adapter: ActiveJob.adapter_name(adapter),
16
+ aborted: event.payload[:aborted],
17
+ }
18
+
19
+ if exception
20
+ payload[:exception_class] = exception.class.name
21
+ payload[:exception_message] = exception.message
22
+ end
23
+
24
+ if job.class.log_arguments?
25
+ payload[:arguments] = job.arguments
26
+ end
27
+
28
+ emit_event("active_job.enqueued", payload)
29
+ end
30
+
31
+ def enqueue_at(event)
32
+ job = event.payload[:job]
33
+ adapter = event.payload[:adapter]
34
+ exception = event.payload[:exception_object] || job.enqueue_error
35
+ payload = {
36
+ job_class: job.class.name,
37
+ job_id: job.job_id,
38
+ queue: job.queue_name,
39
+ scheduled_at: job.scheduled_at,
40
+ adapter: ActiveJob.adapter_name(adapter),
41
+ aborted: event.payload[:aborted],
42
+ }
43
+
44
+ if exception
45
+ payload[:exception_class] = exception.class.name
46
+ payload[:exception_message] = exception.message
47
+ end
48
+
49
+ if job.class.log_arguments?
50
+ payload[:arguments] = job.arguments
51
+ end
52
+
53
+ emit_event("active_job.enqueued_at", payload)
54
+ end
55
+
56
+ def enqueue_all(event)
57
+ jobs = event.payload[:jobs]
58
+ adapter = event.payload[:adapter]
59
+ enqueued_count = event.payload[:enqueued_count].to_i
60
+ failed_count = jobs.size - enqueued_count
61
+
62
+ emit_event("active_job.bulk_enqueued",
63
+ adapter: ActiveJob.adapter_name(adapter),
64
+ job_count: jobs.size,
65
+ enqueued_count: enqueued_count,
66
+ failed_enqueue_count: failed_count,
67
+ enqueued_classes: jobs.filter_map do |job|
68
+ job.class.name if jobs.count == enqueued_count || job.successfully_enqueued?
69
+ end.tally
70
+ )
71
+ end
72
+
73
+ def perform_start(event)
74
+ job = event.payload[:job]
75
+ payload = {
76
+ job_class: job.class.name,
77
+ job_id: job.job_id,
78
+ queue: job.queue_name,
79
+ enqueued_at: job.enqueued_at&.utc&.iso8601(9),
80
+ }
81
+ if job.class.log_arguments?
82
+ payload[:arguments] = job.arguments
83
+ end
84
+ emit_event("active_job.started", payload)
85
+ end
86
+
87
+ def perform(event)
88
+ job = event.payload[:job]
89
+ exception = event.payload[:exception_object]
90
+ adapter = event.payload[:adapter]
91
+ payload = {
92
+ job_class: job.class.name,
93
+ job_id: job.job_id,
94
+ queue: job.queue_name,
95
+ adapter: ActiveJob.adapter_name(adapter),
96
+ aborted: event.payload[:aborted],
97
+ duration: event.duration.round(2),
98
+ }
99
+
100
+ if exception
101
+ payload[:exception_class] = exception.class.name
102
+ payload[:exception_message] = exception.message
103
+ payload[:exception_backtrace] = exception.backtrace
104
+ end
105
+
106
+ emit_event("active_job.completed", payload)
107
+ end
108
+
109
+ def enqueue_retry(event)
110
+ job = event.payload[:job]
111
+ exception = event.payload[:error]
112
+ wait = event.payload[:wait]
113
+
114
+ emit_event("active_job.retry_scheduled",
115
+ job_class: job.class.name,
116
+ job_id: job.job_id,
117
+ executions: job.executions,
118
+ wait_seconds: wait.to_i,
119
+ exception_class: exception&.class&.name,
120
+ exception_message: exception&.message
121
+ )
122
+ end
123
+
124
+ def retry_stopped(event)
125
+ job = event.payload[:job]
126
+ exception = event.payload[:error]
127
+
128
+ emit_event("active_job.retry_stopped",
129
+ job_class: job.class.name,
130
+ job_id: job.job_id,
131
+ executions: job.executions,
132
+ exception_class: exception.class.name,
133
+ exception_message: exception.message
134
+ )
135
+ end
136
+
137
+ def discard(event)
138
+ job = event.payload[:job]
139
+ exception = event.payload[:error]
140
+
141
+ emit_event("active_job.discarded",
142
+ job_class: job.class.name,
143
+ job_id: job.job_id,
144
+ exception_class: exception.class.name,
145
+ exception_message: exception.message
146
+ )
147
+ end
148
+
149
+ def interrupt(event)
150
+ job = event.payload[:job]
151
+ description = event.payload[:description]
152
+ reason = event.payload[:reason]
153
+
154
+ emit_event("active_job.interrupt",
155
+ job_class: job.class.name,
156
+ job_id: job.job_id,
157
+ description: description,
158
+ reason: reason,
159
+ )
160
+ end
161
+
162
+ def resume(event)
163
+ job = event.payload[:job]
164
+ description = event.payload[:description]
165
+
166
+ emit_event("active_job.resume",
167
+ job_class: job.class.name,
168
+ job_id: job.job_id,
169
+ description: description,
170
+ )
171
+ end
172
+
173
+ def step_skipped(event)
174
+ job = event.payload[:job]
175
+ step = event.payload[:step]
176
+
177
+ emit_event("active_job.step_skipped",
178
+ job_class: job.class.name,
179
+ job_id: job.job_id,
180
+ step: step.name,
181
+ )
182
+ end
183
+
184
+ def step_started(event)
185
+ job = event.payload[:job]
186
+ step = event.payload[:step]
187
+
188
+ emit_event("active_job.step_started",
189
+ job_class: job.class.name,
190
+ job_id: job.job_id,
191
+ step: step.name,
192
+ cursor: step.cursor,
193
+ resumed: step.resumed?,
194
+ )
195
+ end
196
+
197
+ def step(event)
198
+ job = event.payload[:job]
199
+ step = event.payload[:step]
200
+ exception = event.payload[:exception_object]
201
+ payload = {
202
+ job_class: job.class.name,
203
+ job_id: job.job_id,
204
+ step: step.name,
205
+ cursor: step.cursor,
206
+ interrupted: event.payload[:interrupted],
207
+ duration: event.duration.round(2),
208
+ }
209
+
210
+ if exception
211
+ payload[:exception_class] = exception.class.name
212
+ payload[:exception_message] = exception.message
213
+ end
214
+
215
+ emit_event("active_job.step", payload)
216
+ end
217
+ end
218
+ end
219
+
220
+ ActiveJob::StructuredEventSubscriber.attach_to :active_job
data/lib/active_job.rb CHANGED
@@ -39,9 +39,12 @@ module ActiveJob
39
39
  autoload :Arguments
40
40
  autoload :DeserializationError, "active_job/arguments"
41
41
  autoload :SerializationError, "active_job/arguments"
42
+ autoload :UnknownJobClassError, "active_job/core"
42
43
  autoload :EnqueueAfterTransactionCommit
43
44
 
44
45
  eager_autoload do
46
+ autoload :Continuable
47
+ autoload :Continuation
45
48
  autoload :Serializers
46
49
  autoload :ConfiguredJob
47
50
  end