activejob 5.2.8.1 → 6.1.6.1

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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +118 -57
  3. data/MIT-LICENSE +1 -2
  4. data/README.md +18 -13
  5. data/lib/active_job/arguments.rb +80 -30
  6. data/lib/active_job/base.rb +6 -1
  7. data/lib/active_job/callbacks.rb +46 -3
  8. data/lib/active_job/configured_job.rb +2 -0
  9. data/lib/active_job/core.rb +40 -21
  10. data/lib/active_job/enqueuing.rb +20 -7
  11. data/lib/active_job/exceptions.rb +60 -28
  12. data/lib/active_job/execution.rb +11 -2
  13. data/lib/active_job/gem_version.rb +3 -3
  14. data/lib/active_job/instrumentation.rb +40 -0
  15. data/lib/active_job/log_subscriber.rb +140 -0
  16. data/lib/active_job/logging.rb +3 -101
  17. data/lib/active_job/queue_adapter.rb +5 -0
  18. data/lib/active_job/queue_adapters/async_adapter.rb +1 -1
  19. data/lib/active_job/queue_adapters/backburner_adapter.rb +2 -2
  20. data/lib/active_job/queue_adapters/inline_adapter.rb +1 -1
  21. data/lib/active_job/queue_adapters/que_adapter.rb +2 -2
  22. data/lib/active_job/queue_adapters/sidekiq_adapter.rb +2 -2
  23. data/lib/active_job/queue_adapters/sucker_punch_adapter.rb +1 -1
  24. data/lib/active_job/queue_adapters/test_adapter.rb +32 -14
  25. data/lib/active_job/queue_adapters.rb +13 -11
  26. data/lib/active_job/queue_name.rb +23 -3
  27. data/lib/active_job/railtie.rb +20 -1
  28. data/lib/active_job/serializers/date_serializer.rb +20 -0
  29. data/lib/active_job/serializers/date_time_serializer.rb +16 -0
  30. data/lib/active_job/serializers/duration_serializer.rb +23 -0
  31. data/lib/active_job/serializers/module_serializer.rb +20 -0
  32. data/lib/active_job/serializers/object_serializer.rb +53 -0
  33. data/lib/active_job/serializers/symbol_serializer.rb +20 -0
  34. data/lib/active_job/serializers/time_object_serializer.rb +13 -0
  35. data/lib/active_job/serializers/time_serializer.rb +16 -0
  36. data/lib/active_job/serializers/time_with_zone_serializer.rb +16 -0
  37. data/lib/active_job/serializers.rb +66 -0
  38. data/lib/active_job/test_helper.rb +317 -68
  39. data/lib/active_job/timezones.rb +13 -0
  40. data/lib/active_job/translation.rb +1 -1
  41. data/lib/active_job.rb +2 -1
  42. data/lib/rails/generators/job/job_generator.rb +4 -0
  43. metadata +25 -9
  44. data/lib/active_job/queue_adapters/qu_adapter.rb +0 -46
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/core_ext/hash/transform_values"
4
- require "active_support/core_ext/string/filters"
5
3
  require "active_support/tagged_logging"
6
4
  require "active_support/logger"
7
5
 
@@ -11,32 +9,10 @@ module ActiveJob
11
9
 
12
10
  included do
13
11
  cattr_accessor :logger, default: ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDOUT))
12
+ class_attribute :log_arguments, instance_accessor: false, default: true
14
13
 
15
- around_enqueue do |_, block, _|
16
- tag_logger do
17
- block.call
18
- end
19
- end
20
-
21
- around_perform do |job, block, _|
22
- tag_logger(job.class.name, job.job_id) do
23
- payload = { adapter: job.class.queue_adapter, job: job }
24
- ActiveSupport::Notifications.instrument("perform_start.active_job", payload.dup)
25
- ActiveSupport::Notifications.instrument("perform.active_job", payload) do
26
- block.call
27
- end
28
- end
29
- end
30
-
31
- after_enqueue do |job|
32
- if job.scheduled_at
33
- ActiveSupport::Notifications.instrument "enqueue_at.active_job",
34
- adapter: job.class.queue_adapter, job: job
35
- else
36
- ActiveSupport::Notifications.instrument "enqueue.active_job",
37
- adapter: job.class.queue_adapter, job: job
38
- end
39
- end
14
+ around_enqueue { |_, block| tag_logger(&block) }
15
+ around_perform { |job, block| tag_logger(job.class.name, job.job_id, &block) }
40
16
  end
41
17
 
42
18
  private
@@ -52,79 +28,5 @@ module ActiveJob
52
28
  def logger_tagged_by_active_job?
53
29
  logger.formatter.current_tags.include?("ActiveJob")
54
30
  end
55
-
56
- class LogSubscriber < ActiveSupport::LogSubscriber #:nodoc:
57
- def enqueue(event)
58
- info do
59
- job = event.payload[:job]
60
- "Enqueued #{job.class.name} (Job ID: #{job.job_id}) to #{queue_name(event)}" + args_info(job)
61
- end
62
- end
63
-
64
- def enqueue_at(event)
65
- info do
66
- job = event.payload[:job]
67
- "Enqueued #{job.class.name} (Job ID: #{job.job_id}) to #{queue_name(event)} at #{scheduled_at(event)}" + args_info(job)
68
- end
69
- end
70
-
71
- def perform_start(event)
72
- info do
73
- job = event.payload[:job]
74
- "Performing #{job.class.name} (Job ID: #{job.job_id}) from #{queue_name(event)}" + args_info(job)
75
- end
76
- end
77
-
78
- def perform(event)
79
- job = event.payload[:job]
80
- ex = event.payload[:exception_object]
81
- if ex
82
- error do
83
- "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")
84
- end
85
- else
86
- info do
87
- "Performed #{job.class.name} (Job ID: #{job.job_id}) from #{queue_name(event)} in #{event.duration.round(2)}ms"
88
- end
89
- end
90
- end
91
-
92
- private
93
- def queue_name(event)
94
- event.payload[:adapter].class.name.demodulize.remove("Adapter") + "(#{event.payload[:job].queue_name})"
95
- end
96
-
97
- def args_info(job)
98
- if job.arguments.any?
99
- " with arguments: " +
100
- job.arguments.map { |arg| format(arg).inspect }.join(", ")
101
- else
102
- ""
103
- end
104
- end
105
-
106
- def format(arg)
107
- case arg
108
- when Hash
109
- arg.transform_values { |value| format(value) }
110
- when Array
111
- arg.map { |value| format(value) }
112
- when GlobalID::Identification
113
- arg.to_global_id rescue arg
114
- else
115
- arg
116
- end
117
- end
118
-
119
- def scheduled_at(event)
120
- Time.at(event.payload[:job].scheduled_at).utc
121
- end
122
-
123
- def logger
124
- ActiveJob::Base.logger
125
- end
126
- end
127
31
  end
128
32
  end
129
-
130
- 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
 
@@ -22,6 +25,8 @@ module ActiveJob
22
25
  _queue_adapter
23
26
  end
24
27
 
28
+ # Returns string denoting the name of the configured queue adapter.
29
+ # By default returns <tt>"async"</tt>.
25
30
  def queue_adapter_name
26
31
  _queue_adapter_name
27
32
  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
@@ -18,13 +18,13 @@ module ActiveJob
18
18
  # Rails.application.config.active_job.queue_adapter = :que
19
19
  class QueAdapter
20
20
  def enqueue(job) #:nodoc:
21
- que_job = JobWrapper.enqueue job.serialize, priority: job.priority
21
+ que_job = JobWrapper.enqueue job.serialize, priority: job.priority, queue: job.queue_name
22
22
  job.provider_job_id = que_job.attrs["job_id"]
23
23
  que_job
24
24
  end
25
25
 
26
26
  def enqueue_at(job, timestamp) #:nodoc:
27
- que_job = JobWrapper.enqueue job.serialize, priority: job.priority, run_at: Time.at(timestamp)
27
+ que_job = JobWrapper.enqueue job.serialize, priority: job.priority, queue: job.queue_name, run_at: Time.at(timestamp)
28
28
  job.provider_job_id = que_job.attrs["job_id"]
29
29
  que_job
30
30
  end
@@ -21,7 +21,7 @@ module ActiveJob
21
21
  # Sidekiq::Client does not support symbols as keys
22
22
  job.provider_job_id = Sidekiq::Client.push \
23
23
  "class" => JobWrapper,
24
- "wrapped" => job.class.to_s,
24
+ "wrapped" => job.class,
25
25
  "queue" => job.queue_name,
26
26
  "args" => [ job.serialize ]
27
27
  end
@@ -29,7 +29,7 @@ module ActiveJob
29
29
  def enqueue_at(job, timestamp) #:nodoc:
30
30
  job.provider_job_id = Sidekiq::Client.push \
31
31
  "class" => JobWrapper,
32
- "wrapped" => job.class.to_s,
32
+ "wrapped" => job.class,
33
33
  "queue" => job.queue_name,
34
34
  "args" => [ job.serialize ],
35
35
  "at" => timestamp
@@ -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)
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.
@@ -26,42 +26,60 @@ module ActiveJob
26
26
  end
27
27
 
28
28
  def enqueue(job) #:nodoc:
29
- return if filtered?(job)
30
-
31
29
  job_data = job_to_hash(job)
32
- enqueue_or_perform(perform_enqueued_jobs, job, job_data)
30
+ perform_or_enqueue(perform_enqueued_jobs && !filtered?(job), job, job_data)
33
31
  end
34
32
 
35
33
  def enqueue_at(job, timestamp) #:nodoc:
36
- return if filtered?(job)
37
-
38
34
  job_data = job_to_hash(job, at: timestamp)
39
- enqueue_or_perform(perform_enqueued_at_jobs, job, job_data)
35
+ perform_or_enqueue(perform_enqueued_at_jobs && !filtered?(job), job, job_data)
40
36
  end
41
37
 
42
38
  private
43
39
  def job_to_hash(job, extras = {})
44
- { job: job.class, args: job.serialize.fetch("arguments"), queue: job.queue_name }.merge!(extras)
40
+ job.serialize.tap do |job_data|
41
+ job_data[:job] = job.class
42
+ job_data[:args] = job_data.fetch("arguments")
43
+ job_data[:queue] = job_data.fetch("queue_name")
44
+ end.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
- Base.execute job.serialize
50
+ Base.execute(job.serialize)
51
51
  else
52
52
  enqueued_jobs << job_data
53
53
  end
54
54
  end
55
55
 
56
56
  def filtered?(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
62
+ end
63
+
64
+ def filtered_queue?(job)
65
+ if queue
66
+ job.queue_name != queue.to_s
67
+ end
68
+ end
69
+
70
+ def filtered_job_class?(job)
57
71
  if filter
58
- !Array(filter).include?(job.class)
72
+ !filter_as_proc(filter).call(job)
59
73
  elsif reject
60
- Array(reject).include?(job.class)
61
- else
62
- false
74
+ filter_as_proc(reject).call(job)
63
75
  end
64
76
  end
77
+
78
+ def filter_as_proc(filter)
79
+ return filter if filter.is_a?(Proc)
80
+
81
+ ->(job) { Array(filter).include?(job.class) }
82
+ end
65
83
  end
66
84
  end
67
85
  end
@@ -3,19 +3,19 @@
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]
14
- # * {Sidekiq}[http://sidekiq.org]
13
+ # * {Sidekiq}[https://sidekiq.org]
15
14
  # * {Sneakers}[https://github.com/jondot/sneakers]
16
15
  # * {Sucker Punch}[https://github.com/brandonhilkert/sucker_punch]
17
- # * {Active Job Async Job}[http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters/AsyncAdapter.html]
18
- # * {Active Job Inline}[http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters/InlineAdapter.html]
16
+ # * {Active Job Async Job}[https://api.rubyonrails.org/classes/ActiveJob/QueueAdapters/AsyncAdapter.html]
17
+ # * {Active Job Inline}[https://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.
@@ -73,9 +72,9 @@ module ActiveJob
73
72
  # Yes: Allows the priority to be set on the job object, at the queue level or
74
73
  # as default configuration option.
75
74
  #
76
- # 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.
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
  #
@@ -87,6 +86,8 @@ module ActiveJob
87
86
  #
88
87
  # Global: The adapter is configured that all jobs have a maximum run time.
89
88
  #
89
+ # No: The adapter does not allow the timeout of jobs to be configured.
90
+ #
90
91
  # N/A: This adapter does not run in a separate process, and therefore timeout
91
92
  # is unsupported.
92
93
  #
@@ -100,6 +101,8 @@ module ActiveJob
100
101
  #
101
102
  # Global: The adapter has a global number of retries.
102
103
  #
104
+ # No: The adapter does not allow the number of retries to be configured.
105
+ #
103
106
  # N/A: The adapter does not run in a separate process, and therefore doesn't
104
107
  # support retries.
105
108
  #
@@ -114,7 +117,6 @@ module ActiveJob
114
117
  autoload :InlineAdapter
115
118
  autoload :BackburnerAdapter
116
119
  autoload :DelayedJobAdapter
117
- autoload :QuAdapter
118
120
  autoload :QueAdapter
119
121
  autoload :QueueClassicAdapter
120
122
  autoload :ResqueAdapter
@@ -123,7 +125,7 @@ module ActiveJob
123
125
  autoload :SuckerPunchAdapter
124
126
  autoload :TestAdapter
125
127
 
126
- ADAPTER = "Adapter".freeze
128
+ ADAPTER = "Adapter"
127
129
  private_constant :ADAPTER
128
130
 
129
131
  class << self
@@ -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.
@@ -18,6 +17,26 @@ module ActiveJob
18
17
  # post.to_feed!
19
18
  # end
20
19
  # end
20
+ #
21
+ # Can be given a block that will evaluate in the context of the job
22
+ # allowing +self.arguments+ to be accessed so that a dynamic queue name
23
+ # can be applied:
24
+ #
25
+ # class PublishToFeedJob < ApplicationJob
26
+ # queue_as do
27
+ # post = self.arguments.first
28
+ #
29
+ # if post.paid?
30
+ # :paid_feeds
31
+ # else
32
+ # :feeds
33
+ # end
34
+ # end
35
+ #
36
+ # def perform(post)
37
+ # post.to_feed!
38
+ # end
39
+ # end
21
40
  def queue_as(part_name = nil, &block)
22
41
  if block_given?
23
42
  self.queue_name = block
@@ -29,13 +48,14 @@ module ActiveJob
29
48
  def queue_name_from_part(part_name) #:nodoc:
30
49
  queue_name = part_name || default_queue_name
31
50
  name_parts = [queue_name_prefix.presence, queue_name]
32
- name_parts.compact.join(queue_name_delimiter)
51
+ -name_parts.compact.join(queue_name_delimiter)
33
52
  end
34
53
  end
35
54
 
36
55
  included do
37
- class_attribute :queue_name, instance_accessor: false, default: default_queue_name
56
+ class_attribute :queue_name, instance_accessor: false, default: -> { self.class.default_queue_name }
38
57
  class_attribute :queue_name_delimiter, instance_accessor: false, default: "_"
58
+ class_attribute :queue_name_prefix
39
59
  end
40
60
 
41
61
  # Returns the name of the queue the job will be run on.
@@ -7,17 +7,36 @@ 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
36
+ end
37
+
38
+ ActiveSupport.on_load(:active_record) do
39
+ self.destroy_association_async_job = ActiveRecord::DestroyAssociationAsyncJob
21
40
  end
22
41
  end
23
42
 
@@ -0,0 +1,20 @@
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
+ def klass
16
+ Date
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveJob
4
+ module Serializers
5
+ class DateTimeSerializer < TimeObjectSerializer # :nodoc:
6
+ def deserialize(hash)
7
+ DateTime.iso8601(hash["value"])
8
+ end
9
+
10
+ private
11
+ def klass
12
+ DateTime
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,23 @@
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
+ def klass
19
+ ActiveSupport::Duration
20
+ end
21
+ end
22
+ end
23
+ 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
@@ -0,0 +1,53 @@
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(json)
43
+ raise NotImplementedError
44
+ end
45
+
46
+ private
47
+ # The class of the object that will be serialized.
48
+ def klass # :doc:
49
+ raise NotImplementedError
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,20 @@
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
+ def klass
16
+ Symbol
17
+ end
18
+ end
19
+ end
20
+ end
@@ -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
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveJob
4
+ module Serializers
5
+ class TimeSerializer < TimeObjectSerializer # :nodoc:
6
+ def deserialize(hash)
7
+ Time.iso8601(hash["value"])
8
+ end
9
+
10
+ private
11
+ def klass
12
+ Time
13
+ end
14
+ end
15
+ end
16
+ end