mongo-resque 1.17.1

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.
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