mongo-resque 1.17.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/HISTORY.md +278 -0
  2. data/LICENSE +20 -0
  3. data/README.markdown +76 -0
  4. data/bin/resque +67 -0
  5. data/bin/resque-web +23 -0
  6. data/docs/HOOKS.md +132 -0
  7. data/docs/PLUGINS.md +93 -0
  8. data/lib/resque/errors.rb +13 -0
  9. data/lib/resque/failure/base.rb +64 -0
  10. data/lib/resque/failure/hoptoad.rb +133 -0
  11. data/lib/resque/failure/mongo.rb +45 -0
  12. data/lib/resque/failure/multiple.rb +54 -0
  13. data/lib/resque/failure/redis.rb +46 -0
  14. data/lib/resque/failure.rb +70 -0
  15. data/lib/resque/helpers.rb +75 -0
  16. data/lib/resque/job.rb +215 -0
  17. data/lib/resque/plugin.rb +51 -0
  18. data/lib/resque/server/public/favicon.ico +0 -0
  19. data/lib/resque/server/public/idle.png +0 -0
  20. data/lib/resque/server/public/jquery-1.3.2.min.js +19 -0
  21. data/lib/resque/server/public/jquery.relatize_date.js +95 -0
  22. data/lib/resque/server/public/poll.png +0 -0
  23. data/lib/resque/server/public/ranger.js +73 -0
  24. data/lib/resque/server/public/reset.css +48 -0
  25. data/lib/resque/server/public/style.css +92 -0
  26. data/lib/resque/server/public/working.png +0 -0
  27. data/lib/resque/server/test_helper.rb +19 -0
  28. data/lib/resque/server/views/error.erb +1 -0
  29. data/lib/resque/server/views/failed.erb +65 -0
  30. data/lib/resque/server/views/key_sets.erb +19 -0
  31. data/lib/resque/server/views/key_string.erb +11 -0
  32. data/lib/resque/server/views/layout.erb +42 -0
  33. data/lib/resque/server/views/next_more.erb +10 -0
  34. data/lib/resque/server/views/overview.erb +4 -0
  35. data/lib/resque/server/views/queues.erb +69 -0
  36. data/lib/resque/server/views/stats.erb +73 -0
  37. data/lib/resque/server/views/workers.erb +109 -0
  38. data/lib/resque/server/views/working.erb +72 -0
  39. data/lib/resque/server.rb +268 -0
  40. data/lib/resque/stat.rb +54 -0
  41. data/lib/resque/tasks.rb +42 -0
  42. data/lib/resque/version.rb +3 -0
  43. data/lib/resque/worker.rb +497 -0
  44. data/lib/resque.rb +417 -0
  45. data/lib/tasks/resque.rake +2 -0
  46. metadata +148 -0
@@ -0,0 +1,64 @@
1
+ module Resque
2
+ module Failure
3
+ # All Failure classes are expected to subclass Base.
4
+ #
5
+ # When a job fails, a new instance of your Failure backend is created
6
+ # and #save is called.
7
+ class Base
8
+ # The exception object raised by the failed job
9
+ attr_accessor :exception
10
+
11
+ # The worker object who detected the failure
12
+ attr_accessor :worker
13
+
14
+ # The string name of the queue from which the failed job was pulled
15
+ attr_accessor :queue
16
+
17
+ # The payload object associated with the failed job
18
+ attr_accessor :payload
19
+
20
+ def initialize(exception, worker, queue, payload)
21
+ @exception = exception
22
+ @worker = worker
23
+ @queue = queue
24
+ @payload = payload
25
+ end
26
+
27
+ # When a job fails, a new instance of your Failure backend is created
28
+ # and #save is called.
29
+ #
30
+ # This is where you POST or PUT or whatever to your Failure service.
31
+ def save
32
+ end
33
+
34
+ # The number of failures.
35
+ def self.count
36
+ 0
37
+ end
38
+
39
+ # Returns a paginated array of failure objects.
40
+ def self.all(start = 0, count = 1)
41
+ []
42
+ end
43
+
44
+ # A URL where someone can go to view failures.
45
+ def self.url
46
+ end
47
+
48
+ # Clear all failure objects
49
+ def self.clear
50
+ end
51
+
52
+ def self.requeue(index)
53
+ end
54
+
55
+ def self.remove(index)
56
+ end
57
+
58
+ # Logging!
59
+ def log(message)
60
+ @worker.log(message)
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,133 @@
1
+ require 'net/https'
2
+ require 'builder'
3
+ require 'uri'
4
+
5
+ module Resque
6
+ module Failure
7
+ # A Failure backend that sends exceptions raised by jobs to Hoptoad.
8
+ #
9
+ # To use it, put this code in an initializer, Rake task, or wherever:
10
+ #
11
+ # require 'resque/failure/hoptoad'
12
+ #
13
+ # Resque::Failure::Hoptoad.configure do |config|
14
+ # config.api_key = 'blah'
15
+ # config.secure = true
16
+ #
17
+ # # optional proxy support
18
+ # config.proxy_host = 'x.y.z.t'
19
+ # config.proxy_port = 8080
20
+ #
21
+ # # server env support, defaults to RAILS_ENV or RACK_ENV
22
+ # config.server_environment = "test"
23
+ # end
24
+ class Hoptoad < Base
25
+ # From the hoptoad plugin
26
+ INPUT_FORMAT = /^([^:]+):(\d+)(?::in `([^']+)')?$/
27
+
28
+ class << self
29
+ attr_accessor :secure, :api_key, :proxy_host, :proxy_port
30
+ attr_accessor :server_environment
31
+ end
32
+
33
+ def self.count
34
+ # We can't get the total # of errors from Hoptoad so we fake it
35
+ # by asking Resque how many errors it has seen.
36
+ Stat[:failed]
37
+ end
38
+
39
+ def self.configure
40
+ yield self
41
+ Resque::Failure.backend = self
42
+ end
43
+
44
+ def save
45
+ http = use_ssl? ? :https : :http
46
+ url = URI.parse("#{http}://hoptoadapp.com/notifier_api/v2/notices")
47
+
48
+ request = Net::HTTP::Proxy(self.class.proxy_host, self.class.proxy_port)
49
+ http = request.new(url.host, url.port)
50
+ headers = {
51
+ 'Content-type' => 'text/xml',
52
+ 'Accept' => 'text/xml, application/xml'
53
+ }
54
+
55
+ http.read_timeout = 5 # seconds
56
+ http.open_timeout = 2 # seconds
57
+
58
+ http.use_ssl = use_ssl?
59
+
60
+ begin
61
+ response = http.post(url.path, xml, headers)
62
+ rescue TimeoutError => e
63
+ log "Timeout while contacting the Hoptoad server."
64
+ end
65
+
66
+ case response
67
+ when Net::HTTPSuccess then
68
+ log "Hoptoad Success: #{response.class}"
69
+ else
70
+ body = response.body if response.respond_to? :body
71
+ log "Hoptoad Failure: #{response.class}\n#{body}"
72
+ end
73
+ end
74
+
75
+ def xml
76
+ x = Builder::XmlMarkup.new
77
+ x.instruct!
78
+ x.notice :version=>"2.0" do
79
+ x.tag! "api-key", api_key
80
+ x.notifier do
81
+ x.name "Resqueue"
82
+ x.version "0.1"
83
+ x.url "http://github.com/defunkt/resque"
84
+ end
85
+ x.error do
86
+ x.tag! "class", exception.class.name
87
+ x.message "#{exception.class.name}: #{exception.message}"
88
+ x.backtrace do
89
+ fill_in_backtrace_lines(x)
90
+ end
91
+ end
92
+ x.request do
93
+ x.url queue.to_s
94
+ x.component worker.to_s
95
+ x.params do
96
+ x.var :key=>"payload_class" do
97
+ x.text! payload["class"].to_s
98
+ end
99
+ x.var :key=>"payload_args" do
100
+ x.text! payload["args"].to_s
101
+ end
102
+ end
103
+ end
104
+ x.tag!("server-environment") do
105
+ x.tag!("project-root", "RAILS_ROOT")
106
+ x.tag!("environment-name",server_environment)
107
+ end
108
+
109
+ end
110
+ end
111
+
112
+ def fill_in_backtrace_lines(x)
113
+ Array(exception.backtrace).each do |unparsed_line|
114
+ _, file, number, method = unparsed_line.match(INPUT_FORMAT).to_a
115
+ x.line :file => file,:number => number
116
+ end
117
+ end
118
+
119
+ def use_ssl?
120
+ self.class.secure
121
+ end
122
+
123
+ def api_key
124
+ self.class.api_key
125
+ end
126
+
127
+ def server_environment
128
+ return self.class.server_environment if self.class.server_environment
129
+ defined?(RAILS_ENV) ? RAILS_ENV : (ENV['RACK_ENV'] || 'development')
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,45 @@
1
+ module Resque
2
+ module Failure
3
+ # A Failure backend that stores exceptions in Mongo. Very simple but
4
+ # works out of the box, along with support in the Resque web app.
5
+ class Mongo < Base
6
+ def save
7
+ data = {
8
+ :failed_at => Time.now.strftime("%Y/%m/%d %H:%M:%S"),
9
+ :payload => payload,
10
+ :exception => exception.class.to_s,
11
+ :error => exception.to_s,
12
+ :backtrace => Array(exception.backtrace),
13
+ :worker => worker.to_s,
14
+ :queue => queue
15
+ }
16
+ Resque.mongo_failures << data
17
+ end
18
+
19
+ def self.count
20
+ Resque.mongo_failures.count
21
+ end
22
+
23
+ def self.all(start = 0, count = 1)
24
+ all_failures = Resque.mongo_failures.find().skip(start.to_i).limit(count.to_i).to_a
25
+ all_failures.size == 1 ? all_failures.first : all_failures
26
+ end
27
+
28
+ def self.clear
29
+ Resque.mongo_failures.remove
30
+ end
31
+
32
+ def self.requeue(index)
33
+ item = all(index)
34
+ item['retried_at'] = Time.now.strftime("%Y/%m/%d %H:%M:%S")
35
+ Resque.mongo_failures.update({ :_id => item['_id']}, item)
36
+ Job.create(item['queue'], item['payload']['class'], *item['payload']['args'])
37
+ end
38
+
39
+ def self.remove(index)
40
+ item = all(index)
41
+ Resque.mongo_failures.remove(:_id => item['_id'])
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,54 @@
1
+ module Resque
2
+ module Failure
3
+ # A Failure backend that uses multiple backends
4
+ # delegates all queries to the first backend
5
+ class Multiple < Base
6
+
7
+ class << self
8
+ attr_accessor :classes
9
+ end
10
+
11
+ def self.configure
12
+ yield self
13
+ Resque::Failure.backend = self
14
+ end
15
+
16
+ def initialize(*args)
17
+ super
18
+ @backends = self.class.classes.map {|klass| klass.new(*args)}
19
+ end
20
+
21
+ def save
22
+ @backends.each(&:save)
23
+ end
24
+
25
+ # The number of failures.
26
+ def self.count
27
+ classes.first.count
28
+ end
29
+
30
+ # Returns a paginated array of failure objects.
31
+ def self.all(start = 0, count = 1)
32
+ classes.first.all(start,count)
33
+ end
34
+
35
+ # A URL where someone can go to view failures.
36
+ def self.url
37
+ classes.first.url
38
+ end
39
+
40
+ # Clear all failure objects
41
+ def self.clear
42
+ classes.first.clear
43
+ end
44
+
45
+ def self.requeue(*args)
46
+ classes.first.requeue(*args)
47
+ end
48
+
49
+ def self.remove(index)
50
+ classes.each { |klass| klass.remove(index) }
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,46 @@
1
+ module Resque
2
+ module Failure
3
+ # A Failure backend that stores exceptions in Mongo. Very simple but
4
+ # works out of the box, along with support in the Resque web app.
5
+ class Redis < Base
6
+ def save
7
+ data = {
8
+ :failed_at => Time.now.strftime("%Y/%m/%d %H:%M:%S"),
9
+ :payload => payload,
10
+ :exception => exception.class.to_s,
11
+ :error => exception.to_s,
12
+ :backtrace => Array(exception.backtrace),
13
+ :worker => worker.to_s,
14
+ :queue => queue
15
+ }
16
+ Resque.mongo_failures << data
17
+ end
18
+
19
+ def self.count
20
+ Resque.mongo_failures.count
21
+ end
22
+
23
+ def self.all(start = 0, count = 1)
24
+ all_failures = Resque.mongo_failures.find().sort([:natural, :desc]).skip(start).limit(count).to_a
25
+ # all_failures.size == 1 ? all_failures.first : all_failures
26
+ end
27
+
28
+ def self.clear
29
+ Resque.mongo_failures.remove
30
+ end
31
+
32
+ def self.requeue(index)
33
+ item = all(index)
34
+ item['retried_at'] = Time.now.strftime("%Y/%m/%d %H:%M:%S")
35
+ Resque.redis.lset(:failed, index, Resque.encode(item))
36
+ Job.create(item['queue'], item['payload']['class'], *item['payload']['args'])
37
+ end
38
+
39
+ def self.remove(index)
40
+ id = rand(0xffffff)
41
+ Resque.redis.lset(:failed, index, id)
42
+ Resque.redis.lrem(:failed, 1, id)
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,70 @@
1
+ module Resque
2
+ # The Failure module provides an interface for working with different
3
+ # failure backends.
4
+ #
5
+ # You can use it to query the failure backend without knowing which specific
6
+ # backend is being used. For instance, the Resque web app uses it to display
7
+ # stats and other information.
8
+ module Failure
9
+ # Creates a new failure, which is delegated to the appropriate backend.
10
+ #
11
+ # Expects a hash with the following keys:
12
+ # :exception - The Exception object
13
+ # :worker - The Worker object who is reporting the failure
14
+ # :queue - The string name of the queue from which the job was pulled
15
+ # :payload - The job's payload
16
+ def self.create(options = {})
17
+ backend.new(*options.values_at(:exception, :worker, :queue, :payload)).save
18
+ end
19
+
20
+ #
21
+ # Sets the current backend. Expects a class descendent of
22
+ # `Resque::Failure::Base`.
23
+ #
24
+ # Example use:
25
+ # require 'resque/failure/hoptoad'
26
+ # Resque::Failure.backend = Resque::Failure::Hoptoad
27
+ def self.backend=(backend)
28
+ @backend = backend
29
+ end
30
+
31
+ # Returns the current backend class. If none has been set, falls
32
+ # back to `Resque::Failure::Redis`
33
+ def self.backend
34
+ return @backend if @backend
35
+ require 'resque/failure/mongo'
36
+ @backend = Failure::Mongo
37
+ end
38
+
39
+ # Returns the int count of how many failures we have seen.
40
+ def self.count
41
+ backend.count
42
+ end
43
+
44
+ # Returns an array of all the failures, paginated.
45
+ #
46
+ # `start` is the int of the first item in the page, `count` is the
47
+ # number of items to return.
48
+ def self.all(start = 0, count = 1)
49
+ backend.all(start, count)
50
+ end
51
+
52
+ # The string url of the backend's web interface, if any.
53
+ def self.url
54
+ backend.url
55
+ end
56
+
57
+ # Clear all failure jobs
58
+ def self.clear
59
+ backend.clear
60
+ end
61
+
62
+ def self.requeue(index)
63
+ backend.requeue(index)
64
+ end
65
+
66
+ def self.remove(index)
67
+ backend.remove(index)
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,75 @@
1
+ module Resque
2
+ # Methods used by various classes in Resque.
3
+ module Helpers
4
+ class DecodeException < StandardError; end
5
+
6
+ # Direct access to the Redis instance.
7
+ #def mongo
8
+ # Resque.mongo
9
+ #end
10
+
11
+ def mongo_workers
12
+ Resque.mongo_workers
13
+ end
14
+
15
+ def mongo_stats
16
+ Resque.mongo_stats
17
+ end
18
+
19
+ # Given a Ruby object, returns a string suitable for storage in a
20
+ # queue.
21
+ def encode(object)
22
+ if defined? Yajl
23
+ Yajl::Encoder.encode(object)
24
+ else
25
+ object.to_json
26
+ end
27
+ end
28
+
29
+ # Given a string, returns a Ruby object.
30
+ def decode(object)
31
+ return unless object
32
+
33
+ if defined? Yajl
34
+ begin
35
+ Yajl::Parser.parse(object, :check_utf8 => false)
36
+ rescue Yajl::ParseError => e
37
+ raise DecodeException, e
38
+ end
39
+ else
40
+ begin
41
+ JSON.parse(object)
42
+ rescue JSON::ParserError => e
43
+ raise DecodeException, e
44
+ end
45
+ end
46
+ end
47
+
48
+ # Given a word with dashes, returns a camel cased version of it.
49
+ #
50
+ # classify('job-name') # => 'JobName'
51
+ def classify(dashed_word)
52
+ dashed_word.split('-').each { |part| part[0] = part[0].chr.upcase }.join
53
+ end
54
+
55
+ # Given a camel cased word, returns the constant it represents
56
+ #
57
+ # constantize('JobName') # => JobName
58
+ def constantize(camel_cased_word)
59
+ camel_cased_word = camel_cased_word.to_s
60
+
61
+ if camel_cased_word.include?('-')
62
+ camel_cased_word = classify(camel_cased_word)
63
+ end
64
+
65
+ names = camel_cased_word.split('::')
66
+ names.shift if names.empty? || names.first.empty?
67
+
68
+ constant = Object
69
+ names.each do |name|
70
+ constant = constant.const_get(name) || constant.const_missing(name)
71
+ end
72
+ constant
73
+ end
74
+ end
75
+ end
data/lib/resque/job.rb ADDED
@@ -0,0 +1,215 @@
1
+ module Resque
2
+ # A Resque::Job represents a unit of work. Each job lives on a
3
+ # single queue and has an associated payload object. The payload
4
+ # is a hash with two attributes: `class` and `args`. The `class` is
5
+ # the name of the Ruby class which should be used to run the
6
+ # job. The `args` are an array of arguments which should be passed
7
+ # to the Ruby class's `perform` class-level method.
8
+ #
9
+ # You can manually run a job using this code:
10
+ #
11
+ # job = Resque::Job.reserve(:high)
12
+ # klass = Resque::Job.constantize(job.payload['class'])
13
+ # klass.perform(*job.payload['args'])
14
+ class Job
15
+ include Helpers
16
+ extend Helpers
17
+
18
+ # Raise Resque::Job::DontPerform from a before_perform hook to
19
+ # abort the job.
20
+ DontPerform = Class.new(StandardError)
21
+
22
+ # The worker object which is currently processing this job.
23
+ attr_accessor :worker
24
+
25
+ # The name of the queue from which this job was pulled (or is to be
26
+ # placed)
27
+ attr_reader :queue
28
+
29
+ # This job's associated payload object.
30
+ attr_reader :payload
31
+
32
+ def initialize(queue, payload)
33
+ @queue = queue
34
+ @payload = payload
35
+ end
36
+
37
+ # Creates a job by placing it on a queue. Expects a string queue
38
+ # name, a string class name, and an optional array of arguments to
39
+ # pass to the class' `perform` method.
40
+ #
41
+ # Raises an exception if no queue or class is given.
42
+ def self.create(queue, klass, *args)
43
+ Resque.validate(klass, queue)
44
+
45
+ item = { :class => klass.to_s, :args => args}
46
+
47
+ #are we trying to put a non-delayed job into a delayed queue?
48
+ if Resque.queue_allows_delayed(queue)
49
+ if Resque.allows_delayed_jobs(klass)
50
+ if args[0].is_a?(Hash) && args[0].has_key?(:delay_until)
51
+ item[:delay_until] = args[0][:delay_until]
52
+ else
53
+ raise QueueError.new 'trying to insert delayed job without delay_until'
54
+ end
55
+ else
56
+ raise QueueError.new 'trying to insert non-delayed job into delayed queue'
57
+ end
58
+ else
59
+ if Resque.allows_delayed_jobs(klass)
60
+ raise QueueError.new 'trying to insert a delayed job into a non-delayed queue'
61
+ end
62
+ end
63
+
64
+ if Resque.inline?
65
+ constantize(klass).perform(*decode(encode(args)))
66
+ else
67
+ Resque.push(queue, item)
68
+ end
69
+ end
70
+
71
+ # Removes a job from a queue. Expects a string queue name, a
72
+ # string class name, and, optionally, args.
73
+ #
74
+ # Returns the number of jobs destroyed.
75
+ #
76
+ # If no args are provided, it will remove all jobs of the class
77
+ # provided.
78
+ #
79
+ # That is, for these two jobs:
80
+ #
81
+ # { 'class' => 'UpdateGraph', 'args' => ['defunkt'] }
82
+ # { 'class' => 'UpdateGraph', 'args' => ['mojombo'] }
83
+ #
84
+ # The following call will remove both:
85
+ #
86
+ # Resque::Job.destroy(queue, 'UpdateGraph')
87
+ #
88
+ # Whereas specifying args will only remove the 2nd job:
89
+ #
90
+ # Resque::Job.destroy(queue, 'UpdateGraph', 'mojombo')
91
+ #
92
+ # This method can be potentially very slow and memory intensive,
93
+ # depending on the size of your queue, as it loads all jobs into
94
+ # a Ruby array before processing.
95
+ def self.destroy(queue, klass, *args)
96
+ collection = Resque.collection_for_queue(queue)
97
+ selector = {'class' => klass.to_s}
98
+ selector['args'] = args unless args.empty?
99
+ destroyed = collection.find(selector).count
100
+ collection.remove(selector, :safe => true)
101
+ destroyed
102
+ end
103
+
104
+ # Given a string queue name, returns an instance of Resque::Job
105
+ # if any jobs are available. If not, returns nil.
106
+ def self.reserve(queue)
107
+ return unless payload = Resque.pop(queue)
108
+ new(queue, payload)
109
+ end
110
+
111
+ # Attempts to perform the work represented by this job instance.
112
+ # Calls #perform on the class given in the payload with the
113
+ # arguments given in the payload.
114
+ def perform
115
+ job = payload_class
116
+ job_args = args || []
117
+ job_was_performed = false
118
+
119
+ before_hooks = Plugin.before_hooks(job)
120
+ around_hooks = Plugin.around_hooks(job)
121
+ after_hooks = Plugin.after_hooks(job)
122
+ failure_hooks = Plugin.failure_hooks(job)
123
+
124
+ begin
125
+ # Execute before_perform hook. Abort the job gracefully if
126
+ # Resque::DontPerform is raised.
127
+ begin
128
+ before_hooks.each do |hook|
129
+ job.send(hook, *job_args)
130
+ end
131
+ rescue DontPerform
132
+ return false
133
+ end
134
+
135
+ # Execute the job. Do it in an around_perform hook if available.
136
+ if around_hooks.empty?
137
+ job.perform(*job_args)
138
+ job_was_performed = true
139
+ else
140
+ # We want to nest all around_perform plugins, with the last one
141
+ # finally calling perform
142
+ stack = around_hooks.reverse.inject(nil) do |last_hook, hook|
143
+ if last_hook
144
+ lambda do
145
+ job.send(hook, *job_args) { last_hook.call }
146
+ end
147
+ else
148
+ lambda do
149
+ job.send(hook, *job_args) do
150
+ result = job.perform(*job_args)
151
+ job_was_performed = true
152
+ result
153
+ end
154
+ end
155
+ end
156
+ end
157
+ stack.call
158
+ end
159
+
160
+ # Execute after_perform hook
161
+ after_hooks.each do |hook|
162
+ job.send(hook, *job_args)
163
+ end
164
+
165
+ # Return true if the job was performed
166
+ return job_was_performed
167
+
168
+ # If an exception occurs during the job execution, look for an
169
+ # on_failure hook then re-raise.
170
+ rescue Object => e
171
+ failure_hooks.each { |hook| job.send(hook, e, *job_args) }
172
+ raise e
173
+ end
174
+ end
175
+
176
+ # Returns the actual class constant represented in this job's payload.
177
+ def payload_class
178
+ @payload_class ||= constantize(@payload['class'])
179
+ end
180
+
181
+ # Returns an array of args represented in this job's payload.
182
+ def args
183
+ @payload['args']
184
+ end
185
+
186
+ # Given an exception object, hands off the needed parameters to
187
+ # the Failure module.
188
+ def fail(exception)
189
+ Failure.create \
190
+ :payload => payload,
191
+ :exception => exception,
192
+ :worker => worker,
193
+ :queue => queue
194
+ end
195
+
196
+ # Creates an identical job, essentially placing this job back on
197
+ # the queue.
198
+ def recreate
199
+ self.class.create(queue, payload_class, *args)
200
+ end
201
+
202
+ # String representation
203
+ def inspect
204
+ obj = @payload
205
+ "(Job{%s} | %s | %s)" % [ @queue, obj['class'], obj['args'].inspect ]
206
+ end
207
+
208
+ # Equality
209
+ def ==(other)
210
+ queue == other.queue &&
211
+ payload_class == other.payload_class &&
212
+ args == other.args
213
+ end
214
+ end
215
+ end