activejob 6.0.6.1 → 6.1.7.6

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.
@@ -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