activejob 4.2.11.3 → 5.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activejob might be problematic. Click here for more details.

Files changed (33) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +88 -54
  3. data/MIT-LICENSE +1 -1
  4. data/README.md +5 -5
  5. data/lib/active_job.rb +2 -1
  6. data/lib/active_job/arguments.rb +38 -19
  7. data/lib/active_job/async_job.rb +77 -0
  8. data/lib/active_job/base.rb +3 -1
  9. data/lib/active_job/callbacks.rb +2 -2
  10. data/lib/active_job/core.rb +42 -5
  11. data/lib/active_job/enqueuing.rb +5 -0
  12. data/lib/active_job/gem_version.rb +4 -4
  13. data/lib/active_job/logging.rb +17 -3
  14. data/lib/active_job/queue_adapter.rb +44 -16
  15. data/lib/active_job/queue_adapters.rb +86 -0
  16. data/lib/active_job/queue_adapters/async_adapter.rb +23 -0
  17. data/lib/active_job/queue_adapters/backburner_adapter.rb +6 -8
  18. data/lib/active_job/queue_adapters/delayed_job_adapter.rb +9 -7
  19. data/lib/active_job/queue_adapters/inline_adapter.rb +5 -7
  20. data/lib/active_job/queue_adapters/qu_adapter.rb +11 -9
  21. data/lib/active_job/queue_adapters/que_adapter.rb +9 -7
  22. data/lib/active_job/queue_adapters/queue_classic_adapter.rb +22 -20
  23. data/lib/active_job/queue_adapters/resque_adapter.rb +8 -10
  24. data/lib/active_job/queue_adapters/sidekiq_adapter.rb +15 -17
  25. data/lib/active_job/queue_adapters/sneakers_adapter.rb +11 -11
  26. data/lib/active_job/queue_adapters/sucker_punch_adapter.rb +5 -7
  27. data/lib/active_job/queue_adapters/test_adapter.rb +25 -16
  28. data/lib/active_job/queue_name.rb +1 -1
  29. data/lib/active_job/queue_priority.rb +44 -0
  30. data/lib/active_job/test_helper.rb +144 -46
  31. data/lib/rails/generators/job/job_generator.rb +1 -2
  32. data/lib/rails/generators/job/templates/job.rb +1 -1
  33. metadata +15 -10
@@ -13,15 +13,13 @@ module ActiveJob
13
13
  #
14
14
  # Rails.application.config.active_job.queue_adapter = :backburner
15
15
  class BackburnerAdapter
16
- class << self
17
- def enqueue(job) #:nodoc:
18
- Backburner::Worker.enqueue JobWrapper, [ job.serialize ], queue: job.queue_name
19
- end
16
+ def enqueue(job) #:nodoc:
17
+ Backburner::Worker.enqueue JobWrapper, [ job.serialize ], queue: job.queue_name
18
+ end
20
19
 
21
- def enqueue_at(job, timestamp) #:nodoc:
22
- delay = timestamp - Time.current.to_f
23
- Backburner::Worker.enqueue JobWrapper, [ job.serialize ], queue: job.queue_name, delay: delay
24
- end
20
+ def enqueue_at(job, timestamp) #:nodoc:
21
+ delay = timestamp - Time.current.to_f
22
+ Backburner::Worker.enqueue JobWrapper, [ job.serialize ], queue: job.queue_name, delay: delay
25
23
  end
26
24
 
27
25
  class JobWrapper #:nodoc:
@@ -13,14 +13,16 @@ module ActiveJob
13
13
  #
14
14
  # Rails.application.config.active_job.queue_adapter = :delayed_job
15
15
  class DelayedJobAdapter
16
- class << self
17
- def enqueue(job) #:nodoc:
18
- Delayed::Job.enqueue(JobWrapper.new(job.serialize), queue: job.queue_name)
19
- end
16
+ def enqueue(job) #:nodoc:
17
+ delayed_job = Delayed::Job.enqueue(JobWrapper.new(job.serialize), queue: job.queue_name, priority: job.priority)
18
+ job.provider_job_id = delayed_job.id
19
+ delayed_job
20
+ end
20
21
 
21
- def enqueue_at(job, timestamp) #:nodoc:
22
- Delayed::Job.enqueue(JobWrapper.new(job.serialize), queue: job.queue_name, run_at: Time.at(timestamp))
23
- end
22
+ def enqueue_at(job, timestamp) #:nodoc:
23
+ delayed_job = Delayed::Job.enqueue(JobWrapper.new(job.serialize), queue: job.queue_name, priority: job.priority, run_at: Time.at(timestamp))
24
+ job.provider_job_id = delayed_job.id
25
+ delayed_job
24
26
  end
25
27
 
26
28
  class JobWrapper #:nodoc:
@@ -9,14 +9,12 @@ module ActiveJob
9
9
  #
10
10
  # Rails.application.config.active_job.queue_adapter = :inline
11
11
  class InlineAdapter
12
- class << self
13
- def enqueue(job) #:nodoc:
14
- Base.execute(job.serialize)
15
- end
12
+ def enqueue(job) #:nodoc:
13
+ Base.execute(job.serialize)
14
+ end
16
15
 
17
- def enqueue_at(*) #:nodoc:
18
- raise NotImplementedError.new("Use a queueing backend to enqueue jobs in the future. Read more at http://guides.rubyonrails.org/active_job_basics.html")
19
- end
16
+ def enqueue_at(*) #:nodoc:
17
+ raise NotImplementedError, "Use a queueing backend to enqueue jobs in the future. Read more at http://guides.rubyonrails.org/active_job_basics.html"
20
18
  end
21
19
  end
22
20
  end
@@ -16,16 +16,18 @@ module ActiveJob
16
16
  #
17
17
  # Rails.application.config.active_job.queue_adapter = :qu
18
18
  class QuAdapter
19
- class << self
20
- def enqueue(job, *args) #:nodoc:
21
- Qu::Payload.new(klass: JobWrapper, args: [job.serialize]).tap do |payload|
22
- payload.instance_variable_set(:@queue, job.queue_name)
23
- end.push
24
- end
19
+ def enqueue(job, *args) #:nodoc:
20
+ qu_job = Qu::Payload.new(klass: JobWrapper, args: [job.serialize]).tap do |payload|
21
+ payload.instance_variable_set(:@queue, job.queue_name)
22
+ end.push
23
+
24
+ # qu_job can be nil depending on the configured backend
25
+ job.provider_job_id = qu_job.id unless qu_job.nil?
26
+ qu_job
27
+ end
25
28
 
26
- def enqueue_at(job, timestamp, *args) #:nodoc:
27
- raise NotImplementedError
28
- end
29
+ def enqueue_at(job, timestamp, *args) #:nodoc:
30
+ raise NotImplementedError, "This queueing backend does not support scheduling jobs. To see what features are supported go to http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html"
29
31
  end
30
32
 
31
33
  class JobWrapper < Qu::Job #:nodoc:
@@ -15,14 +15,16 @@ module ActiveJob
15
15
  #
16
16
  # Rails.application.config.active_job.queue_adapter = :que
17
17
  class QueAdapter
18
- class << self
19
- def enqueue(job) #:nodoc:
20
- JobWrapper.enqueue job.serialize, queue: job.queue_name
21
- end
18
+ def enqueue(job) #:nodoc:
19
+ que_job = JobWrapper.enqueue job.serialize, priority: job.priority
20
+ job.provider_job_id = que_job.attrs["job_id"]
21
+ que_job
22
+ end
22
23
 
23
- def enqueue_at(job, timestamp) #:nodoc:
24
- JobWrapper.enqueue job.serialize, queue: job.queue_name, run_at: Time.at(timestamp)
25
- end
24
+ def enqueue_at(job, timestamp) #:nodoc:
25
+ que_job = JobWrapper.enqueue job.serialize, priority: job.priority, run_at: Time.at(timestamp)
26
+ job.provider_job_id = que_job.attrs["job_id"]
27
+ que_job
26
28
  end
27
29
 
28
30
  class JobWrapper < Que::Job #:nodoc:
@@ -17,29 +17,31 @@ module ActiveJob
17
17
  #
18
18
  # Rails.application.config.active_job.queue_adapter = :queue_classic
19
19
  class QueueClassicAdapter
20
- class << self
21
- def enqueue(job) #:nodoc:
22
- build_queue(job.queue_name).enqueue("#{JobWrapper.name}.perform", job.serialize)
23
- end
20
+ def enqueue(job) #:nodoc:
21
+ qc_job = build_queue(job.queue_name).enqueue("#{JobWrapper.name}.perform", job.serialize)
22
+ job.provider_job_id = qc_job["id"] if qc_job.is_a?(Hash)
23
+ qc_job
24
+ end
24
25
 
25
- def enqueue_at(job, timestamp) #:nodoc:
26
- queue = build_queue(job.queue_name)
27
- unless queue.respond_to?(:enqueue_at)
28
- raise NotImplementedError, 'To be able to schedule jobs with queue_classic ' \
29
- 'the QC::Queue needs to respond to `enqueue_at(timestamp, method, *args)`. ' \
30
- 'You can implement this yourself or you can use the queue_classic-later gem.'
31
- end
32
- queue.enqueue_at(timestamp, "#{JobWrapper.name}.perform", job.serialize)
26
+ def enqueue_at(job, timestamp) #:nodoc:
27
+ queue = build_queue(job.queue_name)
28
+ unless queue.respond_to?(:enqueue_at)
29
+ raise NotImplementedError, 'To be able to schedule jobs with queue_classic ' \
30
+ 'the QC::Queue needs to respond to `enqueue_at(timestamp, method, *args)`. ' \
31
+ 'You can implement this yourself or you can use the queue_classic-later gem.'
33
32
  end
33
+ qc_job = queue.enqueue_at(timestamp, "#{JobWrapper.name}.perform", job.serialize)
34
+ job.provider_job_id = qc_job["id"] if qc_job.is_a?(Hash)
35
+ qc_job
36
+ end
34
37
 
35
- # Builds a <tt>QC::Queue</tt> object to schedule jobs on.
36
- #
37
- # If you have a custom <tt>QC::Queue</tt> subclass you'll need to subclass
38
- # <tt>ActiveJob::QueueAdapters::QueueClassicAdapter</tt> and override the
39
- # <tt>build_queue</tt> method.
40
- def build_queue(queue_name)
41
- QC::Queue.new(queue_name)
42
- end
38
+ # Builds a <tt>QC::Queue</tt> object to schedule jobs on.
39
+ #
40
+ # If you have a custom <tt>QC::Queue</tt> subclass you'll need to subclass
41
+ # <tt>ActiveJob::QueueAdapters::QueueClassicAdapter</tt> and override the
42
+ # <tt>build_queue</tt> method.
43
+ def build_queue(queue_name)
44
+ QC::Queue.new(queue_name)
43
45
  end
44
46
 
45
47
  class JobWrapper #:nodoc:
@@ -26,18 +26,16 @@ module ActiveJob
26
26
  #
27
27
  # Rails.application.config.active_job.queue_adapter = :resque
28
28
  class ResqueAdapter
29
- class << self
30
- def enqueue(job) #:nodoc:
31
- Resque.enqueue_to job.queue_name, JobWrapper, job.serialize
32
- end
29
+ def enqueue(job) #:nodoc:
30
+ Resque.enqueue_to job.queue_name, JobWrapper, job.serialize
31
+ end
33
32
 
34
- def enqueue_at(job, timestamp) #:nodoc:
35
- unless Resque.respond_to?(:enqueue_at_with_queue)
36
- raise NotImplementedError, "To be able to schedule jobs with Resque you need the " \
37
- "resque-scheduler gem. Please add it to your Gemfile and run bundle install"
38
- end
39
- Resque.enqueue_at_with_queue job.queue_name, timestamp, JobWrapper, job.serialize
33
+ def enqueue_at(job, timestamp) #:nodoc:
34
+ unless Resque.respond_to?(:enqueue_at_with_queue)
35
+ raise NotImplementedError, "To be able to schedule jobs with Resque you need the " \
36
+ "resque-scheduler gem. Please add it to your Gemfile and run bundle install"
40
37
  end
38
+ Resque.enqueue_at_with_queue job.queue_name, timestamp, JobWrapper, job.serialize
41
39
  end
42
40
 
43
41
  class JobWrapper #:nodoc:
@@ -15,24 +15,22 @@ module ActiveJob
15
15
  #
16
16
  # Rails.application.config.active_job.queue_adapter = :sidekiq
17
17
  class SidekiqAdapter
18
- class << self
19
- def enqueue(job) #:nodoc:
20
- #Sidekiq::Client does not support symbols as keys
21
- Sidekiq::Client.push \
22
- 'class' => JobWrapper,
23
- 'wrapped' => job.class.to_s,
24
- 'queue' => job.queue_name,
25
- 'args' => [ job.serialize ]
26
- end
18
+ def enqueue(job) #:nodoc:
19
+ #Sidekiq::Client does not support symbols as keys
20
+ job.provider_job_id = Sidekiq::Client.push \
21
+ 'class' => JobWrapper,
22
+ 'wrapped' => job.class.to_s,
23
+ 'queue' => job.queue_name,
24
+ 'args' => [ job.serialize ]
25
+ end
27
26
 
28
- def enqueue_at(job, timestamp) #:nodoc:
29
- Sidekiq::Client.push \
30
- 'class' => JobWrapper,
31
- 'wrapped' => job.class.to_s,
32
- 'queue' => job.queue_name,
33
- 'args' => [ job.serialize ],
34
- 'at' => timestamp
35
- end
27
+ def enqueue_at(job, timestamp) #:nodoc:
28
+ job.provider_job_id = Sidekiq::Client.push \
29
+ 'class' => JobWrapper,
30
+ 'wrapped' => job.class.to_s,
31
+ 'queue' => job.queue_name,
32
+ 'args' => [ job.serialize ],
33
+ 'at' => timestamp
36
34
  end
37
35
 
38
36
  class JobWrapper #:nodoc:
@@ -1,5 +1,5 @@
1
1
  require 'sneakers'
2
- require 'thread'
2
+ require 'monitor'
3
3
 
4
4
  module ActiveJob
5
5
  module QueueAdapters
@@ -16,19 +16,19 @@ module ActiveJob
16
16
  #
17
17
  # Rails.application.config.active_job.queue_adapter = :sneakers
18
18
  class SneakersAdapter
19
- @monitor = Monitor.new
19
+ def initialize
20
+ @monitor = Monitor.new
21
+ end
20
22
 
21
- class << self
22
- def enqueue(job) #:nodoc:
23
- @monitor.synchronize do
24
- JobWrapper.from_queue job.queue_name
25
- JobWrapper.enqueue ActiveSupport::JSON.encode(job.serialize)
26
- end
23
+ def enqueue(job) #:nodoc:
24
+ @monitor.synchronize do
25
+ JobWrapper.from_queue job.queue_name
26
+ JobWrapper.enqueue ActiveSupport::JSON.encode(job.serialize)
27
27
  end
28
+ end
28
29
 
29
- def enqueue_at(job, timestamp) #:nodoc:
30
- raise NotImplementedError
31
- end
30
+ def enqueue_at(job, timestamp) #:nodoc:
31
+ raise NotImplementedError, "This queueing backend does not support scheduling jobs. To see what features are supported go to http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html"
32
32
  end
33
33
 
34
34
  class JobWrapper #:nodoc:
@@ -18,14 +18,12 @@ module ActiveJob
18
18
  #
19
19
  # Rails.application.config.active_job.queue_adapter = :sucker_punch
20
20
  class SuckerPunchAdapter
21
- class << self
22
- def enqueue(job) #:nodoc:
23
- JobWrapper.new.async.perform job.serialize
24
- end
21
+ def enqueue(job) #:nodoc:
22
+ JobWrapper.new.async.perform job.serialize
23
+ end
25
24
 
26
- def enqueue_at(job, timestamp) #:nodoc:
27
- raise NotImplementedError
28
- end
25
+ def enqueue_at(job, timestamp) #:nodoc:
26
+ raise NotImplementedError, "This queueing backend does not support scheduling jobs. To see what features are supported go to http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html"
29
27
  end
30
28
 
31
29
  class JobWrapper #:nodoc:
@@ -10,15 +10,9 @@ module ActiveJob
10
10
  #
11
11
  # Rails.application.config.active_job.queue_adapter = :test
12
12
  class TestAdapter
13
- delegate :name, to: :class
14
- attr_accessor(:perform_enqueued_jobs, :perform_enqueued_at_jobs)
13
+ attr_accessor(:perform_enqueued_jobs, :perform_enqueued_at_jobs, :filter)
15
14
  attr_writer(:enqueued_jobs, :performed_jobs)
16
15
 
17
- def initialize
18
- self.perform_enqueued_jobs = false
19
- self.perform_enqueued_at_jobs = false
20
- end
21
-
22
16
  # Provides a store of all the enqueued jobs with the TestAdapter so you can check them.
23
17
  def enqueued_jobs
24
18
  @enqueued_jobs ||= []
@@ -30,22 +24,37 @@ module ActiveJob
30
24
  end
31
25
 
32
26
  def enqueue(job) #:nodoc:
33
- if perform_enqueued_jobs
34
- performed_jobs << {job: job.class, args: job.serialize['arguments'], queue: job.queue_name}
35
- Base.execute job.serialize
36
- else
37
- enqueued_jobs << {job: job.class, args: job.serialize['arguments'], queue: job.queue_name}
38
- end
27
+ return if filtered?(job)
28
+
29
+ job_data = job_to_hash(job)
30
+ enqueue_or_perform(perform_enqueued_jobs, job, job_data)
39
31
  end
40
32
 
41
33
  def enqueue_at(job, timestamp) #:nodoc:
42
- if perform_enqueued_at_jobs
43
- performed_jobs << {job: job.class, args: job.serialize['arguments'], queue: job.queue_name, at: timestamp}
34
+ return if filtered?(job)
35
+
36
+ job_data = job_to_hash(job, at: timestamp)
37
+ enqueue_or_perform(perform_enqueued_at_jobs, job, job_data)
38
+ end
39
+
40
+ private
41
+
42
+ def job_to_hash(job, extras = {})
43
+ { job: job.class, args: job.serialize.fetch('arguments'), queue: job.queue_name }.merge!(extras)
44
+ end
45
+
46
+ def enqueue_or_perform(perform, job, job_data)
47
+ if perform
48
+ performed_jobs << job_data
44
49
  Base.execute job.serialize
45
50
  else
46
- enqueued_jobs << {job: job.class, args: job.serialize['arguments'], queue: job.queue_name, at: timestamp}
51
+ enqueued_jobs << job_data
47
52
  end
48
53
  end
54
+
55
+ def filtered?(job)
56
+ filter && !Array(filter).include?(job.class)
57
+ end
49
58
  end
50
59
  end
51
60
  end
@@ -39,7 +39,7 @@ module ActiveJob
39
39
  self.queue_name_delimiter = '_' # set default delimiter to '_'
40
40
  end
41
41
 
42
- # Returns the name of the queue the job will be run on
42
+ # Returns the name of the queue the job will be run on.
43
43
  def queue_name
44
44
  if @queue_name.is_a?(Proc)
45
45
  @queue_name = self.class.queue_name_from_part(instance_exec(&@queue_name))
@@ -0,0 +1,44 @@
1
+ module ActiveJob
2
+ module QueuePriority
3
+ extend ActiveSupport::Concern
4
+
5
+ # Includes the ability to override the default queue priority.
6
+ module ClassMethods
7
+ mattr_accessor(:default_priority)
8
+
9
+ # Specifies the priority of the queue to create the job with.
10
+ #
11
+ # class PublishToFeedJob < ActiveJob::Base
12
+ # queue_with_priority 50
13
+ #
14
+ # def perform(post)
15
+ # post.to_feed!
16
+ # end
17
+ # end
18
+ #
19
+ # Specify either an argument or a block.
20
+ def queue_with_priority(priority=nil, &block)
21
+ if block_given?
22
+ self.priority = block
23
+ else
24
+ self.priority = priority
25
+ end
26
+ end
27
+ end
28
+
29
+ included do
30
+ class_attribute :priority, instance_accessor: false
31
+
32
+ self.priority = default_priority
33
+ end
34
+
35
+ # Returns the priority that the job will be created with
36
+ def priority
37
+ if @priority.is_a?(Proc)
38
+ @priority = instance_exec(&@priority)
39
+ end
40
+ @priority
41
+ end
42
+
43
+ end
44
+ end
@@ -1,3 +1,4 @@
1
+ require 'active_support/core_ext/class/subclasses'
1
2
  require 'active_support/core_ext/hash/keys'
2
3
 
3
4
  module ActiveJob
@@ -6,17 +7,28 @@ module ActiveJob
6
7
  extend ActiveSupport::Concern
7
8
 
8
9
  included do
9
- def before_setup
10
- @old_queue_adapter = queue_adapter
11
- ActiveJob::Base.queue_adapter = :test
10
+ def before_setup # :nodoc:
11
+ test_adapter = ActiveJob::QueueAdapters::TestAdapter.new
12
+
13
+ @old_queue_adapters = (ActiveJob::Base.subclasses << ActiveJob::Base).select do |klass|
14
+ # only override explicitly set adapters, a quirk of `class_attribute`
15
+ klass.singleton_class.public_instance_methods(false).include?(:_queue_adapter)
16
+ end.map do |klass|
17
+ [klass, klass.queue_adapter].tap do
18
+ klass.queue_adapter = test_adapter
19
+ end
20
+ end
21
+
12
22
  clear_enqueued_jobs
13
23
  clear_performed_jobs
14
24
  super
15
25
  end
16
26
 
17
- def after_teardown
27
+ def after_teardown # :nodoc:
18
28
  super
19
- ActiveJob::Base.queue_adapter = @old_queue_adapter
29
+ @old_queue_adapters.each do |(klass, adapter)|
30
+ klass.queue_adapter = adapter
31
+ end
20
32
  end
21
33
 
22
34
  # Asserts that the number of enqueued jobs matches the given number.
@@ -42,16 +54,24 @@ module ActiveJob
42
54
  # HelloJob.perform_later('rafael')
43
55
  # end
44
56
  # end
45
- def assert_enqueued_jobs(number)
57
+ #
58
+ # The number of times a specific job is enqueued can be asserted.
59
+ #
60
+ # def test_logging_job
61
+ # assert_enqueued_jobs 2, only: LoggingJob do
62
+ # LoggingJob.perform_later
63
+ # HelloJob.perform_later('jeremy')
64
+ # end
65
+ # end
66
+ def assert_enqueued_jobs(number, only: nil)
46
67
  if block_given?
47
- original_count = enqueued_jobs.size
68
+ original_count = enqueued_jobs_size(only: only)
48
69
  yield
49
- new_count = enqueued_jobs.size
50
- assert_equal number, new_count - original_count,
51
- "#{number} jobs expected, but #{new_count - original_count} were enqueued"
70
+ new_count = enqueued_jobs_size(only: only)
71
+ assert_equal number, new_count - original_count, "#{number} jobs expected, but #{new_count - original_count} were enqueued"
52
72
  else
53
- enqueued_jobs_size = enqueued_jobs.size
54
- assert_equal number, enqueued_jobs_size, "#{number} jobs expected, but #{enqueued_jobs_size} were enqueued"
73
+ actual_count = enqueued_jobs_size(only: only)
74
+ assert_equal number, actual_count, "#{number} jobs expected, but #{actual_count} were enqueued"
55
75
  end
56
76
  end
57
77
 
@@ -71,11 +91,19 @@ module ActiveJob
71
91
  # end
72
92
  # end
73
93
  #
94
+ # It can be asserted that no jobs of a specific kind are enqueued:
95
+ #
96
+ # def test_no_logging
97
+ # assert_no_enqueued_jobs only: LoggingJob do
98
+ # HelloJob.perform_later('jeremy')
99
+ # end
100
+ # end
101
+ #
74
102
  # Note: This assertion is simply a shortcut for:
75
103
  #
76
104
  # assert_enqueued_jobs 0, &block
77
- def assert_no_enqueued_jobs(&block)
78
- assert_enqueued_jobs 0, &block
105
+ def assert_no_enqueued_jobs(only: nil, &block)
106
+ assert_enqueued_jobs 0, only: only, &block
79
107
  end
80
108
 
81
109
  # Asserts that the number of performed jobs matches the given number.
@@ -109,10 +137,32 @@ module ActiveJob
109
137
  # HelloJob.perform_later('sean')
110
138
  # end
111
139
  # end
112
- def assert_performed_jobs(number)
140
+ #
141
+ # The block form supports filtering. If the :only option is specified,
142
+ # then only the listed job(s) will be performed.
143
+ #
144
+ # def test_hello_job
145
+ # assert_performed_jobs 1, only: HelloJob do
146
+ # HelloJob.perform_later('jeremy')
147
+ # LoggingJob.perform_later
148
+ # end
149
+ # end
150
+ #
151
+ # An array may also be specified, to support testing multiple jobs.
152
+ #
153
+ # def test_hello_and_logging_jobs
154
+ # assert_nothing_raised do
155
+ # assert_performed_jobs 2, only: [HelloJob, LoggingJob] do
156
+ # HelloJob.perform_later('jeremy')
157
+ # LoggingJob.perform_later('stewie')
158
+ # RescueJob.perform_later('david')
159
+ # end
160
+ # end
161
+ # end
162
+ def assert_performed_jobs(number, only: nil)
113
163
  if block_given?
114
164
  original_count = performed_jobs.size
115
- perform_enqueued_jobs { yield }
165
+ perform_enqueued_jobs(only: only) { yield }
116
166
  new_count = performed_jobs.size
117
167
  assert_equal number, new_count - original_count,
118
168
  "#{number} jobs expected, but #{new_count - original_count} were performed"
@@ -141,11 +191,33 @@ module ActiveJob
141
191
  # end
142
192
  # end
143
193
  #
194
+ # The block form supports filtering. If the :only option is specified,
195
+ # then only the listed job(s) will be performed.
196
+ #
197
+ # def test_hello_job
198
+ # assert_performed_jobs 1, only: HelloJob do
199
+ # HelloJob.perform_later('jeremy')
200
+ # LoggingJob.perform_later
201
+ # end
202
+ # end
203
+ #
204
+ # An array may also be specified, to support testing multiple jobs.
205
+ #
206
+ # def test_hello_and_logging_jobs
207
+ # assert_nothing_raised do
208
+ # assert_performed_jobs 2, only: [HelloJob, LoggingJob] do
209
+ # HelloJob.perform_later('jeremy')
210
+ # LoggingJob.perform_later('stewie')
211
+ # RescueJob.perform_later('david')
212
+ # end
213
+ # end
214
+ # end
215
+ #
144
216
  # Note: This assertion is simply a shortcut for:
145
217
  #
146
218
  # assert_performed_jobs 0, &block
147
- def assert_no_performed_jobs(&block)
148
- assert_performed_jobs 0, &block
219
+ def assert_no_performed_jobs(only: nil, &block)
220
+ assert_performed_jobs 0, only: only, &block
149
221
  end
150
222
 
151
223
  # Asserts that the job passed in the block has been enqueued with the given arguments.
@@ -154,19 +226,22 @@ module ActiveJob
154
226
  # assert_enqueued_with(job: MyJob, args: [1,2,3], queue: 'low') do
155
227
  # MyJob.perform_later(1,2,3)
156
228
  # end
229
+ #
230
+ # assert_enqueued_with(job: MyJob, at: Date.tomorrow.noon) do
231
+ # MyJob.set(wait_until: Date.tomorrow.noon).perform_later
232
+ # end
157
233
  # end
158
- def assert_enqueued_with(args = {}, &_block)
159
- original_enqueued_jobs = enqueued_jobs.dup
160
- clear_enqueued_jobs
234
+ def assert_enqueued_with(args = {})
235
+ original_enqueued_jobs_count = enqueued_jobs.count
161
236
  args.assert_valid_keys(:job, :args, :at, :queue)
162
237
  serialized_args = serialize_args_for_assertion(args)
163
238
  yield
164
- matching_job = enqueued_jobs.any? do |job|
239
+ in_block_jobs = enqueued_jobs.drop(original_enqueued_jobs_count)
240
+ matching_job = in_block_jobs.find do |job|
165
241
  serialized_args.all? { |key, value| value == job[key] }
166
242
  end
167
243
  assert matching_job, "No enqueued job found with #{args}"
168
- ensure
169
- queue_adapter.enqueued_jobs = original_enqueued_jobs + enqueued_jobs
244
+ instantiate_job(matching_job)
170
245
  end
171
246
 
172
247
  # Asserts that the job passed in the block has been performed with the given arguments.
@@ -175,30 +250,39 @@ module ActiveJob
175
250
  # assert_performed_with(job: MyJob, args: [1,2,3], queue: 'high') do
176
251
  # MyJob.perform_later(1,2,3)
177
252
  # end
253
+ #
254
+ # assert_performed_with(job: MyJob, at: Date.tomorrow.noon) do
255
+ # MyJob.set(wait_until: Date.tomorrow.noon).perform_later
256
+ # end
178
257
  # end
179
- def assert_performed_with(args = {}, &_block)
180
- original_performed_jobs = performed_jobs.dup
181
- clear_performed_jobs
258
+ def assert_performed_with(args = {})
259
+ original_performed_jobs_count = performed_jobs.count
182
260
  args.assert_valid_keys(:job, :args, :at, :queue)
183
261
  serialized_args = serialize_args_for_assertion(args)
184
262
  perform_enqueued_jobs { yield }
185
- matching_job = performed_jobs.any? do |job|
263
+ in_block_jobs = performed_jobs.drop(original_performed_jobs_count)
264
+ matching_job = in_block_jobs.find do |job|
186
265
  serialized_args.all? { |key, value| value == job[key] }
187
266
  end
188
267
  assert matching_job, "No performed job found with #{args}"
189
- ensure
190
- queue_adapter.performed_jobs = original_performed_jobs + performed_jobs
268
+ instantiate_job(matching_job)
191
269
  end
192
270
 
193
- def perform_enqueued_jobs
194
- @old_perform_enqueued_jobs = queue_adapter.perform_enqueued_jobs
195
- @old_perform_enqueued_at_jobs = queue_adapter.perform_enqueued_at_jobs
196
- queue_adapter.perform_enqueued_jobs = true
197
- queue_adapter.perform_enqueued_at_jobs = true
198
- yield
199
- ensure
200
- queue_adapter.perform_enqueued_jobs = @old_perform_enqueued_jobs
201
- queue_adapter.perform_enqueued_at_jobs = @old_perform_enqueued_at_jobs
271
+ def perform_enqueued_jobs(only: nil)
272
+ old_perform_enqueued_jobs = queue_adapter.perform_enqueued_jobs
273
+ old_perform_enqueued_at_jobs = queue_adapter.perform_enqueued_at_jobs
274
+ old_filter = queue_adapter.filter
275
+
276
+ begin
277
+ queue_adapter.perform_enqueued_jobs = true
278
+ queue_adapter.perform_enqueued_at_jobs = true
279
+ queue_adapter.filter = only
280
+ yield
281
+ ensure
282
+ queue_adapter.perform_enqueued_jobs = old_perform_enqueued_jobs
283
+ queue_adapter.perform_enqueued_at_jobs = old_perform_enqueued_at_jobs
284
+ queue_adapter.filter = old_filter
285
+ end
202
286
  end
203
287
 
204
288
  def queue_adapter
@@ -210,20 +294,34 @@ module ActiveJob
210
294
  to: :queue_adapter
211
295
 
212
296
  private
213
- def clear_enqueued_jobs
297
+ def clear_enqueued_jobs # :nodoc:
214
298
  enqueued_jobs.clear
215
299
  end
216
300
 
217
- def clear_performed_jobs
301
+ def clear_performed_jobs # :nodoc:
218
302
  performed_jobs.clear
219
303
  end
220
304
 
221
- def serialize_args_for_assertion(args)
222
- serialized_args = args.dup
223
- if job_args = serialized_args.delete(:args)
224
- serialized_args[:args] = ActiveJob::Arguments.serialize(job_args)
305
+ def enqueued_jobs_size(only: nil) # :nodoc:
306
+ if only
307
+ enqueued_jobs.count { |job| Array(only).include?(job.fetch(:job)) }
308
+ else
309
+ enqueued_jobs.count
225
310
  end
226
- serialized_args
311
+ end
312
+
313
+ def serialize_args_for_assertion(args) # :nodoc:
314
+ args.dup.tap do |serialized_args|
315
+ serialized_args[:args] = ActiveJob::Arguments.serialize(serialized_args[:args]) if serialized_args[:args]
316
+ serialized_args[:at] = serialized_args[:at].to_f if serialized_args[:at]
317
+ end
318
+ end
319
+
320
+ def instantiate_job(payload) # :nodoc:
321
+ job = payload[:job].new(*payload[:args])
322
+ job.scheduled_at = Time.at(payload[:at]) if payload.key?(:at)
323
+ job.queue_name = payload[:queue]
324
+ job
227
325
  end
228
326
  end
229
327
  end