activejob 6.0.6.1 → 6.1.7.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -7,6 +7,10 @@ module ActiveJob
7
7
  module Exceptions
8
8
  extend ActiveSupport::Concern
9
9
 
10
+ included do
11
+ class_attribute :retry_jitter, instance_accessor: false, instance_predicate: false, default: 0.0
12
+ end
13
+
10
14
  module ClassMethods
11
15
  # Catch the exception and reschedule job for re-execution after so many seconds, for a specific number of attempts.
12
16
  # If the exception keeps getting raised beyond the specified number of attempts, the exception is allowed to
@@ -18,23 +22,25 @@ module ActiveJob
18
22
  #
19
23
  # ==== Options
20
24
  # * <tt>:wait</tt> - Re-enqueues the job with a delay specified either in seconds (default: 3 seconds),
21
- # as a computing proc that the number of executions so far as an argument, or as a symbol reference of
22
- # <tt>:exponentially_longer</tt>, which applies the wait algorithm of <tt>(executions ** 4) + 2</tt>
23
- # (first wait 3s, then 18s, then 83s, etc)
25
+ # as a computing proc that takes the number of executions so far as an argument, or as a symbol reference of
26
+ # <tt>:exponentially_longer</tt>, which applies the wait algorithm of <tt>((executions**4) + (Kernel.rand * (executions**4) * jitter)) + 2</tt>
27
+ # (first wait ~3s, then ~18s, then ~83s, etc)
24
28
  # * <tt>:attempts</tt> - Re-enqueues the job the specified number of times (default: 5 attempts)
25
29
  # * <tt>:queue</tt> - Re-enqueues the job on a different queue
26
30
  # * <tt>:priority</tt> - Re-enqueues the job with a different priority
31
+ # * <tt>:jitter</tt> - A random delay of wait time used when calculating backoff. The default is 15% (0.15) which represents the upper bound of possible wait time (expressed as a percentage)
27
32
  #
28
33
  # ==== Examples
29
34
  #
30
35
  # class RemoteServiceJob < ActiveJob::Base
31
- # retry_on CustomAppException # defaults to 3s wait, 5 attempts
36
+ # retry_on CustomAppException # defaults to ~3s wait, 5 attempts
32
37
  # retry_on AnotherCustomAppException, wait: ->(executions) { executions * 2 }
33
38
  #
34
39
  # retry_on ActiveRecord::Deadlocked, wait: 5.seconds, attempts: 3
35
40
  # retry_on Net::OpenTimeout, Timeout::Error, wait: :exponentially_longer, attempts: 10 # retries at most 10 times for Net::OpenTimeout and Timeout::Error combined
36
41
  # # To retry at most 10 times for each individual exception:
37
42
  # # retry_on Net::OpenTimeout, wait: :exponentially_longer, attempts: 10
43
+ # # retry_on Net::ReadTimeout, wait: 5.seconds, jitter: 0.30, attempts: 10
38
44
  # # retry_on Timeout::Error, wait: :exponentially_longer, attempts: 10
39
45
  #
40
46
  # retry_on(YetAnotherCustomAppException) do |job, error|
@@ -47,12 +53,11 @@ module ActiveJob
47
53
  # # Might raise Net::OpenTimeout or Timeout::Error when the remote service is down
48
54
  # end
49
55
  # end
50
- def retry_on(*exceptions, wait: 3.seconds, attempts: 5, queue: nil, priority: nil)
56
+ def retry_on(*exceptions, wait: 3.seconds, attempts: 5, queue: nil, priority: nil, jitter: JITTER_DEFAULT)
51
57
  rescue_from(*exceptions) do |error|
52
58
  executions = executions_for(exceptions)
53
-
54
59
  if executions < attempts
55
- retry_job wait: determine_delay(seconds_or_duration_or_algorithm: wait, executions: executions), queue: queue, priority: priority, error: error
60
+ retry_job wait: determine_delay(seconds_or_duration_or_algorithm: wait, executions: executions, jitter: jitter), queue: queue, priority: priority, error: error
56
61
  else
57
62
  if block_given?
58
63
  instrument :retry_stopped, error: error do
@@ -115,22 +120,27 @@ module ActiveJob
115
120
  # end
116
121
  # end
117
122
  def retry_job(options = {})
118
- instrument :enqueue_retry, **options.slice(:error, :wait) do
123
+ instrument :enqueue_retry, options.slice(:error, :wait) do
119
124
  enqueue options
120
125
  end
121
126
  end
122
127
 
123
128
  private
124
- def determine_delay(seconds_or_duration_or_algorithm:, executions:)
129
+ JITTER_DEFAULT = Object.new
130
+ private_constant :JITTER_DEFAULT
131
+
132
+ def determine_delay(seconds_or_duration_or_algorithm:, executions:, jitter: JITTER_DEFAULT)
133
+ jitter = jitter == JITTER_DEFAULT ? self.class.retry_jitter : (jitter || 0.0)
134
+
125
135
  case seconds_or_duration_or_algorithm
126
136
  when :exponentially_longer
127
- (executions**4) + 2
128
- when ActiveSupport::Duration
129
- duration = seconds_or_duration_or_algorithm
130
- duration.to_i
131
- when Integer
132
- seconds = seconds_or_duration_or_algorithm
133
- seconds
137
+ delay = executions**4
138
+ delay_jitter = determine_jitter_for_delay(delay, jitter)
139
+ delay + delay_jitter + 2
140
+ when ActiveSupport::Duration, Integer
141
+ delay = seconds_or_duration_or_algorithm.to_i
142
+ delay_jitter = determine_jitter_for_delay(delay, jitter)
143
+ delay + delay_jitter
134
144
  when Proc
135
145
  algorithm = seconds_or_duration_or_algorithm
136
146
  algorithm.call(executions)
@@ -139,10 +149,9 @@ module ActiveJob
139
149
  end
140
150
  end
141
151
 
142
- def instrument(name, error: nil, wait: nil, &block)
143
- payload = { job: self, adapter: self.class.queue_adapter, error: error, wait: wait }
144
-
145
- ActiveSupport::Notifications.instrument("#{name}.active_job", payload, &block)
152
+ def determine_jitter_for_delay(delay, jitter)
153
+ return 0.0 if jitter.zero?
154
+ Kernel.rand * delay * jitter
146
155
  end
147
156
 
148
157
  def executions_for(exceptions)
@@ -29,13 +29,21 @@ module ActiveJob
29
29
 
30
30
  # Performs the job immediately. The job is not sent to the queuing adapter
31
31
  # but directly executed by blocking the execution of others until it's finished.
32
+ # +perform_now+ returns the value of your job's +perform+ method.
32
33
  #
33
- # MyJob.new(*args).perform_now
34
+ # class MyJob < ActiveJob::Base
35
+ # def perform
36
+ # "Hello World!"
37
+ # end
38
+ # end
39
+ #
40
+ # puts MyJob.new(*args).perform_now # => "Hello World!"
34
41
  def perform_now
35
42
  # Guard against jobs that were persisted before we started counting executions by zeroing out nil counters
36
43
  self.executions = (executions || 0) + 1
37
44
 
38
45
  deserialize_arguments_if_needed
46
+
39
47
  run_callbacks :perform do
40
48
  perform(*arguments)
41
49
  end
@@ -8,9 +8,9 @@ module ActiveJob
8
8
 
9
9
  module VERSION
10
10
  MAJOR = 6
11
- MINOR = 0
12
- TINY = 6
13
- PRE = "1"
11
+ MINOR = 1
12
+ TINY = 7
13
+ PRE = "6"
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveJob
4
+ module Instrumentation #:nodoc:
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ around_enqueue do |_, block|
9
+ scheduled_at ? instrument(:enqueue_at, &block) : instrument(:enqueue, &block)
10
+ end
11
+
12
+ around_perform do |_, block|
13
+ instrument :perform_start
14
+ instrument :perform, &block
15
+ end
16
+ end
17
+
18
+ private
19
+ def instrument(operation, payload = {}, &block)
20
+ enhanced_block = ->(event_payload) do
21
+ value = block.call if block
22
+
23
+ if defined?(@_halted_callback_hook_called) && @_halted_callback_hook_called
24
+ event_payload[:aborted] = true
25
+ @_halted_callback_hook_called = nil
26
+ end
27
+
28
+ value
29
+ end
30
+
31
+ ActiveSupport::Notifications.instrument \
32
+ "#{operation}.active_job", payload.merge(adapter: queue_adapter, job: self), &enhanced_block
33
+ end
34
+
35
+ def halted_callback_hook(*)
36
+ super
37
+ @_halted_callback_hook_called = true
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/string/filters"
4
+ require "active_support/log_subscriber"
5
+
6
+ module ActiveJob
7
+ class LogSubscriber < ActiveSupport::LogSubscriber #:nodoc:
8
+ def enqueue(event)
9
+ job = event.payload[:job]
10
+ ex = event.payload[:exception_object]
11
+
12
+ if ex
13
+ error do
14
+ "Failed enqueuing #{job.class.name} to #{queue_name(event)}: #{ex.class} (#{ex.message})"
15
+ end
16
+ elsif event.payload[:aborted]
17
+ info do
18
+ "Failed enqueuing #{job.class.name} to #{queue_name(event)}, a before_enqueue callback halted the enqueuing execution."
19
+ end
20
+ else
21
+ info do
22
+ "Enqueued #{job.class.name} (Job ID: #{job.job_id}) to #{queue_name(event)}" + args_info(job)
23
+ end
24
+ end
25
+ end
26
+
27
+ def enqueue_at(event)
28
+ job = event.payload[:job]
29
+ ex = event.payload[:exception_object]
30
+
31
+ if ex
32
+ error do
33
+ "Failed enqueuing #{job.class.name} to #{queue_name(event)}: #{ex.class} (#{ex.message})"
34
+ end
35
+ elsif event.payload[:aborted]
36
+ info do
37
+ "Failed enqueuing #{job.class.name} to #{queue_name(event)}, a before_enqueue callback halted the enqueuing execution."
38
+ end
39
+ else
40
+ info do
41
+ "Enqueued #{job.class.name} (Job ID: #{job.job_id}) to #{queue_name(event)} at #{scheduled_at(event)}" + args_info(job)
42
+ end
43
+ end
44
+ end
45
+
46
+ def perform_start(event)
47
+ info do
48
+ job = event.payload[:job]
49
+ "Performing #{job.class.name} (Job ID: #{job.job_id}) from #{queue_name(event)} enqueued at #{job.enqueued_at}" + args_info(job)
50
+ end
51
+ end
52
+
53
+ def perform(event)
54
+ job = event.payload[:job]
55
+ ex = event.payload[:exception_object]
56
+ if ex
57
+ error do
58
+ "Error performing #{job.class.name} (Job ID: #{job.job_id}) from #{queue_name(event)} in #{event.duration.round(2)}ms: #{ex.class} (#{ex.message}):\n" + Array(ex.backtrace).join("\n")
59
+ end
60
+ elsif event.payload[:aborted]
61
+ error do
62
+ "Error performing #{job.class.name} (Job ID: #{job.job_id}) from #{queue_name(event)} in #{event.duration.round(2)}ms: a before_perform callback halted the job execution"
63
+ end
64
+ else
65
+ info do
66
+ "Performed #{job.class.name} (Job ID: #{job.job_id}) from #{queue_name(event)} in #{event.duration.round(2)}ms"
67
+ end
68
+ end
69
+ end
70
+
71
+ def enqueue_retry(event)
72
+ job = event.payload[:job]
73
+ ex = event.payload[:error]
74
+ wait = event.payload[:wait]
75
+
76
+ info do
77
+ if ex
78
+ "Retrying #{job.class} in #{wait.to_i} seconds, due to a #{ex.class}."
79
+ else
80
+ "Retrying #{job.class} in #{wait.to_i} seconds."
81
+ end
82
+ end
83
+ end
84
+
85
+ def retry_stopped(event)
86
+ job = event.payload[:job]
87
+ ex = event.payload[:error]
88
+
89
+ error do
90
+ "Stopped retrying #{job.class} due to a #{ex.class}, which reoccurred on #{job.executions} attempts."
91
+ end
92
+ end
93
+
94
+ def discard(event)
95
+ job = event.payload[:job]
96
+ ex = event.payload[:error]
97
+
98
+ error do
99
+ "Discarded #{job.class} due to a #{ex.class}."
100
+ end
101
+ end
102
+
103
+ private
104
+ def queue_name(event)
105
+ event.payload[:adapter].class.name.demodulize.remove("Adapter") + "(#{event.payload[:job].queue_name})"
106
+ end
107
+
108
+ def args_info(job)
109
+ if job.class.log_arguments? && job.arguments.any?
110
+ " with arguments: " +
111
+ job.arguments.map { |arg| format(arg).inspect }.join(", ")
112
+ else
113
+ ""
114
+ end
115
+ end
116
+
117
+ def format(arg)
118
+ case arg
119
+ when Hash
120
+ arg.transform_values { |value| format(value) }
121
+ when Array
122
+ arg.map { |value| format(value) }
123
+ when GlobalID::Identification
124
+ arg.to_global_id rescue arg
125
+ else
126
+ arg
127
+ end
128
+ end
129
+
130
+ def scheduled_at(event)
131
+ Time.at(event.payload[:job].scheduled_at).utc
132
+ end
133
+
134
+ def logger
135
+ ActiveJob::Base.logger
136
+ end
137
+ end
138
+ end
139
+
140
+ ActiveJob::LogSubscriber.attach_to :active_job
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/core_ext/string/filters"
4
3
  require "active_support/tagged_logging"
5
4
  require "active_support/logger"
6
5
 
@@ -10,32 +9,10 @@ module ActiveJob
10
9
 
11
10
  included do
12
11
  cattr_accessor :logger, default: ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDOUT))
12
+ class_attribute :log_arguments, instance_accessor: false, default: true
13
13
 
14
- around_enqueue do |_, block|
15
- tag_logger do
16
- block.call
17
- end
18
- end
19
-
20
- around_perform do |job, block|
21
- tag_logger(job.class.name, job.job_id) do
22
- payload = { adapter: job.class.queue_adapter, job: job }
23
- ActiveSupport::Notifications.instrument("perform_start.active_job", payload.dup)
24
- ActiveSupport::Notifications.instrument("perform.active_job", payload) do
25
- block.call
26
- end
27
- end
28
- end
29
-
30
- around_enqueue do |job, block|
31
- if job.scheduled_at
32
- ActiveSupport::Notifications.instrument("enqueue_at.active_job",
33
- adapter: job.class.queue_adapter, job: job, &block)
34
- else
35
- ActiveSupport::Notifications.instrument("enqueue.active_job",
36
- adapter: job.class.queue_adapter, job: job, &block)
37
- end
38
- end
14
+ around_enqueue { |_, block| tag_logger(&block) }
15
+ around_perform { |job, block| tag_logger(job.class.name, job.job_id, &block) }
39
16
  end
40
17
 
41
18
  private
@@ -51,111 +28,5 @@ module ActiveJob
51
28
  def logger_tagged_by_active_job?
52
29
  logger.formatter.current_tags.include?("ActiveJob")
53
30
  end
54
-
55
- class LogSubscriber < ActiveSupport::LogSubscriber #:nodoc:
56
- def enqueue(event)
57
- info do
58
- job = event.payload[:job]
59
- "Enqueued #{job.class.name} (Job ID: #{job.job_id}) to #{queue_name(event)}" + args_info(job)
60
- end
61
- end
62
-
63
- def enqueue_at(event)
64
- info do
65
- job = event.payload[:job]
66
- "Enqueued #{job.class.name} (Job ID: #{job.job_id}) to #{queue_name(event)} at #{scheduled_at(event)}" + args_info(job)
67
- end
68
- end
69
-
70
- def perform_start(event)
71
- info do
72
- job = event.payload[:job]
73
- "Performing #{job.class.name} (Job ID: #{job.job_id}) from #{queue_name(event)} enqueued at #{job.enqueued_at}" + args_info(job)
74
- end
75
- end
76
-
77
- def perform(event)
78
- job = event.payload[:job]
79
- ex = event.payload[:exception_object]
80
- if ex
81
- error do
82
- "Error performing #{job.class.name} (Job ID: #{job.job_id}) from #{queue_name(event)} in #{event.duration.round(2)}ms: #{ex.class} (#{ex.message}):\n" + Array(ex.backtrace).join("\n")
83
- end
84
- else
85
- info do
86
- "Performed #{job.class.name} (Job ID: #{job.job_id}) from #{queue_name(event)} in #{event.duration.round(2)}ms"
87
- end
88
- end
89
- end
90
-
91
- def enqueue_retry(event)
92
- job = event.payload[:job]
93
- ex = event.payload[:error]
94
- wait = event.payload[:wait]
95
-
96
- info do
97
- if ex
98
- "Retrying #{job.class} in #{wait.to_i} seconds, due to a #{ex.class}."
99
- else
100
- "Retrying #{job.class} in #{wait.to_i} seconds."
101
- end
102
- end
103
- end
104
-
105
- def retry_stopped(event)
106
- job = event.payload[:job]
107
- ex = event.payload[:error]
108
-
109
- error do
110
- "Stopped retrying #{job.class} due to a #{ex.class}, which reoccurred on #{job.executions} attempts."
111
- end
112
- end
113
-
114
- def discard(event)
115
- job = event.payload[:job]
116
- ex = event.payload[:error]
117
-
118
- error do
119
- "Discarded #{job.class} due to a #{ex.class}."
120
- end
121
- end
122
-
123
- private
124
- def queue_name(event)
125
- event.payload[:adapter].class.name.demodulize.remove("Adapter") + "(#{event.payload[:job].queue_name})"
126
- end
127
-
128
- def args_info(job)
129
- if job.arguments.any?
130
- " with arguments: " +
131
- job.arguments.map { |arg| format(arg).inspect }.join(", ")
132
- else
133
- ""
134
- end
135
- end
136
-
137
- def format(arg)
138
- case arg
139
- when Hash
140
- arg.transform_values { |value| format(value) }
141
- when Array
142
- arg.map { |value| format(value) }
143
- when GlobalID::Identification
144
- arg.to_global_id rescue arg
145
- else
146
- arg
147
- end
148
- end
149
-
150
- def scheduled_at(event)
151
- Time.at(event.payload[:job].scheduled_at).utc
152
- end
153
-
154
- def logger
155
- ActiveJob::Base.logger
156
- end
157
- end
158
31
  end
159
32
  end
160
-
161
- ActiveJob::Logging::LogSubscriber.attach_to :active_job
@@ -11,6 +11,9 @@ module ActiveJob
11
11
  included do
12
12
  class_attribute :_queue_adapter_name, instance_accessor: false, instance_predicate: false
13
13
  class_attribute :_queue_adapter, instance_accessor: false, instance_predicate: false
14
+
15
+ delegate :queue_adapter, to: :class
16
+
14
17
  self.queue_adapter = :async
15
18
  end
16
19
 
@@ -23,7 +26,7 @@ module ActiveJob
23
26
  end
24
27
 
25
28
  # Returns string denoting the name of the configured queue adapter.
26
- # By default returns +"async"+.
29
+ # By default returns <tt>"async"</tt>.
27
30
  def queue_adapter_name
28
31
  _queue_adapter_name
29
32
  end
@@ -10,7 +10,7 @@ module ActiveJob
10
10
  # This reduces the cost of hosting on a service like Heroku along
11
11
  # with the memory footprint of having to maintain additional jobs if
12
12
  # hosting on a dedicated server. All queues can run within a
13
- # single application (eg. Rails, Sinatra, etc.) process.
13
+ # single application (e.g. Rails, Sinatra, etc.) process.
14
14
  #
15
15
  # Read more about Sucker Punch {here}[https://github.com/brandonhilkert/sucker_punch].
16
16
  #
@@ -12,7 +12,7 @@ module ActiveJob
12
12
  #
13
13
  # Rails.application.config.active_job.queue_adapter = :test
14
14
  class TestAdapter
15
- attr_accessor(:perform_enqueued_jobs, :perform_enqueued_at_jobs, :filter, :reject, :queue)
15
+ attr_accessor(:perform_enqueued_jobs, :perform_enqueued_at_jobs, :filter, :reject, :queue, :at)
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.
@@ -54,7 +54,11 @@ module ActiveJob
54
54
  end
55
55
 
56
56
  def filtered?(job)
57
- filtered_queue?(job) || filtered_job_class?(job)
57
+ filtered_queue?(job) || filtered_job_class?(job) || filtered_time?(job)
58
+ end
59
+
60
+ def filtered_time?(job)
61
+ job.scheduled_at > at.to_f if at && job.scheduled_at
58
62
  end
59
63
 
60
64
  def filtered_queue?(job)
@@ -72,7 +72,7 @@ module ActiveJob
72
72
  # Yes: Allows the priority to be set on the job object, at the queue level or
73
73
  # as default configuration option.
74
74
  #
75
- # No: Does not allow the priority of jobs to be configured.
75
+ # No: The adapter does not allow the priority of jobs to be configured.
76
76
  #
77
77
  # N/A: The adapter does not support queuing, and therefore sorting them.
78
78
  #
@@ -86,6 +86,8 @@ module ActiveJob
86
86
  #
87
87
  # Global: The adapter is configured that all jobs have a maximum run time.
88
88
  #
89
+ # No: The adapter does not allow the timeout of jobs to be configured.
90
+ #
89
91
  # N/A: This adapter does not run in a separate process, and therefore timeout
90
92
  # is unsupported.
91
93
  #
@@ -99,6 +101,8 @@ module ActiveJob
99
101
  #
100
102
  # Global: The adapter has a global number of retries.
101
103
  #
104
+ # No: The adapter does not allow the number of retries to be configured.
105
+ #
102
106
  # N/A: The adapter does not run in a separate process, and therefore doesn't
103
107
  # support retries.
104
108
  #
@@ -6,7 +6,6 @@ module ActiveJob
6
6
 
7
7
  # Includes the ability to override the default queue name and prefix.
8
8
  module ClassMethods
9
- mattr_accessor :queue_name_prefix
10
9
  mattr_accessor :default_queue_name, default: "default"
11
10
 
12
11
  # Specifies the name of the queue to process the job on.
@@ -49,13 +48,14 @@ module ActiveJob
49
48
  def queue_name_from_part(part_name) #:nodoc:
50
49
  queue_name = part_name || default_queue_name
51
50
  name_parts = [queue_name_prefix.presence, queue_name]
52
- name_parts.compact.join(queue_name_delimiter)
51
+ -name_parts.compact.join(queue_name_delimiter)
53
52
  end
54
53
  end
55
54
 
56
55
  included do
57
56
  class_attribute :queue_name, instance_accessor: false, default: -> { self.class.default_queue_name }
58
57
  class_attribute :queue_name_delimiter, instance_accessor: false, default: "_"
58
+ class_attribute :queue_name_prefix
59
59
  end
60
60
 
61
61
  # Returns the name of the queue the job will be run on.
@@ -34,6 +34,10 @@ module ActiveJob
34
34
  ActiveSupport.on_load(:action_dispatch_integration_test) do
35
35
  include ActiveJob::TestHelper
36
36
  end
37
+
38
+ ActiveSupport.on_load(:active_record) do
39
+ self.destroy_association_async_job = ActiveRecord::DestroyAssociationAsyncJob
40
+ end
37
41
  end
38
42
 
39
43
  initializer "active_job.set_reloader_hook" do |app|
@@ -2,11 +2,7 @@
2
2
 
3
3
  module ActiveJob
4
4
  module Serializers
5
- class DateTimeSerializer < ObjectSerializer # :nodoc:
6
- def serialize(time)
7
- super("value" => time.iso8601)
8
- end
9
-
5
+ class DateTimeSerializer < TimeObjectSerializer # :nodoc:
10
6
  def deserialize(hash)
11
7
  DateTime.iso8601(hash["value"])
12
8
  end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveJob
4
+ module Serializers
5
+ class ModuleSerializer < ObjectSerializer # :nodoc:
6
+ def serialize(constant)
7
+ super("value" => constant.name)
8
+ end
9
+
10
+ def deserialize(hash)
11
+ hash["value"].constantize
12
+ end
13
+
14
+ private
15
+ def klass
16
+ Module
17
+ end
18
+ end
19
+ end
20
+ end
@@ -39,7 +39,7 @@ module ActiveJob
39
39
  end
40
40
 
41
41
  # Deserializes an argument from a JSON primitive type.
42
- def deserialize(_argument)
42
+ def deserialize(json)
43
43
  raise NotImplementedError
44
44
  end
45
45
 
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveJob
4
+ module Serializers
5
+ class TimeObjectSerializer < ObjectSerializer # :nodoc:
6
+ NANO_PRECISION = 9
7
+
8
+ def serialize(time)
9
+ super("value" => time.iso8601(NANO_PRECISION))
10
+ end
11
+ end
12
+ end
13
+ end
@@ -2,11 +2,7 @@
2
2
 
3
3
  module ActiveJob
4
4
  module Serializers
5
- class TimeSerializer < ObjectSerializer # :nodoc:
6
- def serialize(time)
7
- super("value" => time.iso8601)
8
- end
9
-
5
+ class TimeSerializer < TimeObjectSerializer # :nodoc:
10
6
  def deserialize(hash)
11
7
  Time.iso8601(hash["value"])
12
8
  end
@@ -2,11 +2,7 @@
2
2
 
3
3
  module ActiveJob
4
4
  module Serializers
5
- class TimeWithZoneSerializer < ObjectSerializer # :nodoc:
6
- def serialize(time)
7
- super("value" => time.iso8601)
8
- end
9
-
5
+ class TimeWithZoneSerializer < TimeObjectSerializer # :nodoc:
10
6
  def deserialize(hash)
11
7
  Time.iso8601(hash["value"]).in_time_zone
12
8
  end