resque_sqs 1.25.2

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 (72) hide show
  1. checksums.yaml +7 -0
  2. data/HISTORY.md +467 -0
  3. data/LICENSE +20 -0
  4. data/README.markdown +866 -0
  5. data/Rakefile +70 -0
  6. data/bin/resque-sqs +81 -0
  7. data/bin/resque-sqs-web +27 -0
  8. data/lib/resque_sqs/errors.rb +13 -0
  9. data/lib/resque_sqs/failure/airbrake.rb +33 -0
  10. data/lib/resque_sqs/failure/base.rb +73 -0
  11. data/lib/resque_sqs/failure/multiple.rb +59 -0
  12. data/lib/resque_sqs/failure/redis.rb +108 -0
  13. data/lib/resque_sqs/failure/redis_multi_queue.rb +89 -0
  14. data/lib/resque_sqs/failure.rb +113 -0
  15. data/lib/resque_sqs/helpers.rb +107 -0
  16. data/lib/resque_sqs/job.rb +346 -0
  17. data/lib/resque_sqs/log_formatters/quiet_formatter.rb +7 -0
  18. data/lib/resque_sqs/log_formatters/verbose_formatter.rb +7 -0
  19. data/lib/resque_sqs/log_formatters/very_verbose_formatter.rb +8 -0
  20. data/lib/resque_sqs/logging.rb +18 -0
  21. data/lib/resque_sqs/plugin.rb +66 -0
  22. data/lib/resque_sqs/server/helpers.rb +52 -0
  23. data/lib/resque_sqs/server/public/favicon.ico +0 -0
  24. data/lib/resque_sqs/server/public/idle.png +0 -0
  25. data/lib/resque_sqs/server/public/jquery-1.3.2.min.js +19 -0
  26. data/lib/resque_sqs/server/public/jquery.relatize_date.js +95 -0
  27. data/lib/resque_sqs/server/public/poll.png +0 -0
  28. data/lib/resque_sqs/server/public/ranger.js +78 -0
  29. data/lib/resque_sqs/server/public/reset.css +44 -0
  30. data/lib/resque_sqs/server/public/style.css +91 -0
  31. data/lib/resque_sqs/server/public/working.png +0 -0
  32. data/lib/resque_sqs/server/test_helper.rb +19 -0
  33. data/lib/resque_sqs/server/views/error.erb +1 -0
  34. data/lib/resque_sqs/server/views/failed.erb +29 -0
  35. data/lib/resque_sqs/server/views/failed_job.erb +50 -0
  36. data/lib/resque_sqs/server/views/failed_queues_overview.erb +24 -0
  37. data/lib/resque_sqs/server/views/key_sets.erb +19 -0
  38. data/lib/resque_sqs/server/views/key_string.erb +11 -0
  39. data/lib/resque_sqs/server/views/layout.erb +44 -0
  40. data/lib/resque_sqs/server/views/next_more.erb +22 -0
  41. data/lib/resque_sqs/server/views/overview.erb +4 -0
  42. data/lib/resque_sqs/server/views/queues.erb +58 -0
  43. data/lib/resque_sqs/server/views/stats.erb +62 -0
  44. data/lib/resque_sqs/server/views/workers.erb +109 -0
  45. data/lib/resque_sqs/server/views/working.erb +72 -0
  46. data/lib/resque_sqs/server.rb +271 -0
  47. data/lib/resque_sqs/stat.rb +57 -0
  48. data/lib/resque_sqs/tasks.rb +83 -0
  49. data/lib/resque_sqs/vendor/utf8_util/utf8_util_18.rb +91 -0
  50. data/lib/resque_sqs/vendor/utf8_util/utf8_util_19.rb +5 -0
  51. data/lib/resque_sqs/vendor/utf8_util.rb +20 -0
  52. data/lib/resque_sqs/version.rb +3 -0
  53. data/lib/resque_sqs/worker.rb +779 -0
  54. data/lib/resque_sqs.rb +479 -0
  55. data/lib/tasks/redis_sqs.rake +161 -0
  56. data/lib/tasks/resque_sqs.rake +2 -0
  57. data/test/airbrake_test.rb +27 -0
  58. data/test/failure_base_test.rb +15 -0
  59. data/test/job_hooks_test.rb +465 -0
  60. data/test/job_plugins_test.rb +230 -0
  61. data/test/logging_test.rb +24 -0
  62. data/test/plugin_test.rb +116 -0
  63. data/test/redis-test-cluster.conf +115 -0
  64. data/test/redis-test.conf +115 -0
  65. data/test/resque-web_test.rb +59 -0
  66. data/test/resque_failure_redis_test.rb +19 -0
  67. data/test/resque_hook_test.rb +165 -0
  68. data/test/resque_test.rb +278 -0
  69. data/test/stdout +42 -0
  70. data/test/test_helper.rb +228 -0
  71. data/test/worker_test.rb +1080 -0
  72. metadata +202 -0
@@ -0,0 +1,107 @@
1
+ require 'multi_json'
2
+
3
+ # OkJson won't work because it doesn't serialize symbols
4
+ # in the same way yajl and json do.
5
+ if MultiJson.respond_to?(:adapter)
6
+ raise "Please install the yajl-ruby or json gem" if MultiJson.adapter.to_s == 'MultiJson::Adapters::OkJson'
7
+ elsif MultiJson.respond_to?(:engine)
8
+ raise "Please install the yajl-ruby or json gem" if MultiJson.engine.to_s == 'MultiJson::Engines::OkJson'
9
+ end
10
+
11
+ module ResqueSqs
12
+ # Methods used by various classes in ResqueSqs.
13
+ module Helpers
14
+ def self.extended(parent_class)
15
+ warn("ResqueSqs::Helpers will be gone with no replacement in Resque 2.0.0.")
16
+ end
17
+
18
+ def self.included(parent_class)
19
+ warn("ResqueSqs::Helpers will be gone with no replacement in Resque 2.0.0.")
20
+ end
21
+
22
+ class DecodeException < StandardError; end
23
+
24
+ # Direct access to the Redis instance.
25
+ def redis
26
+ # No infinite recursions, please.
27
+ # Some external libraries depend on ResqueSqs::Helpers being mixed into
28
+ # Resque, but this method causes recursions. If we have a super method,
29
+ # assume it is canonical. (see #1150)
30
+ return super if defined?(super)
31
+
32
+ ResqueSqs.redis
33
+ end
34
+
35
+ # Given a Ruby object, returns a string suitable for storage in a
36
+ # queue.
37
+ def encode(object)
38
+ if MultiJson.respond_to?(:dump) && MultiJson.respond_to?(:load)
39
+ MultiJson.dump object
40
+ else
41
+ MultiJson.encode object
42
+ end
43
+ end
44
+
45
+ # Given a string, returns a Ruby object.
46
+ def decode(object)
47
+ return unless object
48
+
49
+ begin
50
+ if MultiJson.respond_to?(:dump) && MultiJson.respond_to?(:load)
51
+ MultiJson.load object
52
+ else
53
+ MultiJson.decode object
54
+ end
55
+ rescue ::MultiJson::DecodeError => e
56
+ raise DecodeException, e.message, e.backtrace
57
+ end
58
+ end
59
+
60
+ # Given a word with dashes, returns a camel cased version of it.
61
+ #
62
+ # classify('job-name') # => 'JobName'
63
+ def classify(dashed_word)
64
+ dashed_word.split('-').each { |part| part[0] = part[0].chr.upcase }.join
65
+ end
66
+
67
+ # Tries to find a constant with the name specified in the argument string:
68
+ #
69
+ # constantize("Module") # => Module
70
+ # constantize("Test::Unit") # => Test::Unit
71
+ #
72
+ # The name is assumed to be the one of a top-level constant, no matter
73
+ # whether it starts with "::" or not. No lexical context is taken into
74
+ # account:
75
+ #
76
+ # C = 'outside'
77
+ # module M
78
+ # C = 'inside'
79
+ # C # => 'inside'
80
+ # constantize("C") # => 'outside', same as ::C
81
+ # end
82
+ #
83
+ # NameError is raised when the constant is unknown.
84
+ def constantize(camel_cased_word)
85
+ camel_cased_word = camel_cased_word.to_s
86
+
87
+ if camel_cased_word.include?('-')
88
+ camel_cased_word = classify(camel_cased_word)
89
+ end
90
+
91
+ names = camel_cased_word.split('::')
92
+ names.shift if names.empty? || names.first.empty?
93
+
94
+ constant = Object
95
+ names.each do |name|
96
+ args = Module.method(:const_get).arity != 1 ? [false] : []
97
+
98
+ if constant.const_defined?(name, *args)
99
+ constant = constant.const_get(name)
100
+ else
101
+ constant = constant.const_missing(name)
102
+ end
103
+ end
104
+ constant
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,346 @@
1
+ module ResqueSqs
2
+ # A ResqueSqs::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 = ResqueSqs::Job.reserve(:high)
12
+ # klass = ResqueSqs::Job.constantize(job.payload['class'])
13
+ # klass.perform(*job.payload['args'])
14
+ class Job
15
+ def redis
16
+ ResqueSqs.redis
17
+ end
18
+
19
+ def self.redis
20
+ ResqueSqs.redis
21
+ end
22
+
23
+ # Given a Ruby object, returns a string suitable for storage in a
24
+ # queue.
25
+ def encode(object)
26
+ if MultiJson.respond_to?(:dump) && MultiJson.respond_to?(:load)
27
+ MultiJson.dump object
28
+ else
29
+ MultiJson.encode object
30
+ end
31
+ end
32
+
33
+ # Given a string, returns a Ruby object.
34
+ def decode(object)
35
+ return unless object
36
+
37
+ begin
38
+ if MultiJson.respond_to?(:dump) && MultiJson.respond_to?(:load)
39
+ MultiJson.load object
40
+ else
41
+ MultiJson.decode object
42
+ end
43
+ rescue ::MultiJson::DecodeError => e
44
+ raise DecodeException, e.message, e.backtrace
45
+ end
46
+ end
47
+
48
+ # Given a Ruby object, returns a string suitable for storage in a
49
+ # queue.
50
+ def self.encode(object)
51
+ if MultiJson.respond_to?(:dump) && MultiJson.respond_to?(:load)
52
+ MultiJson.dump object
53
+ else
54
+ MultiJson.encode object
55
+ end
56
+ end
57
+
58
+ # Given a string, returns a Ruby object.
59
+ def self.decode(object)
60
+ return unless object
61
+
62
+ begin
63
+ if MultiJson.respond_to?(:dump) && MultiJson.respond_to?(:load)
64
+ MultiJson.load object
65
+ else
66
+ MultiJson.decode object
67
+ end
68
+ rescue ::MultiJson::DecodeError => e
69
+ raise DecodeException, e.message, e.backtrace
70
+ end
71
+ end
72
+
73
+ # Given a word with dashes, returns a camel cased version of it.
74
+ #
75
+ # classify('job-name') # => 'JobName'
76
+ def classify(dashed_word)
77
+ dashed_word.split('-').each { |part| part[0] = part[0].chr.upcase }.join
78
+ end
79
+
80
+ # Tries to find a constant with the name specified in the argument string:
81
+ #
82
+ # constantize("Module") # => Module
83
+ # constantize("Test::Unit") # => Test::Unit
84
+ #
85
+ # The name is assumed to be the one of a top-level constant, no matter
86
+ # whether it starts with "::" or not. No lexical context is taken into
87
+ # account:
88
+ #
89
+ # C = 'outside'
90
+ # module M
91
+ # C = 'inside'
92
+ # C # => 'inside'
93
+ # constantize("C") # => 'outside', same as ::C
94
+ # end
95
+ #
96
+ # NameError is raised when the constant is unknown.
97
+ def constantize(camel_cased_word)
98
+ camel_cased_word = camel_cased_word.to_s
99
+
100
+ if camel_cased_word.include?('-')
101
+ camel_cased_word = classify(camel_cased_word)
102
+ end
103
+
104
+ names = camel_cased_word.split('::')
105
+ names.shift if names.empty? || names.first.empty?
106
+
107
+ constant = Object
108
+ names.each do |name|
109
+ args = Module.method(:const_get).arity != 1 ? [false] : []
110
+
111
+ if constant.const_defined?(name, *args)
112
+ constant = constant.const_get(name)
113
+ else
114
+ constant = constant.const_missing(name)
115
+ end
116
+ end
117
+ constant
118
+ end
119
+
120
+ # Raise ResqueSqs::Job::DontPerform from a before_perform hook to
121
+ # abort the job.
122
+ DontPerform = Class.new(StandardError)
123
+
124
+ # The worker object which is currently processing this job.
125
+ attr_accessor :worker
126
+
127
+ # The name of the queue from which this job was pulled (or is to be
128
+ # placed)
129
+ attr_reader :queue
130
+
131
+ # This job's associated payload object.
132
+ attr_reader :payload
133
+
134
+ def initialize(queue, payload)
135
+ @queue = queue
136
+ @payload = payload
137
+ @failure_hooks_ran = false
138
+ end
139
+
140
+ # Creates a job by placing it on a queue. Expects a string queue
141
+ # name, a string class name, and an optional array of arguments to
142
+ # pass to the class' `perform` method.
143
+ #
144
+ # Raises an exception if no queue or class is given.
145
+ def self.create(queue, klass, *args)
146
+ ResqueSqs.validate(klass, queue)
147
+
148
+ if ResqueSqs.inline?
149
+ # Instantiating a ResqueSqs::Job and calling perform on it so callbacks run
150
+ # decode(encode(args)) to ensure that args are normalized in the same manner as a non-inline job
151
+ new(:inline, {'class' => klass, 'args' => decode(encode(args))}).perform
152
+ else
153
+ ResqueSqs.push(queue, :class => klass.to_s, :args => args)
154
+ end
155
+ end
156
+
157
+ # Removes a job from a queue. Expects a string queue name, a
158
+ # string class name, and, optionally, args.
159
+ #
160
+ # Returns the number of jobs destroyed.
161
+ #
162
+ # If no args are provided, it will remove all jobs of the class
163
+ # provided.
164
+ #
165
+ # That is, for these two jobs:
166
+ #
167
+ # { 'class' => 'UpdateGraph', 'args' => ['defunkt'] }
168
+ # { 'class' => 'UpdateGraph', 'args' => ['mojombo'] }
169
+ #
170
+ # The following call will remove both:
171
+ #
172
+ # ResqueSqs::Job.destroy(queue, 'UpdateGraph')
173
+ #
174
+ # Whereas specifying args will only remove the 2nd job:
175
+ #
176
+ # ResqueSqs::Job.destroy(queue, 'UpdateGraph', 'mojombo')
177
+ #
178
+ # This method can be potentially very slow and memory intensive,
179
+ # depending on the size of your queue, as it loads all jobs into
180
+ # a Ruby array before processing.
181
+ def self.destroy(queue, klass, *args)
182
+ klass = klass.to_s
183
+ queue = "queue:#{queue}"
184
+ destroyed = 0
185
+
186
+ if args.empty?
187
+ redis.lrange(queue, 0, -1).each do |string|
188
+ if decode(string)['class'] == klass
189
+ destroyed += redis.lrem(queue, 0, string).to_i
190
+ end
191
+ end
192
+ else
193
+ destroyed += redis.lrem(queue, 0, encode(:class => klass, :args => args))
194
+ end
195
+
196
+ destroyed
197
+ end
198
+
199
+ # Given a string queue name, returns an instance of ResqueSqs::Job
200
+ # if any jobs are available. If not, returns nil.
201
+ def self.reserve(queue)
202
+ return unless payload = ResqueSqs.pop(queue)
203
+ new(queue, payload)
204
+ end
205
+
206
+ # Attempts to perform the work represented by this job instance.
207
+ # Calls #perform on the class given in the payload with the
208
+ # arguments given in the payload.
209
+ def perform
210
+ job = payload_class
211
+ job_args = args || []
212
+ job_was_performed = false
213
+
214
+ begin
215
+ # Execute before_perform hook. Abort the job gracefully if
216
+ # ResqueSqs::DontPerform is raised.
217
+ begin
218
+ before_hooks.each do |hook|
219
+ job.send(hook, *job_args)
220
+ end
221
+ rescue DontPerform
222
+ return false
223
+ end
224
+
225
+ # Execute the job. Do it in an around_perform hook if available.
226
+ if around_hooks.empty?
227
+ job.perform(*job_args)
228
+ job_was_performed = true
229
+ else
230
+ # We want to nest all around_perform plugins, with the last one
231
+ # finally calling perform
232
+ stack = around_hooks.reverse.inject(nil) do |last_hook, hook|
233
+ if last_hook
234
+ lambda do
235
+ job.send(hook, *job_args) { last_hook.call }
236
+ end
237
+ else
238
+ lambda do
239
+ job.send(hook, *job_args) do
240
+ result = job.perform(*job_args)
241
+ job_was_performed = true
242
+ result
243
+ end
244
+ end
245
+ end
246
+ end
247
+ stack.call
248
+ end
249
+
250
+ # Execute after_perform hook
251
+ after_hooks.each do |hook|
252
+ job.send(hook, *job_args)
253
+ end
254
+
255
+ # Return true if the job was performed
256
+ return job_was_performed
257
+
258
+ # If an exception occurs during the job execution, look for an
259
+ # on_failure hook then re-raise.
260
+ rescue Object => e
261
+ run_failure_hooks(e)
262
+ raise e
263
+ end
264
+ end
265
+
266
+ # Returns the actual class constant represented in this job's payload.
267
+ def payload_class
268
+ @payload_class ||= constantize(@payload['class'])
269
+ end
270
+
271
+ # Returns the payload class as a string without raising NameError
272
+ def payload_class_name
273
+ payload_class.to_s
274
+ rescue NameError
275
+ 'No Name'
276
+ end
277
+
278
+ def has_payload_class?
279
+ payload_class != Object
280
+ rescue NameError
281
+ false
282
+ end
283
+
284
+ # Returns an array of args represented in this job's payload.
285
+ def args
286
+ @payload['args']
287
+ end
288
+
289
+ # Given an exception object, hands off the needed parameters to
290
+ # the Failure module.
291
+ def fail(exception)
292
+ run_failure_hooks(exception)
293
+ Failure.create \
294
+ :payload => payload,
295
+ :exception => exception,
296
+ :worker => worker,
297
+ :queue => queue
298
+ end
299
+
300
+ # Creates an identical job, essentially placing this job back on
301
+ # the queue.
302
+ def recreate
303
+ self.class.create(queue, payload_class, *args)
304
+ end
305
+
306
+ # String representation
307
+ def inspect
308
+ obj = @payload
309
+ "(Job{%s} | %s | %s)" % [ @queue, obj['class'], obj['args'].inspect ]
310
+ end
311
+
312
+ # Equality
313
+ def ==(other)
314
+ queue == other.queue &&
315
+ payload_class == other.payload_class &&
316
+ args == other.args
317
+ end
318
+
319
+ def before_hooks
320
+ @before_hooks ||= Plugin.before_hooks(payload_class)
321
+ end
322
+
323
+ def around_hooks
324
+ @around_hooks ||= Plugin.around_hooks(payload_class)
325
+ end
326
+
327
+ def after_hooks
328
+ @after_hooks ||= Plugin.after_hooks(payload_class)
329
+ end
330
+
331
+ def failure_hooks
332
+ @failure_hooks ||= Plugin.failure_hooks(payload_class)
333
+ end
334
+
335
+ def run_failure_hooks(exception)
336
+ begin
337
+ job_args = args || []
338
+ if has_payload_class?
339
+ failure_hooks.each { |hook| payload_class.send(hook, exception, *job_args) } unless @failure_hooks_ran
340
+ end
341
+ ensure
342
+ @failure_hooks_ran = true
343
+ end
344
+ end
345
+ end
346
+ end
@@ -0,0 +1,7 @@
1
+ module ResqueSqs
2
+ class QuietFormatter
3
+ def call(serverity, datetime, progname, msg)
4
+ ""
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module ResqueSqs
2
+ class VerboseFormatter
3
+ def call(serverity, datetime, progname, msg)
4
+ "*** #{msg}\n"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,8 @@
1
+ module ResqueSqs
2
+ class VeryVerboseFormatter
3
+ def call(serverity, datetime, progname, msg)
4
+ time = Time.now.strftime('%H:%M:%S %Y-%m-%d')
5
+ "** [#{time}] #$$: #{msg}\n"
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,18 @@
1
+ module ResqueSqs
2
+ # Include this module in classes you wish to have logging facilities
3
+ module Logging
4
+ module_function
5
+
6
+ # Thunk to the logger's own log method (if configured)
7
+ def self.log(severity, message)
8
+ ResqueSqs.logger.__send__(severity, message) if ResqueSqs.logger
9
+ end
10
+
11
+ # Log level aliases
12
+ def debug(message); Logging.log :debug, message; end
13
+ def info(message); Logging.log :info, message; end
14
+ def warn(message); Logging.log :warn, message; end
15
+ def error(message); Logging.log :error, message; end
16
+ def fatal(message); Logging.log :fatal, message; end
17
+ end
18
+ end
@@ -0,0 +1,66 @@
1
+ module ResqueSqs
2
+ module Plugin
3
+ extend self
4
+
5
+ LintError = Class.new(RuntimeError)
6
+
7
+ # Ensure that your plugin conforms to good hook naming conventions.
8
+ #
9
+ # ResqueSqs::Plugin.lint(MyResquePlugin)
10
+ def lint(plugin)
11
+ hooks = before_hooks(plugin) + around_hooks(plugin) + after_hooks(plugin)
12
+
13
+ hooks.each do |hook|
14
+ if hook =~ /perform$/
15
+ raise LintError, "#{plugin}.#{hook} is not namespaced"
16
+ end
17
+ end
18
+
19
+ failure_hooks(plugin).each do |hook|
20
+ if hook =~ /failure$/
21
+ raise LintError, "#{plugin}.#{hook} is not namespaced"
22
+ end
23
+ end
24
+ end
25
+
26
+ # Given an object, returns a list `before_perform` hook names.
27
+ def before_hooks(job)
28
+ job.methods.grep(/^before_perform/).sort
29
+ end
30
+
31
+ # Given an object, returns a list `around_perform` hook names.
32
+ def around_hooks(job)
33
+ job.methods.grep(/^around_perform/).sort
34
+ end
35
+
36
+ # Given an object, returns a list `after_perform` hook names.
37
+ def after_hooks(job)
38
+ job.methods.grep(/^after_perform/).sort
39
+ end
40
+
41
+ # Given an object, returns a list `on_failure` hook names.
42
+ def failure_hooks(job)
43
+ job.methods.grep(/^on_failure/).sort
44
+ end
45
+
46
+ # Given an object, returns a list `after_enqueue` hook names.
47
+ def after_enqueue_hooks(job)
48
+ job.methods.grep(/^after_enqueue/).sort
49
+ end
50
+
51
+ # Given an object, returns a list `before_enqueue` hook names.
52
+ def before_enqueue_hooks(job)
53
+ job.methods.grep(/^before_enqueue/).sort
54
+ end
55
+
56
+ # Given an object, returns a list `after_dequeue` hook names.
57
+ def after_dequeue_hooks(job)
58
+ job.methods.grep(/^after_dequeue/).sort
59
+ end
60
+
61
+ # Given an object, returns a list `before_dequeue` hook names.
62
+ def before_dequeue_hooks(job)
63
+ job.methods.grep(/^before_dequeue/).sort
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,52 @@
1
+ ResqueSqs::Server.helpers do
2
+ ####################
3
+ #failed.erb helpers#
4
+ ####################
5
+
6
+ def failed_date_format
7
+ "%Y/%m/%d %T %z"
8
+ end
9
+
10
+ def failed_multiple_queues?
11
+ return @multiple_failed_queues if defined?(@multiple_failed_queues)
12
+ @multiple_failed_queues = ResqueSqs::Failure.queues.size > 1
13
+ end
14
+
15
+ def failed_size
16
+ @failed_size ||= ResqueSqs::Failure.count(params[:queue], params[:class])
17
+ end
18
+
19
+ def failed_per_page
20
+ @failed_per_page = if params[:class]
21
+ failed_size
22
+ else
23
+ 20
24
+ end
25
+ end
26
+
27
+ def failed_start_at
28
+ params[:start].to_i
29
+ end
30
+
31
+ def failed_end_at
32
+ if failed_start_at + failed_per_page > failed_size
33
+ failed_size
34
+ else
35
+ failed_start_at + failed_per_page
36
+ end
37
+ end
38
+
39
+ def failed_order
40
+ params[:order] || 'desc'
41
+ end
42
+
43
+ def failed_class_counts(queue = params[:queue])
44
+ classes = Hash.new(0)
45
+ ResqueSqs::Failure.each(0, ResqueSqs::Failure.count(queue), queue) do |_, item|
46
+ class_name = item['payload']['class'] if item['payload']
47
+ class_name ||= "nil"
48
+ classes[class_name] += 1
49
+ end
50
+ classes
51
+ end
52
+ end