activejob 4.2.0.beta1 → 4.2.0.beta2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f5b4155049f893704d35f9712dacadac5c6a63a3
4
- data.tar.gz: c072445cde94c55babc2a55489e877ff7930529e
3
+ metadata.gz: a1959ada2768247a24b82a496e71ea52185e0f44
4
+ data.tar.gz: b0144f92ff3b44c8cecb1d94ee55d668911f9a42
5
5
  SHA512:
6
- metadata.gz: 563242f9567f12e8cb4ecb8204fc9a0956c93507ddea5eca3d3fe824157aca34afacfd47eff94e73aadadedbfb8972e058af16cb226f9ca53959cab3b8cf2de3
7
- data.tar.gz: fbc34e79c27f8ce715d350c740b149b8fe02decf1d3175a6c1ab3a859b93caf963a094c1c5c5d01c6b22f8f5bb9cd093504f2263d10f4ca50b0319ef334f66c6
6
+ metadata.gz: 06e70d712da50b7a14d683f3c53f4a57e0d88eada98c9a720d5d57be0e364f82b2b9bf6e53ad5bcbaf503e0b2fdde3fcbca49cf26fff0ee9cbebad6442a3e5ee
7
+ data.tar.gz: 853811e9c3e1102f8beed9e8a9b02ccb1346524c57125dce72dcd6b87b80c3810130bd287dd81467b27a34e2bd499edea1175c754f7057adf94684ffb41879b1
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Active Job is a framework for declaring jobs and making them run on a variety
4
4
  of queueing backends. These jobs can be everything from regularly scheduled
5
- clean-ups, billing charges, or mailings. Anything that can be chopped up into
5
+ clean-ups, to billing charges, to mailings. Anything that can be chopped up into
6
6
  small units of work and run in parallel, really.
7
7
 
8
8
  It also serves as the backend for ActionMailer's #deliver_later functionality
@@ -24,9 +24,9 @@ Set the queue adapter for Active Job:
24
24
 
25
25
  ``` ruby
26
26
  ActiveJob::Base.queue_adapter = :inline # default queue adapter
27
- # Adapters currently supported: :backburner, :delayed_job, :qu, :que, :queue_classic,
28
- # :resque, :sidekiq, :sneakers, :sucker_punch
29
27
  ```
28
+ Note: To learn how to use your preferred queueing backend see its adapter
29
+ documentation at ActiveJob::QueueAdapters.
30
30
 
31
31
  Declare a job like so:
32
32
 
@@ -43,15 +43,15 @@ end
43
43
  Enqueue a job like so:
44
44
 
45
45
  ```ruby
46
- MyJob.enqueue record # Enqueue a job to be performed as soon the queueing system is free.
46
+ MyJob.perform_later record # Enqueue a job to be performed as soon the queueing system is free.
47
47
  ```
48
48
 
49
49
  ```ruby
50
- MyJob.enqueue_at Date.tomorrow.noon, record # Enqueue a job to be performed tomorrow at noon.
50
+ MyJob.set(wait_until: Date.tomorrow.noon).perform_later(record) # Enqueue a job to be performed tomorrow at noon.
51
51
  ```
52
52
 
53
53
  ```ruby
54
- MyJob.enqueue_in 1.week, record # Enqueue a job to be performed 1 week from now.
54
+ MyJob.set(wait: 1.week).perform_later(record) # Enqueue a job to be performed 1 week from now.
55
55
  ```
56
56
 
57
57
  That's it!
@@ -88,18 +88,9 @@ by default has been mixed into Active Record classes.
88
88
 
89
89
  ## Supported queueing systems
90
90
 
91
- We currently have adapters for:
92
-
93
- * [Backburner](https://github.com/nesquena/backburner)
94
- * [Delayed Job](https://github.com/collectiveidea/delayed_job)
95
- * [Qu](https://github.com/bkeepers/qu)
96
- * [Que](https://github.com/chanks/que)
97
- * [QueueClassic](https://github.com/ryandotsmith/queue_classic)
98
- * [Resque 1.x](https://github.com/resque/resque)
99
- * [Sidekiq](https://github.com/mperham/sidekiq)
100
- * [Sneakers](https://github.com/jondot/sneakers)
101
- * [Sucker Punch](https://github.com/brandonhilkert/sucker_punch)
102
-
91
+ Active Job has built-in adapters for multiple queueing backends (Sidekiq,
92
+ Resque, Delayed Job and others). To get an up-to-date list of the adapters
93
+ see the API Documentation for [ActiveJob::QueueAdapters](http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html).
103
94
 
104
95
  ## Auxiliary gems
105
96
 
@@ -30,4 +30,8 @@ module ActiveJob
30
30
  extend ActiveSupport::Autoload
31
31
 
32
32
  autoload :Base
33
- end
33
+ autoload :QueueAdapters
34
+ autoload :ConfiguredJob
35
+ autoload :TestCase
36
+ autoload :TestHelper
37
+ end
@@ -1,14 +1,25 @@
1
1
  module ActiveJob
2
+ # Raised when an exception is raised during job arguments deserialization.
3
+ #
4
+ # Wraps the original exception raised as +original_exception+.
2
5
  class DeserializationError < StandardError
3
6
  attr_reader :original_exception
4
7
 
5
- def initialize(e)
6
- super ("Error while trying to deserialize arguments: #{e.message}")
8
+ def initialize(e) #:nodoc:
9
+ super("Error while trying to deserialize arguments: #{e.message}")
7
10
  @original_exception = e
8
11
  set_backtrace e.backtrace
9
12
  end
10
13
  end
11
14
 
15
+ # Raised when an unsupported argument type is being set as job argument. We
16
+ # currently support NilClass, Fixnum, Float, String, TrueClass, FalseClass,
17
+ # Bignum and object that can be represented as GlobalIDs (ex: Active Record).
18
+ # Also raised if you set the key for a Hash something else than a string or
19
+ # a symbol.
20
+ class SerializationError < ArgumentError
21
+ end
22
+
12
23
  module Arguments
13
24
  extend self
14
25
  TYPE_WHITELIST = [ NilClass, Fixnum, Float, String, TrueClass, FalseClass, Bignum ]
@@ -19,43 +30,75 @@ module ActiveJob
19
30
 
20
31
  def deserialize(arguments)
21
32
  arguments.map { |argument| deserialize_argument(argument) }
33
+ rescue => e
34
+ raise DeserializationError.new(e)
22
35
  end
23
36
 
24
37
  private
38
+ GLOBALID_KEY = '_aj_globalid'.freeze
39
+ private_constant :GLOBALID_KEY
40
+
25
41
  def serialize_argument(argument)
26
42
  case argument
27
- when GlobalID::Identification
28
- argument.global_id.to_s
29
43
  when *TYPE_WHITELIST
30
44
  argument
45
+ when GlobalID::Identification
46
+ { GLOBALID_KEY => argument.to_global_id.to_s }
31
47
  when Array
32
- serialize(argument)
48
+ argument.map { |arg| serialize_argument(arg) }
33
49
  when Hash
34
- Hash[ argument.map { |key, value| [ serialize_hash_key(key), serialize_argument(value) ] } ]
50
+ argument.each_with_object({}) do |(key, value), hash|
51
+ hash[serialize_hash_key(key)] = serialize_argument(value)
52
+ end
35
53
  else
36
- raise "Unsupported argument type: #{argument.class.name}"
54
+ raise SerializationError.new("Unsupported argument type: #{argument.class.name}")
37
55
  end
38
56
  end
39
57
 
40
58
  def deserialize_argument(argument)
41
59
  case argument
60
+ when String
61
+ GlobalID::Locator.locate(argument) || argument
62
+ when *TYPE_WHITELIST
63
+ argument
42
64
  when Array
43
- deserialize(argument)
65
+ argument.map { |arg| deserialize_argument(arg) }
44
66
  when Hash
45
- Hash[ argument.map { |key, value| [ key, deserialize_argument(value) ] } ].with_indifferent_access
67
+ if serialized_global_id?(argument)
68
+ deserialize_global_id argument
69
+ else
70
+ deserialize_hash argument
71
+ end
46
72
  else
47
- GlobalID::Locator.locate(argument) || argument
73
+ raise ArgumentError, "Can only deserialize primitive arguments: #{argument.inspect}"
48
74
  end
49
- rescue => e
50
- raise DeserializationError.new(e)
51
75
  end
52
76
 
77
+ def serialized_global_id?(hash)
78
+ hash.size == 1 and hash.include?(GLOBALID_KEY)
79
+ end
80
+
81
+ def deserialize_global_id(hash)
82
+ GlobalID::Locator.locate hash[GLOBALID_KEY]
83
+ end
84
+
85
+ def deserialize_hash(serialized_hash)
86
+ serialized_hash.each_with_object({}.with_indifferent_access) do |(key, value), hash|
87
+ hash[key] = deserialize_argument(value)
88
+ end
89
+ end
90
+
91
+ RESERVED_KEYS = [GLOBALID_KEY, GLOBALID_KEY.to_sym]
92
+ private_constant :RESERVED_KEYS
93
+
53
94
  def serialize_hash_key(key)
54
95
  case key
96
+ when *RESERVED_KEYS
97
+ raise SerializationError.new("Can't serialize a Hash with reserved key #{key.inspect}")
55
98
  when String, Symbol
56
99
  key.to_s
57
100
  else
58
- raise "Unsupported hash key type: #{key.class.name}"
101
+ raise SerializationError.new("Only string and symbol hash keys may be serialized as job arguments, but #{key.inspect} is a #{key.class}")
59
102
  end
60
103
  end
61
104
  end
@@ -1,20 +1,19 @@
1
+ require 'active_job/core'
1
2
  require 'active_job/queue_adapter'
2
3
  require 'active_job/queue_name'
3
4
  require 'active_job/enqueuing'
4
5
  require 'active_job/execution'
5
6
  require 'active_job/callbacks'
6
- require 'active_job/identifier'
7
7
  require 'active_job/logging'
8
8
 
9
9
  module ActiveJob
10
10
  class Base
11
- extend QueueAdapter
12
-
11
+ include Core
12
+ include QueueAdapter
13
13
  include QueueName
14
14
  include Enqueuing
15
15
  include Execution
16
16
  include Callbacks
17
- include Identifier
18
17
  include Logging
19
18
 
20
19
  ActiveSupport.run_load_hooks(:active_job, self)
@@ -3,8 +3,8 @@ require 'active_support/callbacks'
3
3
  module ActiveJob
4
4
  # = Active Job Callbacks
5
5
  #
6
- # Active Job provides hooks during the lifecycle of a job. Callbacks allows you to trigger
7
- # logic during the lifecycle of a job. Available callbacks:
6
+ # Active Job provides hooks during the lifecycle of a job. Callbacks allow you
7
+ # to trigger logic during the lifecycle of a job. Available callbacks are:
8
8
  #
9
9
  # * <tt>before_enqueue</tt>
10
10
  # * <tt>around_enqueue</tt>
@@ -36,7 +36,7 @@ module ActiveJob
36
36
  # def perform(video_id)
37
37
  # Video.find(video_id).process
38
38
  # end
39
- # end
39
+ # end
40
40
  #
41
41
  def before_perform(*filters, &blk)
42
42
  set_callback(:perform, :before, *filters, &blk)
@@ -55,7 +55,7 @@ module ActiveJob
55
55
  # def perform(video_id)
56
56
  # Video.find(video_id).process
57
57
  # end
58
- # end
58
+ # end
59
59
  #
60
60
  def after_perform(*filters, &blk)
61
61
  set_callback(:perform, :after, *filters, &blk)
@@ -75,7 +75,7 @@ module ActiveJob
75
75
  # def perform(video_id)
76
76
  # Video.find(video_id).process
77
77
  # end
78
- # end
78
+ # end
79
79
  #
80
80
  def around_perform(*filters, &blk)
81
81
  set_callback(:perform, :around, *filters, &blk)
@@ -94,7 +94,7 @@ module ActiveJob
94
94
  # def perform(video_id)
95
95
  # Video.find(video_id).process
96
96
  # end
97
- # end
97
+ # end
98
98
  #
99
99
  def before_enqueue(*filters, &blk)
100
100
  set_callback(:enqueue, :before, *filters, &blk)
@@ -113,7 +113,7 @@ module ActiveJob
113
113
  # def perform(video_id)
114
114
  # Video.find(video_id).process
115
115
  # end
116
- # end
116
+ # end
117
117
  #
118
118
  def after_enqueue(*filters, &blk)
119
119
  set_callback(:enqueue, :after, *filters, &blk)
@@ -134,7 +134,7 @@ module ActiveJob
134
134
  # def perform(video_id)
135
135
  # Video.find(video_id).process
136
136
  # end
137
- # end
137
+ # end
138
138
  #
139
139
  def around_enqueue(*filters, &blk)
140
140
  set_callback(:enqueue, :around, *filters, &blk)
@@ -0,0 +1,16 @@
1
+ module ActiveJob
2
+ class ConfiguredJob #:nodoc:
3
+ def initialize(job_class, options={})
4
+ @options = options
5
+ @job_class = job_class
6
+ end
7
+
8
+ def perform_now(*args)
9
+ @job_class.new(*args).perform_now
10
+ end
11
+
12
+ def perform_later(*args)
13
+ @job_class.new(*args).enqueue @options
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,89 @@
1
+ module ActiveJob
2
+ module Core
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ # Job arguments
7
+ attr_accessor :arguments
8
+ attr_writer :serialized_arguments
9
+
10
+ # Timestamp when the job should be performed
11
+ attr_accessor :scheduled_at
12
+
13
+ # Job Identifier
14
+ attr_accessor :job_id
15
+
16
+ # Queue in which the job will reside.
17
+ attr_writer :queue_name
18
+ end
19
+
20
+ module ClassMethods
21
+ # Creates a new job instance from a hash created with +serialize+
22
+ def deserialize(job_data)
23
+ job = job_data['job_class'].constantize.new
24
+ job.job_id = job_data['job_id']
25
+ job.queue_name = job_data['queue_name']
26
+ job.serialized_arguments = job_data['arguments']
27
+ job
28
+ end
29
+
30
+ # Creates a job preconfigured with the given options. You can call
31
+ # perform_later with the job arguments to enqueue the job with the
32
+ # preconfigured options
33
+ #
34
+ # ==== Options
35
+ # * <tt>:wait</tt> - Enqueues the job with the specified delay
36
+ # * <tt>:wait_until</tt> - Enqueues the job at the time specified
37
+ # * <tt>:queue</tt> - Enqueues the job on the specified queue
38
+ #
39
+ # ==== Examples
40
+ #
41
+ # VideoJob.set(queue: :some_queue).perform_later(Video.last)
42
+ # VideoJob.set(wait: 5.minutes).perform_later(Video.last)
43
+ # VideoJob.set(wait_until: Time.tomorroe).perform_later(Video.last)
44
+ # VideoJob.set(queue: :some_queue, wait: 5.minutes).perform_later(Video.last)
45
+ # VideoJob.set(queue: :some_queue, wait_until: Time.tomorroe).perform_later(Video.last)
46
+ def set(options={})
47
+ ConfiguredJob.new(self, options)
48
+ end
49
+ end
50
+
51
+ # Creates a new job instance. Takes as arguments the arguments that
52
+ # will be passed to the perform method.
53
+ def initialize(*arguments)
54
+ @arguments = arguments
55
+ @job_id = SecureRandom.uuid
56
+ @queue_name = self.class.queue_name
57
+ end
58
+
59
+ # Returns a hash with the job data that can safely be passed to the
60
+ # queueing adapter.
61
+ def serialize
62
+ {
63
+ 'job_class' => self.class.name,
64
+ 'job_id' => job_id,
65
+ 'queue_name' => queue_name,
66
+ 'arguments' => serialize_arguments(arguments)
67
+ }
68
+ end
69
+
70
+ private
71
+ def deserialize_arguments_if_needed
72
+ if defined?(@serialized_arguments) && @serialized_arguments.present?
73
+ @arguments = deserialize_arguments(@serialized_arguments)
74
+ @serialized_arguments = nil
75
+ end
76
+ end
77
+
78
+ def serialize_arguments(serialized_args)
79
+ Arguments.serialize(serialized_args)
80
+ end
81
+
82
+ def deserialize_arguments(serialized_args)
83
+ Arguments.deserialize(serialized_args)
84
+ end
85
+ end
86
+ end
87
+
88
+
89
+
@@ -5,67 +5,71 @@ module ActiveJob
5
5
  extend ActiveSupport::Concern
6
6
 
7
7
  module ClassMethods
8
- # Push a job onto the queue. The arguments must be legal JSON types
8
+ # Push a job onto the queue. The arguments must be legal JSON types
9
9
  # (string, int, float, nil, true, false, hash or array) or
10
- # GlobalID::Identification instances. Arbitrary Ruby objects
10
+ # GlobalID::Identification instances. Arbitrary Ruby objects
11
11
  # are not supported.
12
12
  #
13
13
  # Returns an instance of the job class queued with args available in
14
14
  # Job#arguments.
15
- def enqueue(*args)
16
- new(args).tap do |job|
17
- job.run_callbacks :enqueue do
18
- queue_adapter.enqueue self, job.job_id, *Arguments.serialize(args)
19
- end
20
- end
21
- end
22
-
23
- # Enqueue a job to be performed at +interval+ from now.
24
- #
25
- # enqueue_in(1.week, "mike")
26
- #
27
- # Returns an instance of the job class queued with args available in
28
- # Job#arguments and the timestamp in Job#enqueue_at.
29
- def enqueue_in(interval, *args)
30
- enqueue_at interval.seconds.from_now, *args
15
+ def perform_later(*args)
16
+ job_or_instantiate(*args).enqueue
31
17
  end
32
18
 
33
- # Enqueue a job to be performed at an explicit point in time.
34
- #
35
- # enqueue_at(Date.tomorrow.midnight, "mike")
36
- #
37
- # Returns an instance of the job class queued with args available in
38
- # Job#arguments and the timestamp in Job#enqueue_at.
39
- def enqueue_at(timestamp, *args)
40
- new(args).tap do |job|
41
- job.enqueued_at = timestamp
42
-
43
- job.run_callbacks :enqueue do
44
- queue_adapter.enqueue_at self, timestamp.to_f, job.job_id, *Arguments.serialize(args)
45
- end
19
+ protected
20
+ def job_or_instantiate(*args)
21
+ args.first.is_a?(self) ? args.first : new(*args)
46
22
  end
47
- end
48
- end
49
-
50
- included do
51
- attr_accessor :arguments
52
- attr_accessor :enqueued_at
53
- end
54
-
55
- def initialize(arguments = nil)
56
- @arguments = arguments
57
23
  end
58
24
 
59
- def retry_now
60
- self.class.enqueue(*arguments)
25
+ # Reschedule the job to be re-executed. This is useful in combination
26
+ # with the +rescue_from+ option. When you rescue an exception from your job
27
+ # you can ask Active Job to retry performing your job.
28
+ #
29
+ # ==== Options
30
+ # * <tt>:wait</tt> - Enqueues the job with the specified delay
31
+ # * <tt>:wait_until</tt> - Enqueues the job at the time specified
32
+ # * <tt>:queue</tt> - Enqueues the job on the specified queue
33
+ #
34
+ # ==== Examples
35
+ #
36
+ # class SiteScrapperJob < ActiveJob::Base
37
+ # rescue_from(ErrorLoadingSite) do
38
+ # retry_job queue: :low_priority
39
+ # end
40
+ # def perform(*args)
41
+ # # raise ErrorLoadingSite if cannot scrape
42
+ # end
43
+ # end
44
+ def retry_job(options={})
45
+ enqueue options
61
46
  end
62
47
 
63
- def retry_in(interval)
64
- self.class.enqueue_in interval, *arguments
65
- end
66
-
67
- def retry_at(timestamp)
68
- self.class.enqueue_at timestamp, *arguments
48
+ # Enqueues the job to be performed by the queue adapter.
49
+ #
50
+ # ==== Options
51
+ # * <tt>:wait</tt> - Enqueues the job with the specified delay
52
+ # * <tt>:wait_until</tt> - Enqueues the job at the time specified
53
+ # * <tt>:queue</tt> - Enqueues the job on the specified queue
54
+ #
55
+ # ==== Examples
56
+ #
57
+ # my_job_instance.enqueue
58
+ # my_job_instance.enqueue wait: 5.minutes
59
+ # my_job_instance.enqueue queue: :important
60
+ # my_job_instance.enqueue wait_until: Date.tomorrow.midnight
61
+ def enqueue(options={})
62
+ self.scheduled_at = options[:wait].seconds.from_now.to_f if options[:wait]
63
+ self.scheduled_at = options[:wait_until].to_f if options[:wait_until]
64
+ self.queue_name = self.class.queue_name_from_part(options[:queue]) if options[:queue]
65
+ run_callbacks :enqueue do
66
+ if self.scheduled_at
67
+ self.class.queue_adapter.enqueue_at self, self.scheduled_at
68
+ else
69
+ self.class.queue_adapter.enqueue self
70
+ end
71
+ end
72
+ self
69
73
  end
70
74
  end
71
75
  end