activejob 6.0.3.3 → 6.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
 
@@ -72,7 +72,7 @@ module ActiveJob
72
72
  # Yes: Allows the priority to be set on the job object, at the queue level or
73
73
  # as default configuration option.
74
74
  #
75
- # No: Does not allow the priority of jobs to be configured.
75
+ # No: The adapter does not allow the priority of jobs to be configured.
76
76
  #
77
77
  # N/A: The adapter does not support queuing, and therefore sorting them.
78
78
  #
@@ -86,6 +86,8 @@ module ActiveJob
86
86
  #
87
87
  # Global: The adapter is configured that all jobs have a maximum run time.
88
88
  #
89
+ # No: The adapter does not allow the timeout of jobs to be configured.
90
+ #
89
91
  # N/A: This adapter does not run in a separate process, and therefore timeout
90
92
  # is unsupported.
91
93
  #
@@ -99,6 +101,8 @@ module ActiveJob
99
101
  #
100
102
  # Global: The adapter has a global number of retries.
101
103
  #
104
+ # No: The adapter does not allow the number of retries to be configured.
105
+ #
102
106
  # N/A: The adapter does not run in a separate process, and therefore doesn't
103
107
  # support retries.
104
108
  #
@@ -10,7 +10,7 @@ module ActiveJob
10
10
  # This reduces the cost of hosting on a service like Heroku along
11
11
  # with the memory footprint of having to maintain additional jobs if
12
12
  # hosting on a dedicated server. All queues can run within a
13
- # single application (eg. Rails, Sinatra, etc.) process.
13
+ # single application (e.g. Rails, Sinatra, etc.) process.
14
14
  #
15
15
  # Read more about Sucker Punch {here}[https://github.com/brandonhilkert/sucker_punch].
16
16
  #
@@ -12,7 +12,7 @@ module ActiveJob
12
12
  #
13
13
  # Rails.application.config.active_job.queue_adapter = :test
14
14
  class TestAdapter
15
- attr_accessor(:perform_enqueued_jobs, :perform_enqueued_at_jobs, :filter, :reject, :queue)
15
+ attr_accessor(:perform_enqueued_jobs, :perform_enqueued_at_jobs, :filter, :reject, :queue, :at)
16
16
  attr_writer(:enqueued_jobs, :performed_jobs)
17
17
 
18
18
  # Provides a store of all the enqueued jobs with the TestAdapter so you can check them.
@@ -54,7 +54,11 @@ module ActiveJob
54
54
  end
55
55
 
56
56
  def filtered?(job)
57
- filtered_queue?(job) || filtered_job_class?(job)
57
+ filtered_queue?(job) || filtered_job_class?(job) || filtered_time?(job)
58
+ end
59
+
60
+ def filtered_time?(job)
61
+ job.scheduled_at > at.to_f if at && job.scheduled_at
58
62
  end
59
63
 
60
64
  def filtered_queue?(job)
@@ -6,7 +6,6 @@ module ActiveJob
6
6
 
7
7
  # Includes the ability to override the default queue name and prefix.
8
8
  module ClassMethods
9
- mattr_accessor :queue_name_prefix
10
9
  mattr_accessor :default_queue_name, default: "default"
11
10
 
12
11
  # Specifies the name of the queue to process the job on.
@@ -49,13 +48,14 @@ module ActiveJob
49
48
  def queue_name_from_part(part_name) #:nodoc:
50
49
  queue_name = part_name || default_queue_name
51
50
  name_parts = [queue_name_prefix.presence, queue_name]
52
- name_parts.compact.join(queue_name_delimiter)
51
+ -name_parts.compact.join(queue_name_delimiter)
53
52
  end
54
53
  end
55
54
 
56
55
  included do
57
56
  class_attribute :queue_name, instance_accessor: false, default: -> { self.class.default_queue_name }
58
57
  class_attribute :queue_name_delimiter, instance_accessor: false, default: "_"
58
+ class_attribute :queue_name_prefix
59
59
  end
60
60
 
61
61
  # Returns the name of the queue the job will be run on.
@@ -34,6 +34,10 @@ module ActiveJob
34
34
  ActiveSupport.on_load(:action_dispatch_integration_test) do
35
35
  include ActiveJob::TestHelper
36
36
  end
37
+
38
+ ActiveSupport.on_load(:active_record) do
39
+ self.destroy_association_async_job = ActiveRecord::DestroyAssociationAsyncJob
40
+ end
37
41
  end
38
42
 
39
43
  initializer "active_job.set_reloader_hook" do |app|
@@ -9,12 +9,14 @@ module ActiveJob
9
9
  extend ActiveSupport::Autoload
10
10
 
11
11
  autoload :ObjectSerializer
12
+ autoload :TimeObjectSerializer
12
13
  autoload :SymbolSerializer
13
14
  autoload :DurationSerializer
14
15
  autoload :DateTimeSerializer
15
16
  autoload :DateSerializer
16
17
  autoload :TimeWithZoneSerializer
17
18
  autoload :TimeSerializer
19
+ autoload :ModuleSerializer
18
20
 
19
21
  mattr_accessor :_additional_serializers
20
22
  self._additional_serializers = Set.new
@@ -58,6 +60,7 @@ module ActiveJob
58
60
  DateTimeSerializer,
59
61
  DateSerializer,
60
62
  TimeWithZoneSerializer,
61
- TimeSerializer
63
+ TimeSerializer,
64
+ ModuleSerializer
62
65
  end
63
66
  end
@@ -2,11 +2,7 @@
2
2
 
3
3
  module ActiveJob
4
4
  module Serializers
5
- class DateTimeSerializer < ObjectSerializer # :nodoc:
6
- def serialize(time)
7
- super("value" => time.iso8601)
8
- end
9
-
5
+ class DateTimeSerializer < TimeObjectSerializer # :nodoc:
10
6
  def deserialize(hash)
11
7
  DateTime.iso8601(hash["value"])
12
8
  end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveJob
4
+ module Serializers
5
+ class ModuleSerializer < ObjectSerializer # :nodoc:
6
+ def serialize(constant)
7
+ super("value" => constant.name)
8
+ end
9
+
10
+ def deserialize(hash)
11
+ hash["value"].constantize
12
+ end
13
+
14
+ private
15
+ def klass
16
+ Module
17
+ end
18
+ end
19
+ end
20
+ end
@@ -39,7 +39,7 @@ module ActiveJob
39
39
  end
40
40
 
41
41
  # Deserializes an argument from a JSON primitive type.
42
- def deserialize(_argument)
42
+ def deserialize(json)
43
43
  raise NotImplementedError
44
44
  end
45
45
 
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveJob
4
+ module Serializers
5
+ class TimeObjectSerializer < ObjectSerializer # :nodoc:
6
+ NANO_PRECISION = 9
7
+
8
+ def serialize(time)
9
+ super("value" => time.iso8601(NANO_PRECISION))
10
+ end
11
+ end
12
+ end
13
+ end
@@ -2,11 +2,7 @@
2
2
 
3
3
  module ActiveJob
4
4
  module Serializers
5
- class TimeSerializer < ObjectSerializer # :nodoc:
6
- def serialize(time)
7
- super("value" => time.iso8601)
8
- end
9
-
5
+ class TimeSerializer < TimeObjectSerializer # :nodoc:
10
6
  def deserialize(hash)
11
7
  Time.iso8601(hash["value"])
12
8
  end
@@ -2,11 +2,7 @@
2
2
 
3
3
  module ActiveJob
4
4
  module Serializers
5
- class TimeWithZoneSerializer < ObjectSerializer # :nodoc:
6
- def serialize(time)
7
- super("value" => time.iso8601)
8
- end
9
-
5
+ class TimeWithZoneSerializer < TimeObjectSerializer # :nodoc:
10
6
  def deserialize(hash)
11
7
  Time.iso8601(hash["value"]).in_time_zone
12
8
  end
@@ -9,6 +9,8 @@ module ActiveJob
9
9
  :performed_jobs, :performed_jobs=,
10
10
  to: :queue_adapter
11
11
 
12
+ include ActiveSupport::Testing::Assertions
13
+
12
14
  module TestQueueAdapter
13
15
  extend ActiveSupport::Concern
14
16
 
@@ -117,17 +119,17 @@ module ActiveJob
117
119
  # HelloJob.perform_later('elfassy')
118
120
  # end
119
121
  # end
120
- def assert_enqueued_jobs(number, only: nil, except: nil, queue: nil)
122
+ def assert_enqueued_jobs(number, only: nil, except: nil, queue: nil, &block)
121
123
  if block_given?
122
- original_count = enqueued_jobs_with(only: only, except: except, queue: queue)
124
+ original_jobs = enqueued_jobs_with(only: only, except: except, queue: queue)
123
125
 
124
- yield
126
+ assert_nothing_raised(&block)
125
127
 
126
- new_count = enqueued_jobs_with(only: only, except: except, queue: queue)
128
+ new_jobs = enqueued_jobs_with(only: only, except: except, queue: queue)
127
129
 
128
- actual_count = new_count - original_count
130
+ actual_count = (new_jobs - original_jobs).count
129
131
  else
130
- actual_count = enqueued_jobs_with(only: only, except: except, queue: queue)
132
+ actual_count = enqueued_jobs_with(only: only, except: except, queue: queue).count
131
133
  end
132
134
 
133
135
  assert_equal number, actual_count, "#{number} jobs expected, but #{actual_count} were enqueued"
@@ -279,7 +281,7 @@ module ActiveJob
279
281
 
280
282
  performed_jobs_size = new_count - original_count
281
283
  else
282
- performed_jobs_size = performed_jobs_with(only: only, except: except, queue: queue)
284
+ performed_jobs_size = performed_jobs_with(only: only, except: except, queue: queue).count
283
285
  end
284
286
 
285
287
  assert_equal number, performed_jobs_size, "#{number} jobs expected, but #{performed_jobs_size} were performed"
@@ -345,44 +347,40 @@ module ActiveJob
345
347
  #
346
348
  # def test_assert_enqueued_with
347
349
  # MyJob.perform_later(1,2,3)
348
- # assert_enqueued_with(job: MyJob, args: [1,2,3], queue: 'low')
350
+ # assert_enqueued_with(job: MyJob, args: [1,2,3])
349
351
  #
350
- # MyJob.set(wait_until: Date.tomorrow.noon).perform_later
351
- # assert_enqueued_with(job: MyJob, at: Date.tomorrow.noon)
352
+ # MyJob.set(wait_until: Date.tomorrow.noon, queue: "my_queue").perform_later
353
+ # assert_enqueued_with(at: Date.tomorrow.noon, queue: "my_queue")
352
354
  # end
353
355
  #
354
- # The +at+ and +args+ arguments also accept a proc.
356
+ # The given arguments may also be specified as matcher procs that return a
357
+ # boolean value indicating whether a job's attribute meets certain criteria.
355
358
  #
356
- # To the +at+ proc, it will get passed the actual job's at argument.
359
+ # For example, a proc can be used to match a range of times:
357
360
  #
358
361
  # def test_assert_enqueued_with
359
- # expected_time = ->(at) do
360
- # (Date.yesterday..Date.tomorrow).cover?(at)
361
- # end
362
+ # at_matcher = ->(job_at) { (Date.yesterday..Date.tomorrow).cover?(job_at) }
363
+ #
364
+ # MyJob.set(wait_until: Date.today.noon).perform_later
362
365
  #
363
- # MyJob.set(at: Date.today.noon).perform_later
364
- # assert_enqueued_with(job: MyJob, at: expected_time)
366
+ # assert_enqueued_with(job: MyJob, at: at_matcher)
365
367
  # end
366
368
  #
367
- # To the +args+ proc, it will get passed the actual job's arguments
368
- # Your proc needs to return a boolean value determining if
369
- # the job's arguments matches your expectation. This is useful to check only
370
- # for a subset of arguments.
369
+ # A proc can also be used to match a subset of a job's args:
371
370
  #
372
371
  # def test_assert_enqueued_with
373
- # expected_args = ->(job_args) do
374
- # assert job_args.first.key?(:foo)
375
- # end
372
+ # args_matcher = ->(job_args) { job_args[0].key?(:foo) }
373
+ #
374
+ # MyJob.perform_later(foo: "bar", other_arg: "No need to check in the test")
376
375
  #
377
- # MyJob.perform_later(foo: 'bar', other_arg: 'No need to check in the test')
378
- # assert_enqueued_with(job: MyJob, args: expected_args, queue: 'low')
376
+ # assert_enqueued_with(job: MyJob, args: args_matcher)
379
377
  # end
380
378
  #
381
379
  # If a block is passed, asserts that the block will cause the job to be
382
380
  # enqueued with the given arguments.
383
381
  #
384
382
  # def test_assert_enqueued_with
385
- # assert_enqueued_with(job: MyJob, args: [1,2,3], queue: 'low') do
383
+ # assert_enqueued_with(job: MyJob, args: [1,2,3]) do
386
384
  # MyJob.perform_later(1,2,3)
387
385
  # end
388
386
  #
@@ -390,22 +388,24 @@ module ActiveJob
390
388
  # MyJob.set(wait_until: Date.tomorrow.noon).perform_later
391
389
  # end
392
390
  # end
393
- def assert_enqueued_with(job: nil, args: nil, at: nil, queue: nil)
391
+ def assert_enqueued_with(job: nil, args: nil, at: nil, queue: nil, &block)
394
392
  expected = { job: job, args: args, at: at, queue: queue }.compact
395
393
  expected_args = prepare_args_for_assertion(expected)
394
+ potential_matches = []
396
395
 
397
396
  if block_given?
398
- original_enqueued_jobs_count = enqueued_jobs.count
397
+ original_enqueued_jobs = enqueued_jobs.dup
399
398
 
400
- yield
399
+ assert_nothing_raised(&block)
401
400
 
402
- jobs = enqueued_jobs.drop(original_enqueued_jobs_count)
401
+ jobs = enqueued_jobs - original_enqueued_jobs
403
402
  else
404
403
  jobs = enqueued_jobs
405
404
  end
406
405
 
407
406
  matching_job = jobs.find do |enqueued_job|
408
407
  deserialized_job = deserialize_args_for_assertion(enqueued_job)
408
+ potential_matches << deserialized_job
409
409
 
410
410
  expected_args.all? do |key, value|
411
411
  if value.respond_to?(:call)
@@ -416,7 +416,9 @@ module ActiveJob
416
416
  end
417
417
  end
418
418
 
419
- assert matching_job, "No enqueued job found with #{expected}"
419
+ message = +"No enqueued job found with #{expected}"
420
+ message << "\n\nPotential matches: #{potential_matches.join("\n")}" if potential_matches.present?
421
+ assert matching_job, message
420
422
  instantiate_job(matching_job)
421
423
  end
422
424
 
@@ -427,42 +429,40 @@ module ActiveJob
427
429
  #
428
430
  # perform_enqueued_jobs
429
431
  #
430
- # assert_performed_with(job: MyJob, args: [1,2,3], queue: 'high')
432
+ # assert_performed_with(job: MyJob, args: [1,2,3])
431
433
  #
432
- # MyJob.set(wait_until: Date.tomorrow.noon).perform_later
434
+ # MyJob.set(wait_until: Date.tomorrow.noon, queue: "my_queue").perform_later
433
435
  #
434
436
  # perform_enqueued_jobs
435
437
  #
436
- # assert_performed_with(job: MyJob, at: Date.tomorrow.noon)
438
+ # assert_performed_with(at: Date.tomorrow.noon, queue: "my_queue")
437
439
  # end
438
440
  #
439
- # The +at+ and +args+ arguments also accept a proc.
441
+ # The given arguments may also be specified as matcher procs that return a
442
+ # boolean value indicating whether a job's attribute meets certain criteria.
440
443
  #
441
- # To the +at+ proc, it will get passed the actual job's at argument.
444
+ # For example, a proc can be used to match a range of times:
442
445
  #
443
- # def test_assert_enqueued_with
444
- # expected_time = ->(at) do
445
- # (Date.yesterday..Date.tomorrow).cover?(at)
446
- # end
446
+ # def test_assert_performed_with
447
+ # at_matcher = ->(job_at) { (Date.yesterday..Date.tomorrow).cover?(job_at) }
448
+ #
449
+ # MyJob.set(wait_until: Date.today.noon).perform_later
450
+ #
451
+ # perform_enqueued_jobs
447
452
  #
448
- # MyJob.set(at: Date.today.noon).perform_later
449
- # assert_enqueued_with(job: MyJob, at: expected_time)
453
+ # assert_performed_with(job: MyJob, at: at_matcher)
450
454
  # end
451
455
  #
452
- # To the +args+ proc, it will get passed the actual job's arguments
453
- # Your proc needs to return a boolean value determining if
454
- # the job's arguments matches your expectation. This is useful to check only
455
- # for a subset of arguments.
456
+ # A proc can also be used to match a subset of a job's args:
456
457
  #
457
458
  # def test_assert_performed_with
458
- # expected_args = ->(job_args) do
459
- # assert job_args.first.key?(:foo)
460
- # end
461
- # MyJob.perform_later(foo: 'bar', other_arg: 'No need to check in the test')
459
+ # args_matcher = ->(job_args) { job_args[0].key?(:foo) }
460
+ #
461
+ # MyJob.perform_later(foo: "bar", other_arg: "No need to check in the test")
462
462
  #
463
463
  # perform_enqueued_jobs
464
464
  #
465
- # assert_performed_with(job: MyJob, args: expected_args, queue: 'high')
465
+ # assert_performed_with(job: MyJob, args: args_matcher)
466
466
  # end
467
467
  #
468
468
  # If a block is passed, that block performs all of the jobs that were
@@ -470,7 +470,7 @@ module ActiveJob
470
470
  # the job has been performed with the given arguments in the block.
471
471
  #
472
472
  # def test_assert_performed_with
473
- # assert_performed_with(job: MyJob, args: [1,2,3], queue: 'high') do
473
+ # assert_performed_with(job: MyJob, args: [1,2,3]) do
474
474
  # MyJob.perform_later(1,2,3)
475
475
  # end
476
476
  #
@@ -481,6 +481,7 @@ module ActiveJob
481
481
  def assert_performed_with(job: nil, args: nil, at: nil, queue: nil, &block)
482
482
  expected = { job: job, args: args, at: at, queue: queue }.compact
483
483
  expected_args = prepare_args_for_assertion(expected)
484
+ potential_matches = []
484
485
 
485
486
  if block_given?
486
487
  original_performed_jobs_count = performed_jobs.count
@@ -494,6 +495,7 @@ module ActiveJob
494
495
 
495
496
  matching_job = jobs.find do |enqueued_job|
496
497
  deserialized_job = deserialize_args_for_assertion(enqueued_job)
498
+ potential_matches << deserialized_job
497
499
 
498
500
  expected_args.all? do |key, value|
499
501
  if value.respond_to?(:call)
@@ -504,7 +506,10 @@ module ActiveJob
504
506
  end
505
507
  end
506
508
 
507
- assert matching_job, "No performed job found with #{expected}"
509
+ message = +"No performed job found with #{expected}"
510
+ message << "\n\nPotential matches: #{potential_matches.join("\n")}" if potential_matches.present?
511
+ assert matching_job, message
512
+
508
513
  instantiate_job(matching_job)
509
514
  end
510
515
 
@@ -563,8 +568,10 @@ module ActiveJob
563
568
  # assert_performed_jobs 1
564
569
  # end
565
570
  #
566
- def perform_enqueued_jobs(only: nil, except: nil, queue: nil)
567
- return flush_enqueued_jobs(only: only, except: except, queue: queue) unless block_given?
571
+ # If the +:at+ option is specified, then only run jobs enqueued to run
572
+ # immediately or before the given time
573
+ def perform_enqueued_jobs(only: nil, except: nil, queue: nil, at: nil, &block)
574
+ return flush_enqueued_jobs(only: only, except: except, queue: queue, at: at) unless block_given?
568
575
 
569
576
  validate_option(only: only, except: except)
570
577
 
@@ -573,6 +580,7 @@ module ActiveJob
573
580
  old_filter = queue_adapter.filter
574
581
  old_reject = queue_adapter.reject
575
582
  old_queue = queue_adapter.queue
583
+ old_at = queue_adapter.at
576
584
 
577
585
  begin
578
586
  queue_adapter.perform_enqueued_jobs = true
@@ -580,14 +588,16 @@ module ActiveJob
580
588
  queue_adapter.filter = only
581
589
  queue_adapter.reject = except
582
590
  queue_adapter.queue = queue
591
+ queue_adapter.at = at
583
592
 
584
- yield
593
+ assert_nothing_raised(&block)
585
594
  ensure
586
595
  queue_adapter.perform_enqueued_jobs = old_perform_enqueued_jobs
587
596
  queue_adapter.perform_enqueued_at_jobs = old_perform_enqueued_at_jobs
588
597
  queue_adapter.filter = old_filter
589
598
  queue_adapter.reject = old_reject
590
599
  queue_adapter.queue = old_queue
600
+ queue_adapter.at = old_at
591
601
  end
592
602
  end
593
603
 
@@ -609,10 +619,10 @@ module ActiveJob
609
619
  performed_jobs.clear
610
620
  end
611
621
 
612
- def jobs_with(jobs, only: nil, except: nil, queue: nil)
622
+ def jobs_with(jobs, only: nil, except: nil, queue: nil, at: nil)
613
623
  validate_option(only: only, except: except)
614
624
 
615
- jobs.count do |job|
625
+ jobs.dup.select do |job|
616
626
  job_class = job.fetch(:job)
617
627
 
618
628
  if only
@@ -625,6 +635,10 @@ module ActiveJob
625
635
  next false unless queue.to_s == job.fetch(:queue, job_class.queue_name)
626
636
  end
627
637
 
638
+ if at && job[:at]
639
+ next false if job[:at] > at.to_f
640
+ end
641
+
628
642
  yield job if block_given?
629
643
 
630
644
  true
@@ -637,41 +651,28 @@ module ActiveJob
637
651
  ->(job) { Array(filter).include?(job.fetch(:job)) }
638
652
  end
639
653
 
640
- def enqueued_jobs_with(only: nil, except: nil, queue: nil, &block)
641
- jobs_with(enqueued_jobs, only: only, except: except, queue: queue, &block)
654
+ def enqueued_jobs_with(only: nil, except: nil, queue: nil, at: nil, &block)
655
+ jobs_with(enqueued_jobs, only: only, except: except, queue: queue, at: at, &block)
642
656
  end
643
657
 
644
658
  def performed_jobs_with(only: nil, except: nil, queue: nil, &block)
645
659
  jobs_with(performed_jobs, only: only, except: except, queue: queue, &block)
646
660
  end
647
661
 
648
- def flush_enqueued_jobs(only: nil, except: nil, queue: nil)
649
- enqueued_jobs_with(only: only, except: except, queue: queue) do |payload|
650
- instantiate_job(payload).perform_now
662
+ def flush_enqueued_jobs(only: nil, except: nil, queue: nil, at: nil)
663
+ enqueued_jobs_with(only: only, except: except, queue: queue, at: at) do |payload|
664
+ queue_adapter.enqueued_jobs.delete(payload)
651
665
  queue_adapter.performed_jobs << payload
652
- end
666
+ instantiate_job(payload).perform_now
667
+ end.count
653
668
  end
654
669
 
655
670
  def prepare_args_for_assertion(args)
656
671
  args.dup.tap do |arguments|
657
- if arguments[:at] && !arguments[:at].respond_to?(:call)
672
+ if arguments[:at].acts_like?(:time)
658
673
  at_range = arguments[:at] - 1..arguments[:at] + 1
659
674
  arguments[:at] = ->(at) { at_range.cover?(at) }
660
675
  end
661
- arguments[:args] = round_time_arguments(arguments[:args]) if arguments[:args]
662
- end
663
- end
664
-
665
- def round_time_arguments(argument)
666
- case argument
667
- when Time, ActiveSupport::TimeWithZone, DateTime
668
- argument.change(usec: 0)
669
- when Hash
670
- argument.transform_values { |value| round_time_arguments(value) }
671
- when Array
672
- argument.map { |element| round_time_arguments(element) }
673
- else
674
- argument
675
676
  end
676
677
  end
677
678