activejob 5.2.4.1 → 6.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +104 -35
- data/MIT-LICENSE +1 -1
- data/README.md +17 -10
- data/lib/active_job.rb +2 -1
- data/lib/active_job/arguments.rb +40 -28
- data/lib/active_job/base.rb +3 -1
- data/lib/active_job/callbacks.rb +4 -1
- data/lib/active_job/core.rb +38 -21
- data/lib/active_job/enqueuing.rb +26 -5
- data/lib/active_job/exceptions.rb +40 -17
- data/lib/active_job/execution.rb +1 -1
- data/lib/active_job/gem_version.rb +4 -4
- data/lib/active_job/logging.rb +40 -9
- data/lib/active_job/queue_adapter.rb +2 -0
- data/lib/active_job/queue_adapters.rb +8 -10
- data/lib/active_job/queue_adapters/async_adapter.rb +1 -1
- data/lib/active_job/queue_adapters/backburner_adapter.rb +2 -2
- data/lib/active_job/queue_adapters/inline_adapter.rb +1 -1
- data/lib/active_job/queue_adapters/test_adapter.rb +22 -8
- data/lib/active_job/queue_name.rb +21 -1
- data/lib/active_job/railtie.rb +16 -1
- data/lib/active_job/serializers.rb +63 -0
- data/lib/active_job/serializers/date_serializer.rb +21 -0
- data/lib/active_job/serializers/date_time_serializer.rb +21 -0
- data/lib/active_job/serializers/duration_serializer.rb +24 -0
- data/lib/active_job/serializers/object_serializer.rb +54 -0
- data/lib/active_job/serializers/symbol_serializer.rb +21 -0
- data/lib/active_job/serializers/time_serializer.rb +21 -0
- data/lib/active_job/serializers/time_with_zone_serializer.rb +21 -0
- data/lib/active_job/test_helper.rb +278 -57
- data/lib/active_job/timezones.rb +13 -0
- data/lib/active_job/translation.rb +1 -1
- data/lib/rails/generators/job/job_generator.rb +4 -0
- metadata +21 -10
- data/lib/active_job/queue_adapters/qu_adapter.rb +0 -46
@@ -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
|
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
|
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
|
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
|
-
|
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
|
-
|
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
|
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
|
-
!
|
68
|
+
!filter_as_proc(filter).call(job)
|
59
69
|
elsif reject
|
60
|
-
|
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
|
@@ -18,6 +18,26 @@ module ActiveJob
|
|
18
18
|
# post.to_feed!
|
19
19
|
# end
|
20
20
|
# end
|
21
|
+
#
|
22
|
+
# Can be given a block that will evaluate in the context of the job
|
23
|
+
# allowing +self.arguments+ to be accessed so that a dynamic queue name
|
24
|
+
# can be applied:
|
25
|
+
#
|
26
|
+
# class PublishToFeedJob < ApplicationJob
|
27
|
+
# queue_as do
|
28
|
+
# post = self.arguments.first
|
29
|
+
#
|
30
|
+
# if post.paid?
|
31
|
+
# :paid_feeds
|
32
|
+
# else
|
33
|
+
# :feeds
|
34
|
+
# end
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# def perform(post)
|
38
|
+
# post.to_feed!
|
39
|
+
# end
|
40
|
+
# end
|
21
41
|
def queue_as(part_name = nil, &block)
|
22
42
|
if block_given?
|
23
43
|
self.queue_name = block
|
@@ -34,7 +54,7 @@ module ActiveJob
|
|
34
54
|
end
|
35
55
|
|
36
56
|
included do
|
37
|
-
class_attribute :queue_name, instance_accessor: false, default: default_queue_name
|
57
|
+
class_attribute :queue_name, instance_accessor: false, default: -> { self.class.default_queue_name }
|
38
58
|
class_attribute :queue_name_delimiter, instance_accessor: false, default: "_"
|
39
59
|
end
|
40
60
|
|
data/lib/active_job/railtie.rb
CHANGED
@@ -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
|
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,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
|
@@ -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
|
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "active_support/core_ext/class/subclasses"
|
4
|
-
require "active_support/core_ext/hash/keys"
|
5
4
|
|
6
5
|
module ActiveJob
|
7
6
|
# Provides helper methods for testing Active Job
|
@@ -52,7 +51,7 @@ module ActiveJob
|
|
52
51
|
queue_adapter_changed_jobs.each { |klass| klass.disable_test_adapter }
|
53
52
|
end
|
54
53
|
|
55
|
-
# Specifies the queue adapter to use with all
|
54
|
+
# Specifies the queue adapter to use with all Active Job test helpers.
|
56
55
|
#
|
57
56
|
# Returns an instance of the queue adapter and defaults to
|
58
57
|
# <tt>ActiveJob::QueueAdapters::TestAdapter</tt>.
|
@@ -75,7 +74,7 @@ module ActiveJob
|
|
75
74
|
# assert_enqueued_jobs 2
|
76
75
|
# end
|
77
76
|
#
|
78
|
-
# If a block is passed, that block will cause the specified number of
|
77
|
+
# If a block is passed, asserts that the block will cause the specified number of
|
79
78
|
# jobs to be enqueued.
|
80
79
|
#
|
81
80
|
# def test_jobs_again
|
@@ -89,7 +88,7 @@ module ActiveJob
|
|
89
88
|
# end
|
90
89
|
# end
|
91
90
|
#
|
92
|
-
#
|
91
|
+
# Asserts the number of times a specific job was enqueued by passing +:only+ option.
|
93
92
|
#
|
94
93
|
# def test_logging_job
|
95
94
|
# assert_enqueued_jobs 1, only: LoggingJob do
|
@@ -98,7 +97,7 @@ module ActiveJob
|
|
98
97
|
# end
|
99
98
|
# end
|
100
99
|
#
|
101
|
-
#
|
100
|
+
# Asserts the number of times a job except specific class was enqueued by passing +:except+ option.
|
102
101
|
#
|
103
102
|
# def test_logging_job
|
104
103
|
# assert_enqueued_jobs 1, except: HelloJob do
|
@@ -107,7 +106,10 @@ module ActiveJob
|
|
107
106
|
# end
|
108
107
|
# end
|
109
108
|
#
|
110
|
-
#
|
109
|
+
# +:only+ and +:except+ options accepts Class, Array of Class or Proc. When passed a Proc,
|
110
|
+
# a hash containing the job's class and it's argument are passed as argument.
|
111
|
+
#
|
112
|
+
# Asserts the number of times a job is enqueued to a specific queue by passing +:queue+ option.
|
111
113
|
#
|
112
114
|
# def test_logging_job
|
113
115
|
# assert_enqueued_jobs 2, queue: 'default' do
|
@@ -117,14 +119,18 @@ module ActiveJob
|
|
117
119
|
# end
|
118
120
|
def assert_enqueued_jobs(number, only: nil, except: nil, queue: nil)
|
119
121
|
if block_given?
|
120
|
-
original_count =
|
122
|
+
original_count = enqueued_jobs_with(only: only, except: except, queue: queue)
|
123
|
+
|
121
124
|
yield
|
122
|
-
|
123
|
-
|
125
|
+
|
126
|
+
new_count = enqueued_jobs_with(only: only, except: except, queue: queue)
|
127
|
+
|
128
|
+
actual_count = new_count - original_count
|
124
129
|
else
|
125
|
-
actual_count =
|
126
|
-
assert_equal number, actual_count, "#{number} jobs expected, but #{actual_count} were enqueued"
|
130
|
+
actual_count = enqueued_jobs_with(only: only, except: except, queue: queue)
|
127
131
|
end
|
132
|
+
|
133
|
+
assert_equal number, actual_count, "#{number} jobs expected, but #{actual_count} were enqueued"
|
128
134
|
end
|
129
135
|
|
130
136
|
# Asserts that no jobs have been enqueued.
|
@@ -135,7 +141,7 @@ module ActiveJob
|
|
135
141
|
# assert_enqueued_jobs 1
|
136
142
|
# end
|
137
143
|
#
|
138
|
-
# If a block is passed, that block
|
144
|
+
# If a block is passed, asserts that the block will not cause any job to be enqueued.
|
139
145
|
#
|
140
146
|
# def test_jobs_again
|
141
147
|
# assert_no_enqueued_jobs do
|
@@ -143,7 +149,7 @@ module ActiveJob
|
|
143
149
|
# end
|
144
150
|
# end
|
145
151
|
#
|
146
|
-
#
|
152
|
+
# Asserts that no jobs of a specific kind are enqueued by passing +:only+ option.
|
147
153
|
#
|
148
154
|
# def test_no_logging
|
149
155
|
# assert_no_enqueued_jobs only: LoggingJob do
|
@@ -151,7 +157,7 @@ module ActiveJob
|
|
151
157
|
# end
|
152
158
|
# end
|
153
159
|
#
|
154
|
-
#
|
160
|
+
# Asserts that no jobs except specific class are enqueued by passing +:except+ option.
|
155
161
|
#
|
156
162
|
# def test_no_logging
|
157
163
|
# assert_no_enqueued_jobs except: HelloJob do
|
@@ -159,16 +165,27 @@ module ActiveJob
|
|
159
165
|
# end
|
160
166
|
# end
|
161
167
|
#
|
168
|
+
# +:only+ and +:except+ options accepts Class, Array of Class or Proc. When passed a Proc,
|
169
|
+
# a hash containing the job's class and it's argument are passed as argument.
|
170
|
+
#
|
171
|
+
# Asserts that no jobs are enqueued to a specific queue by passing +:queue+ option
|
172
|
+
#
|
173
|
+
# def test_no_logging
|
174
|
+
# assert_no_enqueued_jobs queue: 'default' do
|
175
|
+
# LoggingJob.set(queue: :some_queue).perform_later
|
176
|
+
# end
|
177
|
+
# end
|
178
|
+
#
|
162
179
|
# Note: This assertion is simply a shortcut for:
|
163
180
|
#
|
164
181
|
# assert_enqueued_jobs 0, &block
|
165
|
-
def assert_no_enqueued_jobs(only: nil, except: nil, &block)
|
166
|
-
assert_enqueued_jobs 0, only: only, except: except, &block
|
182
|
+
def assert_no_enqueued_jobs(only: nil, except: nil, queue: nil, &block)
|
183
|
+
assert_enqueued_jobs 0, only: only, except: except, queue: queue, &block
|
167
184
|
end
|
168
185
|
|
169
186
|
# Asserts that the number of performed jobs matches the given number.
|
170
187
|
# If no block is passed, <tt>perform_enqueued_jobs</tt>
|
171
|
-
# must be called around the job call.
|
188
|
+
# must be called around or after the job call.
|
172
189
|
#
|
173
190
|
# def test_jobs
|
174
191
|
# assert_performed_jobs 0
|
@@ -178,13 +195,14 @@ module ActiveJob
|
|
178
195
|
# end
|
179
196
|
# assert_performed_jobs 1
|
180
197
|
#
|
181
|
-
#
|
182
|
-
#
|
183
|
-
#
|
184
|
-
#
|
198
|
+
# HelloJob.perform_later('yves')
|
199
|
+
#
|
200
|
+
# perform_enqueued_jobs
|
201
|
+
#
|
202
|
+
# assert_performed_jobs 2
|
185
203
|
# end
|
186
204
|
#
|
187
|
-
# If a block is passed, that block
|
205
|
+
# If a block is passed, asserts that the block will cause the specified number of
|
188
206
|
# jobs to be performed.
|
189
207
|
#
|
190
208
|
# def test_jobs_again
|
@@ -198,7 +216,7 @@ module ActiveJob
|
|
198
216
|
# end
|
199
217
|
# end
|
200
218
|
#
|
201
|
-
#
|
219
|
+
# This method also supports filtering. If the +:only+ option is specified,
|
202
220
|
# then only the listed job(s) will be performed.
|
203
221
|
#
|
204
222
|
# def test_hello_job
|
@@ -208,7 +226,7 @@ module ActiveJob
|
|
208
226
|
# end
|
209
227
|
# end
|
210
228
|
#
|
211
|
-
# Also if the
|
229
|
+
# Also if the +:except+ option is specified,
|
212
230
|
# then the job(s) except specific class will be performed.
|
213
231
|
#
|
214
232
|
# def test_hello_job
|
@@ -229,17 +247,42 @@ module ActiveJob
|
|
229
247
|
# end
|
230
248
|
# end
|
231
249
|
# end
|
232
|
-
|
250
|
+
#
|
251
|
+
# A proc may also be specified. When passed a Proc, the job's instance will be passed as argument.
|
252
|
+
#
|
253
|
+
# def test_hello_and_logging_jobs
|
254
|
+
# assert_nothing_raised do
|
255
|
+
# assert_performed_jobs(1, only: ->(job) { job.is_a?(HelloJob) }) do
|
256
|
+
# HelloJob.perform_later('jeremy')
|
257
|
+
# LoggingJob.perform_later('stewie')
|
258
|
+
# RescueJob.perform_later('david')
|
259
|
+
# end
|
260
|
+
# end
|
261
|
+
# end
|
262
|
+
#
|
263
|
+
# If the +:queue+ option is specified,
|
264
|
+
# then only the job(s) enqueued to a specific queue will be performed.
|
265
|
+
#
|
266
|
+
# def test_assert_performed_jobs_with_queue_option
|
267
|
+
# assert_performed_jobs 1, queue: :some_queue do
|
268
|
+
# HelloJob.set(queue: :some_queue).perform_later("jeremy")
|
269
|
+
# HelloJob.set(queue: :other_queue).perform_later("bogdan")
|
270
|
+
# end
|
271
|
+
# end
|
272
|
+
def assert_performed_jobs(number, only: nil, except: nil, queue: nil, &block)
|
233
273
|
if block_given?
|
234
274
|
original_count = performed_jobs.size
|
235
|
-
|
275
|
+
|
276
|
+
perform_enqueued_jobs(only: only, except: except, queue: queue, &block)
|
277
|
+
|
236
278
|
new_count = performed_jobs.size
|
237
|
-
|
238
|
-
|
279
|
+
|
280
|
+
performed_jobs_size = new_count - original_count
|
239
281
|
else
|
240
|
-
performed_jobs_size =
|
241
|
-
assert_equal number, performed_jobs_size, "#{number} jobs expected, but #{performed_jobs_size} were performed"
|
282
|
+
performed_jobs_size = performed_jobs_with(only: only, except: except, queue: queue)
|
242
283
|
end
|
284
|
+
|
285
|
+
assert_equal number, performed_jobs_size, "#{number} jobs expected, but #{performed_jobs_size} were performed"
|
243
286
|
end
|
244
287
|
|
245
288
|
# Asserts that no jobs have been performed.
|
@@ -253,7 +296,7 @@ module ActiveJob
|
|
253
296
|
# end
|
254
297
|
# end
|
255
298
|
#
|
256
|
-
# If a block is passed, that block
|
299
|
+
# If a block is passed, asserts that the block will not cause any job to be performed.
|
257
300
|
#
|
258
301
|
# def test_jobs_again
|
259
302
|
# assert_no_performed_jobs do
|
@@ -261,7 +304,7 @@ module ActiveJob
|
|
261
304
|
# end
|
262
305
|
# end
|
263
306
|
#
|
264
|
-
# The block form supports filtering. If the
|
307
|
+
# The block form supports filtering. If the +:only+ option is specified,
|
265
308
|
# then only the listed job(s) will not be performed.
|
266
309
|
#
|
267
310
|
# def test_no_logging
|
@@ -270,7 +313,7 @@ module ActiveJob
|
|
270
313
|
# end
|
271
314
|
# end
|
272
315
|
#
|
273
|
-
# Also if the
|
316
|
+
# Also if the +:except+ option is specified,
|
274
317
|
# then the job(s) except specific class will not be performed.
|
275
318
|
#
|
276
319
|
# def test_no_logging
|
@@ -279,14 +322,53 @@ module ActiveJob
|
|
279
322
|
# end
|
280
323
|
# end
|
281
324
|
#
|
325
|
+
# +:only+ and +:except+ options accepts Class, Array of Class or Proc. When passed a Proc,
|
326
|
+
# an instance of the job will be passed as argument.
|
327
|
+
#
|
328
|
+
# If the +:queue+ option is specified,
|
329
|
+
# then only the job(s) enqueued to a specific queue will not be performed.
|
330
|
+
#
|
331
|
+
# def test_assert_no_performed_jobs_with_queue_option
|
332
|
+
# assert_no_performed_jobs queue: :some_queue do
|
333
|
+
# HelloJob.set(queue: :other_queue).perform_later("jeremy")
|
334
|
+
# end
|
335
|
+
# end
|
336
|
+
#
|
282
337
|
# Note: This assertion is simply a shortcut for:
|
283
338
|
#
|
284
339
|
# assert_performed_jobs 0, &block
|
285
|
-
def assert_no_performed_jobs(only: nil, except: nil, &block)
|
286
|
-
assert_performed_jobs 0, only: only, except: except, &block
|
340
|
+
def assert_no_performed_jobs(only: nil, except: nil, queue: nil, &block)
|
341
|
+
assert_performed_jobs 0, only: only, except: except, queue: queue, &block
|
287
342
|
end
|
288
343
|
|
289
|
-
# Asserts that the job
|
344
|
+
# Asserts that the job has been enqueued with the given arguments.
|
345
|
+
#
|
346
|
+
# def test_assert_enqueued_with
|
347
|
+
# MyJob.perform_later(1,2,3)
|
348
|
+
# assert_enqueued_with(job: MyJob, args: [1,2,3], queue: 'low')
|
349
|
+
#
|
350
|
+
# MyJob.set(wait_until: Date.tomorrow.noon).perform_later
|
351
|
+
# assert_enqueued_with(job: MyJob, at: Date.tomorrow.noon)
|
352
|
+
# end
|
353
|
+
#
|
354
|
+
#
|
355
|
+
# The +args+ argument also accepts a proc which will get passed the actual
|
356
|
+
# job's arguments. Your proc needs to return a boolean value determining if
|
357
|
+
# the job's arguments matches your expectation. This is useful to check only
|
358
|
+
# for a subset of arguments.
|
359
|
+
#
|
360
|
+
# def test_assert_enqueued_with
|
361
|
+
# expected_args = ->(job_args) do
|
362
|
+
# assert job_args.first.key?(:foo)
|
363
|
+
# end
|
364
|
+
#
|
365
|
+
# MyJob.perform_later(foo: 'bar', other_arg: 'No need to check in the test')
|
366
|
+
# assert_enqueued_with(job: MyJob, args: expected_args, queue: 'low')
|
367
|
+
# end
|
368
|
+
#
|
369
|
+
#
|
370
|
+
# If a block is passed, asserts that the block will cause the job to be
|
371
|
+
# enqueued with the given arguments.
|
290
372
|
#
|
291
373
|
# def test_assert_enqueued_with
|
292
374
|
# assert_enqueued_with(job: MyJob, args: [1,2,3], queue: 'low') do
|
@@ -298,20 +380,70 @@ module ActiveJob
|
|
298
380
|
# end
|
299
381
|
# end
|
300
382
|
def assert_enqueued_with(job: nil, args: nil, at: nil, queue: nil)
|
301
|
-
original_enqueued_jobs_count = enqueued_jobs.count
|
302
383
|
expected = { job: job, args: args, at: at, queue: queue }.compact
|
303
384
|
expected_args = prepare_args_for_assertion(expected)
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
385
|
+
|
386
|
+
if block_given?
|
387
|
+
original_enqueued_jobs_count = enqueued_jobs.count
|
388
|
+
|
389
|
+
yield
|
390
|
+
|
391
|
+
jobs = enqueued_jobs.drop(original_enqueued_jobs_count)
|
392
|
+
else
|
393
|
+
jobs = enqueued_jobs
|
309
394
|
end
|
395
|
+
|
396
|
+
matching_job = jobs.find do |enqueued_job|
|
397
|
+
deserialized_job = deserialize_args_for_assertion(enqueued_job)
|
398
|
+
|
399
|
+
expected_args.all? do |key, value|
|
400
|
+
if value.respond_to?(:call)
|
401
|
+
value.call(deserialized_job[key])
|
402
|
+
else
|
403
|
+
value == deserialized_job[key]
|
404
|
+
end
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
310
408
|
assert matching_job, "No enqueued job found with #{expected}"
|
311
409
|
instantiate_job(matching_job)
|
312
410
|
end
|
313
411
|
|
314
|
-
# Asserts that the job
|
412
|
+
# Asserts that the job has been performed with the given arguments.
|
413
|
+
#
|
414
|
+
# def test_assert_performed_with
|
415
|
+
# MyJob.perform_later(1,2,3)
|
416
|
+
#
|
417
|
+
# perform_enqueued_jobs
|
418
|
+
#
|
419
|
+
# assert_performed_with(job: MyJob, args: [1,2,3], queue: 'high')
|
420
|
+
#
|
421
|
+
# MyJob.set(wait_until: Date.tomorrow.noon).perform_later
|
422
|
+
#
|
423
|
+
# perform_enqueued_jobs
|
424
|
+
#
|
425
|
+
# assert_performed_with(job: MyJob, at: Date.tomorrow.noon)
|
426
|
+
# end
|
427
|
+
#
|
428
|
+
# The +args+ argument also accepts a proc which will get passed the actual
|
429
|
+
# job's arguments. Your proc needs to return a boolean value determining if
|
430
|
+
# the job's arguments matches your expectation. This is useful to check only
|
431
|
+
# for a subset of arguments.
|
432
|
+
#
|
433
|
+
# def test_assert_performed_with
|
434
|
+
# expected_args = ->(job_args) do
|
435
|
+
# assert job_args.first.key?(:foo)
|
436
|
+
# end
|
437
|
+
# MyJob.perform_later(foo: 'bar', other_arg: 'No need to check in the test')
|
438
|
+
#
|
439
|
+
# perform_enqueued_jobs
|
440
|
+
#
|
441
|
+
# assert_performed_with(job: MyJob, args: expected_args, queue: 'high')
|
442
|
+
# end
|
443
|
+
#
|
444
|
+
# If a block is passed, that block performs all of the jobs that were
|
445
|
+
# enqueued throughout the duration of the block and asserts that
|
446
|
+
# the job has been performed with the given arguments in the block.
|
315
447
|
#
|
316
448
|
# def test_assert_performed_with
|
317
449
|
# assert_performed_with(job: MyJob, args: [1,2,3], queue: 'high') do
|
@@ -322,21 +454,39 @@ module ActiveJob
|
|
322
454
|
# MyJob.set(wait_until: Date.tomorrow.noon).perform_later
|
323
455
|
# end
|
324
456
|
# end
|
325
|
-
def assert_performed_with(job: nil, args: nil, at: nil, queue: nil)
|
326
|
-
original_performed_jobs_count = performed_jobs.count
|
457
|
+
def assert_performed_with(job: nil, args: nil, at: nil, queue: nil, &block)
|
327
458
|
expected = { job: job, args: args, at: at, queue: queue }.compact
|
328
459
|
expected_args = prepare_args_for_assertion(expected)
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
460
|
+
|
461
|
+
if block_given?
|
462
|
+
original_performed_jobs_count = performed_jobs.count
|
463
|
+
|
464
|
+
perform_enqueued_jobs(&block)
|
465
|
+
|
466
|
+
jobs = performed_jobs.drop(original_performed_jobs_count)
|
467
|
+
else
|
468
|
+
jobs = performed_jobs
|
334
469
|
end
|
470
|
+
|
471
|
+
matching_job = jobs.find do |enqueued_job|
|
472
|
+
deserialized_job = deserialize_args_for_assertion(enqueued_job)
|
473
|
+
|
474
|
+
expected_args.all? do |key, value|
|
475
|
+
if value.respond_to?(:call)
|
476
|
+
value.call(deserialized_job[key])
|
477
|
+
else
|
478
|
+
value == deserialized_job[key]
|
479
|
+
end
|
480
|
+
end
|
481
|
+
end
|
482
|
+
|
335
483
|
assert matching_job, "No performed job found with #{expected}"
|
336
484
|
instantiate_job(matching_job)
|
337
485
|
end
|
338
486
|
|
339
|
-
# Performs all enqueued jobs
|
487
|
+
# Performs all enqueued jobs. If a block is given, performs all of the jobs
|
488
|
+
# that were enqueued throughout the duration of the block. If a block is
|
489
|
+
# not given, performs all of the enqueued jobs up to this point in the test.
|
340
490
|
#
|
341
491
|
# def test_perform_enqueued_jobs
|
342
492
|
# perform_enqueued_jobs do
|
@@ -345,6 +495,14 @@ module ActiveJob
|
|
345
495
|
# assert_performed_jobs 1
|
346
496
|
# end
|
347
497
|
#
|
498
|
+
# def test_perform_enqueued_jobs_without_block
|
499
|
+
# MyJob.perform_later(1, 2, 3)
|
500
|
+
#
|
501
|
+
# perform_enqueued_jobs
|
502
|
+
#
|
503
|
+
# assert_performed_jobs 1
|
504
|
+
# end
|
505
|
+
#
|
348
506
|
# This method also supports filtering. If the +:only+ option is specified,
|
349
507
|
# then only the listed job(s) will be performed.
|
350
508
|
#
|
@@ -367,24 +525,45 @@ module ActiveJob
|
|
367
525
|
# assert_performed_jobs 1
|
368
526
|
# end
|
369
527
|
#
|
370
|
-
|
528
|
+
# +:only+ and +:except+ options accepts Class, Array of Class or Proc. When passed a Proc,
|
529
|
+
# an instance of the job will be passed as argument.
|
530
|
+
#
|
531
|
+
# If the +:queue+ option is specified,
|
532
|
+
# then only the job(s) enqueued to a specific queue will be performed.
|
533
|
+
#
|
534
|
+
# def test_perform_enqueued_jobs_with_queue
|
535
|
+
# perform_enqueued_jobs queue: :some_queue do
|
536
|
+
# MyJob.set(queue: :some_queue).perform_later(1, 2, 3) # will be performed
|
537
|
+
# HelloJob.set(queue: :other_queue).perform_later(1, 2, 3) # will not be performed
|
538
|
+
# end
|
539
|
+
# assert_performed_jobs 1
|
540
|
+
# end
|
541
|
+
#
|
542
|
+
def perform_enqueued_jobs(only: nil, except: nil, queue: nil)
|
543
|
+
return flush_enqueued_jobs(only: only, except: except, queue: queue) unless block_given?
|
544
|
+
|
371
545
|
validate_option(only: only, except: except)
|
546
|
+
|
372
547
|
old_perform_enqueued_jobs = queue_adapter.perform_enqueued_jobs
|
373
548
|
old_perform_enqueued_at_jobs = queue_adapter.perform_enqueued_at_jobs
|
374
549
|
old_filter = queue_adapter.filter
|
375
550
|
old_reject = queue_adapter.reject
|
551
|
+
old_queue = queue_adapter.queue
|
376
552
|
|
377
553
|
begin
|
378
554
|
queue_adapter.perform_enqueued_jobs = true
|
379
555
|
queue_adapter.perform_enqueued_at_jobs = true
|
380
556
|
queue_adapter.filter = only
|
381
557
|
queue_adapter.reject = except
|
558
|
+
queue_adapter.queue = queue
|
559
|
+
|
382
560
|
yield
|
383
561
|
ensure
|
384
562
|
queue_adapter.perform_enqueued_jobs = old_perform_enqueued_jobs
|
385
563
|
queue_adapter.perform_enqueued_at_jobs = old_perform_enqueued_at_jobs
|
386
564
|
queue_adapter.filter = old_filter
|
387
565
|
queue_adapter.reject = old_reject
|
566
|
+
queue_adapter.queue = old_queue
|
388
567
|
end
|
389
568
|
end
|
390
569
|
|
@@ -406,30 +585,72 @@ module ActiveJob
|
|
406
585
|
performed_jobs.clear
|
407
586
|
end
|
408
587
|
|
409
|
-
def
|
588
|
+
def jobs_with(jobs, only: nil, except: nil, queue: nil)
|
410
589
|
validate_option(only: only, except: except)
|
411
|
-
|
590
|
+
|
591
|
+
jobs.count do |job|
|
412
592
|
job_class = job.fetch(:job)
|
593
|
+
|
413
594
|
if only
|
414
|
-
next false unless
|
595
|
+
next false unless filter_as_proc(only).call(job)
|
415
596
|
elsif except
|
416
|
-
next false if
|
597
|
+
next false if filter_as_proc(except).call(job)
|
417
598
|
end
|
599
|
+
|
418
600
|
if queue
|
419
601
|
next false unless queue.to_s == job.fetch(:queue, job_class.queue_name)
|
420
602
|
end
|
603
|
+
|
604
|
+
yield job if block_given?
|
605
|
+
|
421
606
|
true
|
422
607
|
end
|
423
608
|
end
|
424
609
|
|
610
|
+
def filter_as_proc(filter)
|
611
|
+
return filter if filter.is_a?(Proc)
|
612
|
+
|
613
|
+
->(job) { Array(filter).include?(job.fetch(:job)) }
|
614
|
+
end
|
615
|
+
|
616
|
+
def enqueued_jobs_with(only: nil, except: nil, queue: nil, &block)
|
617
|
+
jobs_with(enqueued_jobs, only: only, except: except, queue: queue, &block)
|
618
|
+
end
|
619
|
+
|
620
|
+
def performed_jobs_with(only: nil, except: nil, queue: nil, &block)
|
621
|
+
jobs_with(performed_jobs, only: only, except: except, queue: queue, &block)
|
622
|
+
end
|
623
|
+
|
624
|
+
def flush_enqueued_jobs(only: nil, except: nil, queue: nil)
|
625
|
+
enqueued_jobs_with(only: only, except: except, queue: queue) do |payload|
|
626
|
+
instantiate_job(payload).perform_now
|
627
|
+
queue_adapter.performed_jobs << payload
|
628
|
+
end
|
629
|
+
end
|
630
|
+
|
425
631
|
def prepare_args_for_assertion(args)
|
426
632
|
args.dup.tap do |arguments|
|
427
|
-
arguments[:at] = arguments[:at]
|
633
|
+
arguments[:at] = round_time_arguments(arguments[:at]) if arguments[:at]
|
634
|
+
arguments[:args] = round_time_arguments(arguments[:args]) if arguments[:args]
|
635
|
+
end
|
636
|
+
end
|
637
|
+
|
638
|
+
def round_time_arguments(argument)
|
639
|
+
case argument
|
640
|
+
when Time, ActiveSupport::TimeWithZone, DateTime
|
641
|
+
argument.change(usec: 0)
|
642
|
+
when Hash
|
643
|
+
argument.transform_values { |value| round_time_arguments(value) }
|
644
|
+
when Array
|
645
|
+
argument.map { |element| round_time_arguments(element) }
|
646
|
+
else
|
647
|
+
argument
|
428
648
|
end
|
429
649
|
end
|
430
650
|
|
431
651
|
def deserialize_args_for_assertion(job)
|
432
652
|
job.dup.tap do |new_job|
|
653
|
+
new_job[:at] = round_time_arguments(Time.at(new_job[:at])) if new_job[:at]
|
433
654
|
new_job[:args] = ActiveJob::Arguments.deserialize(new_job[:args]) if new_job[:args]
|
434
655
|
end
|
435
656
|
end
|