activejob 4.2.0.beta1 → 4.2.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
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