activejob 5.2.7.1 → 6.0.0.beta1

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 (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +69 -98
  3. data/MIT-LICENSE +1 -1
  4. data/README.md +12 -6
  5. data/lib/active_job/arguments.rb +40 -28
  6. data/lib/active_job/base.rb +3 -1
  7. data/lib/active_job/callbacks.rb +4 -1
  8. data/lib/active_job/core.rb +33 -21
  9. data/lib/active_job/enqueuing.rb +26 -5
  10. data/lib/active_job/exceptions.rb +33 -17
  11. data/lib/active_job/execution.rb +1 -1
  12. data/lib/active_job/gem_version.rb +4 -4
  13. data/lib/active_job/logging.rb +39 -8
  14. data/lib/active_job/queue_adapter.rb +2 -0
  15. data/lib/active_job/queue_adapters/async_adapter.rb +1 -1
  16. data/lib/active_job/queue_adapters/backburner_adapter.rb +2 -2
  17. data/lib/active_job/queue_adapters/inline_adapter.rb +1 -1
  18. data/lib/active_job/queue_adapters/test_adapter.rb +22 -8
  19. data/lib/active_job/queue_adapters.rb +5 -7
  20. data/lib/active_job/queue_name.rb +1 -1
  21. data/lib/active_job/railtie.rb +16 -1
  22. data/lib/active_job/serializers/date_serializer.rb +21 -0
  23. data/lib/active_job/serializers/date_time_serializer.rb +21 -0
  24. data/lib/active_job/serializers/duration_serializer.rb +24 -0
  25. data/lib/active_job/serializers/object_serializer.rb +54 -0
  26. data/lib/active_job/serializers/symbol_serializer.rb +21 -0
  27. data/lib/active_job/serializers/time_serializer.rb +21 -0
  28. data/lib/active_job/serializers/time_with_zone_serializer.rb +21 -0
  29. data/lib/active_job/serializers.rb +63 -0
  30. data/lib/active_job/test_helper.rb +262 -56
  31. data/lib/active_job/timezones.rb +13 -0
  32. data/lib/active_job/translation.rb +1 -1
  33. data/lib/active_job.rb +2 -1
  34. data/lib/rails/generators/job/job_generator.rb +4 -0
  35. metadata +23 -15
  36. data/lib/active_job/queue_adapters/qu_adapter.rb +0 -46
@@ -30,28 +30,38 @@ module ActiveJob
30
30
  # class RemoteServiceJob < ActiveJob::Base
31
31
  # retry_on CustomAppException # defaults to 3s wait, 5 attempts
32
32
  # retry_on AnotherCustomAppException, wait: ->(executions) { executions * 2 }
33
+ #
34
+ # retry_on ActiveRecord::Deadlocked, wait: 5.seconds, attempts: 3
35
+ # retry_on Net::OpenTimeout, Timeout::Error, wait: :exponentially_longer, attempts: 10 # retries at most 10 times for Net::OpenTimeout and Timeout::Error combined
36
+ # # To retry at most 10 times for each individual exception:
37
+ # # retry_on Net::OpenTimeout, wait: :exponentially_longer, attempts: 10
38
+ # # retry_on Timeout::Error, wait: :exponentially_longer, attempts: 10
39
+ #
33
40
  # retry_on(YetAnotherCustomAppException) do |job, error|
34
41
  # ExceptionNotifier.caught(error)
35
42
  # end
36
- # retry_on ActiveRecord::Deadlocked, wait: 5.seconds, attempts: 3
37
- # retry_on Net::OpenTimeout, wait: :exponentially_longer, attempts: 10
38
43
  #
39
44
  # def perform(*args)
40
45
  # # Might raise CustomAppException, AnotherCustomAppException, or YetAnotherCustomAppException for something domain specific
41
46
  # # Might raise ActiveRecord::Deadlocked when a local db deadlock is detected
42
- # # Might raise Net::OpenTimeout when the remote service is down
47
+ # # Might raise Net::OpenTimeout or Timeout::Error when the remote service is down
43
48
  # end
44
49
  # end
45
- def retry_on(exception, wait: 3.seconds, attempts: 5, queue: nil, priority: nil)
46
- rescue_from exception do |error|
47
- if executions < attempts
48
- logger.error "Retrying #{self.class} in #{wait} seconds, due to a #{exception}. The original exception was #{error.cause.inspect}."
49
- retry_job wait: determine_delay(wait), queue: queue, priority: priority
50
+ def retry_on(*exceptions, wait: 3.seconds, attempts: 5, queue: nil, priority: nil)
51
+ rescue_from(*exceptions) do |error|
52
+ # Guard against jobs that were persisted before we started having individual executions counters per retry_on
53
+ self.exception_executions ||= {}
54
+ self.exception_executions[exceptions.to_s] = (exception_executions[exceptions.to_s] || 0) + 1
55
+
56
+ if exception_executions[exceptions.to_s] < attempts
57
+ retry_job wait: determine_delay(wait), queue: queue, priority: priority, error: error
50
58
  else
51
59
  if block_given?
52
- yield self, error
60
+ instrument :retry_stopped, error: error do
61
+ yield self, error
62
+ end
53
63
  else
54
- logger.error "Stopped retrying #{self.class} due to a #{exception}, which reoccurred on #{executions} attempts. The original exception was #{error.cause.inspect}."
64
+ instrument :retry_stopped, error: error
55
65
  raise error
56
66
  end
57
67
  end
@@ -76,12 +86,10 @@ module ActiveJob
76
86
  # # Might raise CustomAppException for something domain specific
77
87
  # end
78
88
  # end
79
- def discard_on(exception)
80
- rescue_from exception do |error|
81
- if block_given?
82
- yield self, error
83
- else
84
- logger.error "Discarded #{self.class} due to a #{exception}. The original exception was #{error.cause.inspect}."
89
+ def discard_on(*exceptions)
90
+ rescue_from(*exceptions) do |error|
91
+ instrument :discard, error: error do
92
+ yield self, error if block_given?
85
93
  end
86
94
  end
87
95
  end
@@ -109,7 +117,9 @@ module ActiveJob
109
117
  # end
110
118
  # end
111
119
  def retry_job(options = {})
112
- enqueue options
120
+ instrument :enqueue_retry, options.slice(:error, :wait) do
121
+ enqueue options
122
+ end
113
123
  end
114
124
 
115
125
  private
@@ -130,5 +140,11 @@ module ActiveJob
130
140
  raise "Couldn't determine a delay based on #{seconds_or_duration_or_algorithm.inspect}"
131
141
  end
132
142
  end
143
+
144
+ def instrument(name, error: nil, wait: nil, &block)
145
+ payload = { job: self, adapter: self.class.queue_adapter, error: error, wait: wait }
146
+
147
+ ActiveSupport::Notifications.instrument("#{name}.active_job", payload, &block)
148
+ end
133
149
  end
134
150
  end
@@ -26,7 +26,7 @@ module ActiveJob
26
26
  end
27
27
  end
28
28
 
29
- # Performs the job immediately. The job is not sent to the queueing adapter
29
+ # Performs the job immediately. The job is not sent to the queuing adapter
30
30
  # but directly executed by blocking the execution of others until it's finished.
31
31
  #
32
32
  # MyJob.new(*args).perform_now
@@ -7,10 +7,10 @@ module ActiveJob
7
7
  end
8
8
 
9
9
  module VERSION
10
- MAJOR = 5
11
- MINOR = 2
12
- TINY = 7
13
- PRE = "1"
10
+ MAJOR = 6
11
+ MINOR = 0
12
+ TINY = 0
13
+ PRE = "beta1"
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/core_ext/hash/transform_values"
4
3
  require "active_support/core_ext/string/filters"
5
4
  require "active_support/tagged_logging"
6
5
  require "active_support/logger"
@@ -12,13 +11,13 @@ module ActiveJob
12
11
  included do
13
12
  cattr_accessor :logger, default: ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDOUT))
14
13
 
15
- around_enqueue do |_, block, _|
14
+ around_enqueue do |_, block|
16
15
  tag_logger do
17
16
  block.call
18
17
  end
19
18
  end
20
19
 
21
- around_perform do |job, block, _|
20
+ around_perform do |job, block|
22
21
  tag_logger(job.class.name, job.job_id) do
23
22
  payload = { adapter: job.class.queue_adapter, job: job }
24
23
  ActiveSupport::Notifications.instrument("perform_start.active_job", payload.dup)
@@ -28,13 +27,13 @@ module ActiveJob
28
27
  end
29
28
  end
30
29
 
31
- after_enqueue do |job|
30
+ around_enqueue do |job, block|
32
31
  if job.scheduled_at
33
- ActiveSupport::Notifications.instrument "enqueue_at.active_job",
34
- adapter: job.class.queue_adapter, job: job
32
+ ActiveSupport::Notifications.instrument("enqueue_at.active_job",
33
+ adapter: job.class.queue_adapter, job: job, &block)
35
34
  else
36
- ActiveSupport::Notifications.instrument "enqueue.active_job",
37
- adapter: job.class.queue_adapter, job: job
35
+ ActiveSupport::Notifications.instrument("enqueue.active_job",
36
+ adapter: job.class.queue_adapter, job: job, &block)
38
37
  end
39
38
  end
40
39
  end
@@ -89,6 +88,38 @@ module ActiveJob
89
88
  end
90
89
  end
91
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
+
92
123
  private
93
124
  def queue_name(event)
94
125
  event.payload[:adapter].class.name.demodulize.remove("Adapter") + "(#{event.payload[:job].queue_name})"
@@ -22,6 +22,8 @@ module ActiveJob
22
22
  _queue_adapter
23
23
  end
24
24
 
25
+ # Returns string denoting the name of the configured queue adapter.
26
+ # By default returns +"async"+.
25
27
  def queue_adapter_name
26
28
  _queue_adapter_name
27
29
  end
@@ -31,7 +31,7 @@ module ActiveJob
31
31
  # jobs. Since jobs share a single thread pool, long-running jobs will block
32
32
  # short-lived jobs. Fine for dev/test; bad for production.
33
33
  class AsyncAdapter
34
- # See {Concurrent::ThreadPoolExecutor}[https://ruby-concurrency.github.io/concurrent-ruby/Concurrent/ThreadPoolExecutor.html] for executor options.
34
+ # See {Concurrent::ThreadPoolExecutor}[https://ruby-concurrency.github.io/concurrent-ruby/master/Concurrent/ThreadPoolExecutor.html] for executor options.
35
35
  def initialize(**executor_options)
36
36
  @scheduler = Scheduler.new(**executor_options)
37
37
  end
@@ -16,12 +16,12 @@ module ActiveJob
16
16
  # Rails.application.config.active_job.queue_adapter = :backburner
17
17
  class BackburnerAdapter
18
18
  def enqueue(job) #:nodoc:
19
- Backburner::Worker.enqueue JobWrapper, [ job.serialize ], queue: job.queue_name
19
+ Backburner::Worker.enqueue(JobWrapper, [job.serialize], queue: job.queue_name, pri: job.priority)
20
20
  end
21
21
 
22
22
  def enqueue_at(job, timestamp) #:nodoc:
23
23
  delay = timestamp - Time.current.to_f
24
- Backburner::Worker.enqueue JobWrapper, [ job.serialize ], queue: job.queue_name, delay: delay
24
+ Backburner::Worker.enqueue(JobWrapper, [job.serialize], queue: job.queue_name, pri: job.priority, delay: delay)
25
25
  end
26
26
 
27
27
  class JobWrapper #:nodoc:
@@ -16,7 +16,7 @@ module ActiveJob
16
16
  end
17
17
 
18
18
  def enqueue_at(*) #:nodoc:
19
- raise NotImplementedError, "Use a queueing backend to enqueue jobs in the future. Read more at http://guides.rubyonrails.org/active_job_basics.html"
19
+ raise NotImplementedError, "Use a queueing backend to enqueue jobs in the future. Read more at https://guides.rubyonrails.org/active_job_basics.html"
20
20
  end
21
21
  end
22
22
  end
@@ -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)
15
+ attr_accessor(:perform_enqueued_jobs, :perform_enqueued_at_jobs, :filter, :reject, :queue)
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.
@@ -29,14 +29,14 @@ module ActiveJob
29
29
  return if filtered?(job)
30
30
 
31
31
  job_data = job_to_hash(job)
32
- enqueue_or_perform(perform_enqueued_jobs, job, job_data)
32
+ perform_or_enqueue(perform_enqueued_jobs, job, job_data)
33
33
  end
34
34
 
35
35
  def enqueue_at(job, timestamp) #:nodoc:
36
36
  return if filtered?(job)
37
37
 
38
38
  job_data = job_to_hash(job, at: timestamp)
39
- enqueue_or_perform(perform_enqueued_at_jobs, job, job_data)
39
+ perform_or_enqueue(perform_enqueued_at_jobs, job, job_data)
40
40
  end
41
41
 
42
42
  private
@@ -44,7 +44,7 @@ module ActiveJob
44
44
  { job: job.class, args: job.serialize.fetch("arguments"), queue: job.queue_name }.merge!(extras)
45
45
  end
46
46
 
47
- def enqueue_or_perform(perform, job, job_data)
47
+ def perform_or_enqueue(perform, job, job_data)
48
48
  if perform
49
49
  performed_jobs << job_data
50
50
  Base.execute job.serialize
@@ -54,14 +54,28 @@ module ActiveJob
54
54
  end
55
55
 
56
56
  def filtered?(job)
57
+ filtered_queue?(job) || filtered_job_class?(job)
58
+ end
59
+
60
+ def filtered_queue?(job)
61
+ if queue
62
+ job.queue_name != queue.to_s
63
+ end
64
+ end
65
+
66
+ def filtered_job_class?(job)
57
67
  if filter
58
- !Array(filter).include?(job.class)
68
+ !filter_as_proc(filter).call(job)
59
69
  elsif reject
60
- Array(reject).include?(job.class)
61
- else
62
- false
70
+ filter_as_proc(reject).call(job)
63
71
  end
64
72
  end
73
+
74
+ def filter_as_proc(filter)
75
+ return filter if filter.is_a?(Proc)
76
+
77
+ ->(job) { Array(filter).include?(job.class) }
78
+ end
65
79
  end
66
80
  end
67
81
  end
@@ -3,11 +3,10 @@
3
3
  module ActiveJob
4
4
  # == Active Job adapters
5
5
  #
6
- # Active Job has adapters for the following queueing backends:
6
+ # Active Job has adapters for the following queuing backends:
7
7
  #
8
8
  # * {Backburner}[https://github.com/nesquena/backburner]
9
9
  # * {Delayed Job}[https://github.com/collectiveidea/delayed_job]
10
- # * {Qu}[https://github.com/bkeepers/qu]
11
10
  # * {Que}[https://github.com/chanks/que]
12
11
  # * {queue_classic}[https://github.com/QueueClassic/queue_classic]
13
12
  # * {Resque}[https://github.com/resque/resque]
@@ -16,6 +15,7 @@ module ActiveJob
16
15
  # * {Sucker Punch}[https://github.com/brandonhilkert/sucker_punch]
17
16
  # * {Active Job Async Job}[http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters/AsyncAdapter.html]
18
17
  # * {Active Job Inline}[http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters/InlineAdapter.html]
18
+ # * Please Note: We are not accepting pull requests for new adapters. See the {README}[link:files/activejob/README_md.html] for more details.
19
19
  #
20
20
  # === Backends Features
21
21
  #
@@ -23,7 +23,6 @@ module ActiveJob
23
23
  # |-------------------|-------|--------|------------|------------|---------|---------|
24
24
  # | Backburner | Yes | Yes | Yes | Yes | Job | Global |
25
25
  # | Delayed Job | Yes | Yes | Yes | Job | Global | Global |
26
- # | Qu | Yes | Yes | No | No | No | Global |
27
26
  # | Que | Yes | Yes | Yes | Job | No | Job |
28
27
  # | queue_classic | Yes | Yes | Yes* | No | No | No |
29
28
  # | Resque | Yes | Yes | Yes (Gem) | Queue | Global | Yes |
@@ -53,7 +52,7 @@ module ActiveJob
53
52
  #
54
53
  # No: The adapter will run jobs at the next opportunity and cannot use perform_later.
55
54
  #
56
- # N/A: The adapter does not support queueing.
55
+ # N/A: The adapter does not support queuing.
57
56
  #
58
57
  # NOTE:
59
58
  # queue_classic supports job scheduling since version 3.1.
@@ -75,7 +74,7 @@ module ActiveJob
75
74
  #
76
75
  # No: Does not allow the priority of jobs to be configured.
77
76
  #
78
- # N/A: The adapter does not support queueing, and therefore sorting them.
77
+ # N/A: The adapter does not support queuing, and therefore sorting them.
79
78
  #
80
79
  # ==== Timeout
81
80
  #
@@ -114,7 +113,6 @@ module ActiveJob
114
113
  autoload :InlineAdapter
115
114
  autoload :BackburnerAdapter
116
115
  autoload :DelayedJobAdapter
117
- autoload :QuAdapter
118
116
  autoload :QueAdapter
119
117
  autoload :QueueClassicAdapter
120
118
  autoload :ResqueAdapter
@@ -123,7 +121,7 @@ module ActiveJob
123
121
  autoload :SuckerPunchAdapter
124
122
  autoload :TestAdapter
125
123
 
126
- ADAPTER = "Adapter".freeze
124
+ ADAPTER = "Adapter"
127
125
  private_constant :ADAPTER
128
126
 
129
127
  class << self
@@ -34,7 +34,7 @@ module ActiveJob
34
34
  end
35
35
 
36
36
  included do
37
- class_attribute :queue_name, instance_accessor: false, default: default_queue_name
37
+ class_attribute :queue_name, instance_accessor: false, default: -> { self.class.default_queue_name }
38
38
  class_attribute :queue_name_delimiter, instance_accessor: false, default: "_"
39
39
  end
40
40
 
@@ -7,17 +7,32 @@ module ActiveJob
7
7
  # = Active Job Railtie
8
8
  class Railtie < Rails::Railtie # :nodoc:
9
9
  config.active_job = ActiveSupport::OrderedOptions.new
10
+ config.active_job.custom_serializers = []
10
11
 
11
12
  initializer "active_job.logger" do
12
13
  ActiveSupport.on_load(:active_job) { self.logger = ::Rails.logger }
13
14
  end
14
15
 
16
+ initializer "active_job.custom_serializers" do |app|
17
+ config.after_initialize do
18
+ custom_serializers = app.config.active_job.delete(:custom_serializers)
19
+ ActiveJob::Serializers.add_serializers custom_serializers
20
+ end
21
+ end
22
+
15
23
  initializer "active_job.set_configs" do |app|
16
24
  options = app.config.active_job
17
25
  options.queue_adapter ||= :async
18
26
 
19
27
  ActiveSupport.on_load(:active_job) do
20
- options.each { |k, v| send("#{k}=", v) }
28
+ options.each do |k, v|
29
+ k = "#{k}="
30
+ send(k, v) if respond_to? k
31
+ end
32
+ end
33
+
34
+ ActiveSupport.on_load(:action_dispatch_integration_test) do
35
+ include ActiveJob::TestHelper
21
36
  end
22
37
  end
23
38
 
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveJob
4
+ module Serializers
5
+ class DateSerializer < ObjectSerializer # :nodoc:
6
+ def serialize(date)
7
+ super("value" => date.iso8601)
8
+ end
9
+
10
+ def deserialize(hash)
11
+ Date.iso8601(hash["value"])
12
+ end
13
+
14
+ private
15
+
16
+ def klass
17
+ Date
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveJob
4
+ module Serializers
5
+ class DateTimeSerializer < ObjectSerializer # :nodoc:
6
+ def serialize(time)
7
+ super("value" => time.iso8601)
8
+ end
9
+
10
+ def deserialize(hash)
11
+ DateTime.iso8601(hash["value"])
12
+ end
13
+
14
+ private
15
+
16
+ def klass
17
+ DateTime
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveJob
4
+ module Serializers
5
+ class DurationSerializer < ObjectSerializer # :nodoc:
6
+ def serialize(duration)
7
+ super("value" => duration.value, "parts" => Arguments.serialize(duration.parts))
8
+ end
9
+
10
+ def deserialize(hash)
11
+ value = hash["value"]
12
+ parts = Arguments.deserialize(hash["parts"])
13
+
14
+ klass.new(value, parts)
15
+ end
16
+
17
+ private
18
+
19
+ def klass
20
+ ActiveSupport::Duration
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveJob
4
+ module Serializers
5
+ # Base class for serializing and deserializing custom objects.
6
+ #
7
+ # Example:
8
+ #
9
+ # class MoneySerializer < ActiveJob::Serializers::ObjectSerializer
10
+ # def serialize(money)
11
+ # super("amount" => money.amount, "currency" => money.currency)
12
+ # end
13
+ #
14
+ # def deserialize(hash)
15
+ # Money.new(hash["amount"], hash["currency"])
16
+ # end
17
+ #
18
+ # private
19
+ #
20
+ # def klass
21
+ # Money
22
+ # end
23
+ # end
24
+ class ObjectSerializer
25
+ include Singleton
26
+
27
+ class << self
28
+ delegate :serialize?, :serialize, :deserialize, to: :instance
29
+ end
30
+
31
+ # Determines if an argument should be serialized by a serializer.
32
+ def serialize?(argument)
33
+ argument.is_a?(klass)
34
+ end
35
+
36
+ # Serializes an argument to a JSON primitive type.
37
+ def serialize(hash)
38
+ { Arguments::OBJECT_SERIALIZER_KEY => self.class.name }.merge!(hash)
39
+ end
40
+
41
+ # Deserializes an argument from a JSON primitive type.
42
+ def deserialize(_argument)
43
+ raise NotImplementedError
44
+ end
45
+
46
+ private
47
+
48
+ # The class of the object that will be serialized.
49
+ def klass # :doc:
50
+ raise NotImplementedError
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveJob
4
+ module Serializers
5
+ class SymbolSerializer < ObjectSerializer # :nodoc:
6
+ def serialize(argument)
7
+ super("value" => argument.to_s)
8
+ end
9
+
10
+ def deserialize(argument)
11
+ argument["value"].to_sym
12
+ end
13
+
14
+ private
15
+
16
+ def klass
17
+ Symbol
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveJob
4
+ module Serializers
5
+ class TimeSerializer < ObjectSerializer # :nodoc:
6
+ def serialize(time)
7
+ super("value" => time.iso8601)
8
+ end
9
+
10
+ def deserialize(hash)
11
+ Time.iso8601(hash["value"])
12
+ end
13
+
14
+ private
15
+
16
+ def klass
17
+ Time
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveJob
4
+ module Serializers
5
+ class TimeWithZoneSerializer < ObjectSerializer # :nodoc:
6
+ def serialize(time)
7
+ super("value" => time.iso8601)
8
+ end
9
+
10
+ def deserialize(hash)
11
+ Time.iso8601(hash["value"]).in_time_zone
12
+ end
13
+
14
+ private
15
+
16
+ def klass
17
+ ActiveSupport::TimeWithZone
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+
5
+ module ActiveJob
6
+ # The <tt>ActiveJob::Serializers</tt> module is used to store a list of known serializers
7
+ # and to add new ones. It also has helpers to serialize/deserialize objects.
8
+ module Serializers # :nodoc:
9
+ extend ActiveSupport::Autoload
10
+
11
+ autoload :ObjectSerializer
12
+ autoload :SymbolSerializer
13
+ autoload :DurationSerializer
14
+ autoload :DateTimeSerializer
15
+ autoload :DateSerializer
16
+ autoload :TimeWithZoneSerializer
17
+ autoload :TimeSerializer
18
+
19
+ mattr_accessor :_additional_serializers
20
+ self._additional_serializers = Set.new
21
+
22
+ class << self
23
+ # Returns serialized representative of the passed object.
24
+ # Will look up through all known serializers.
25
+ # Raises <tt>ActiveJob::SerializationError</tt> if it can't find a proper serializer.
26
+ def serialize(argument)
27
+ serializer = serializers.detect { |s| s.serialize?(argument) }
28
+ raise SerializationError.new("Unsupported argument type: #{argument.class.name}") unless serializer
29
+ serializer.serialize(argument)
30
+ end
31
+
32
+ # Returns deserialized object.
33
+ # Will look up through all known serializers.
34
+ # If no serializer found will raise <tt>ArgumentError</tt>.
35
+ def deserialize(argument)
36
+ serializer_name = argument[Arguments::OBJECT_SERIALIZER_KEY]
37
+ raise ArgumentError, "Serializer name is not present in the argument: #{argument.inspect}" unless serializer_name
38
+
39
+ serializer = serializer_name.safe_constantize
40
+ raise ArgumentError, "Serializer #{serializer_name} is not known" unless serializer
41
+
42
+ serializer.deserialize(argument)
43
+ end
44
+
45
+ # Returns list of known serializers.
46
+ def serializers
47
+ self._additional_serializers
48
+ end
49
+
50
+ # Adds new serializers to a list of known serializers.
51
+ def add_serializers(*new_serializers)
52
+ self._additional_serializers += new_serializers.flatten
53
+ end
54
+ end
55
+
56
+ add_serializers SymbolSerializer,
57
+ DurationSerializer,
58
+ DateTimeSerializer,
59
+ DateSerializer,
60
+ TimeWithZoneSerializer,
61
+ TimeSerializer
62
+ end
63
+ end