wireframe-resque_unit 0.4.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +130 -0
- data/lib/resque_unit.rb +13 -0
- data/lib/resque_unit/assertions.rb +81 -0
- data/lib/resque_unit/errors.rb +17 -0
- data/lib/resque_unit/helpers.rb +57 -0
- data/lib/resque_unit/plugin.rb +70 -0
- data/lib/resque_unit/resque.rb +224 -0
- data/lib/resque_unit/scheduler.rb +41 -0
- data/lib/resque_unit/scheduler_assertions.rb +49 -0
- data/lib/resque_unit_scheduler.rb +4 -0
- data/test/resque_test.rb +61 -0
- data/test/resque_unit_scheduler_test.rb +201 -0
- data/test/resque_unit_test.rb +460 -0
- data/test/sample_jobs.rb +167 -0
- data/test/test_helper.rb +9 -0
- metadata +96 -0
data/README.md
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
ResqueUnit
|
2
|
+
==========
|
3
|
+
|
4
|
+
ResqueUnit provides some extra assertions and a mock Resque for
|
5
|
+
testing Rails code that depends on Resque. You can install it as
|
6
|
+
either a gem or a plugin:
|
7
|
+
|
8
|
+
gem install resque_unit
|
9
|
+
|
10
|
+
and in your test.rb:
|
11
|
+
|
12
|
+
config.gem 'resque_unit'
|
13
|
+
|
14
|
+
If you'd rather install it as a plugin, you should be able to run
|
15
|
+
|
16
|
+
script/plugin install git://github.com/justinweiss/resque_unit.git
|
17
|
+
|
18
|
+
inside your Rails projects.
|
19
|
+
|
20
|
+
Examples
|
21
|
+
========
|
22
|
+
|
23
|
+
ResqueUnit provides some extra assertions for your unit tests. For
|
24
|
+
example, if you have code that queues a resque job:
|
25
|
+
|
26
|
+
class MyJob
|
27
|
+
@queue = :low
|
28
|
+
|
29
|
+
def self.perform(x)
|
30
|
+
# do stuff
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def queue_job
|
35
|
+
Resque.enqueue(MyJob, 1)
|
36
|
+
end
|
37
|
+
|
38
|
+
You can write a unit test for code that queues this job:
|
39
|
+
|
40
|
+
def test_job_queued
|
41
|
+
queue_job
|
42
|
+
assert_queued(MyJob) # assert that MyJob was queued in the :low queue
|
43
|
+
end
|
44
|
+
|
45
|
+
You can also verify that a job was queued with arguments:
|
46
|
+
|
47
|
+
def test_job_queued_with_arguments
|
48
|
+
queue_job
|
49
|
+
assert_queued(MyJob, [1])
|
50
|
+
end
|
51
|
+
|
52
|
+
And you can run all the jobs in the queue, so you can verify that they
|
53
|
+
run correctly:
|
54
|
+
|
55
|
+
def test_job_runs
|
56
|
+
queue_job
|
57
|
+
Resque.run!
|
58
|
+
assert stuff_was_done, "Job didn't run"
|
59
|
+
end
|
60
|
+
|
61
|
+
You can also access the queues directly:
|
62
|
+
|
63
|
+
def test_jobs_in_queue
|
64
|
+
queue_job
|
65
|
+
assert_equal 1, Resque.queue(:low).length
|
66
|
+
end
|
67
|
+
|
68
|
+
Finally, you can enable hooks:
|
69
|
+
|
70
|
+
Resque.enable_hooks!
|
71
|
+
|
72
|
+
class MyJobWithHooks
|
73
|
+
@queue = :hooked
|
74
|
+
|
75
|
+
def self.perform(x)
|
76
|
+
# do stuff
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.after_enqueue_mark(*args)
|
80
|
+
# called when the job is enqueued
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.before_perform_mark(*args)
|
84
|
+
# called just before the +perform+ method
|
85
|
+
end
|
86
|
+
|
87
|
+
def self.after_perform_mark(*args)
|
88
|
+
# called just after the +perform+ method
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.failure_perform_mark(*args)
|
92
|
+
# called if the +perform+ method raised
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
def queue_job
|
98
|
+
Resque.enqueue(MyJobWithHooks, 1)
|
99
|
+
end
|
100
|
+
|
101
|
+
Caveats
|
102
|
+
=======
|
103
|
+
|
104
|
+
* You should make sure that you call `Resque.reset!` in your test's
|
105
|
+
setup method to clear all of the test queues.
|
106
|
+
* Hooks support is optional. Just because you probably don't want to call
|
107
|
+
them during unit tests if they play with a DB. Call `Resque.enable_hooks!`
|
108
|
+
in your tests's setup method to enable hooks. To disable hooks, call
|
109
|
+
`Resque.disable_hooks!`.
|
110
|
+
|
111
|
+
Resque-Scheduler Support
|
112
|
+
========================
|
113
|
+
|
114
|
+
By calling `require 'resque_unit_scheduler'`, ResqueUnit will provide
|
115
|
+
mocks for [resque-scheduler's](http://github.com/bvandenbos/resque-scheduler)
|
116
|
+
`enqueue_at` and `enqueue_in` methods, along with a few extra
|
117
|
+
assertions. These are used like this:
|
118
|
+
|
119
|
+
Resque.enqueue_in(600, MediumPriorityJob) # enqueues MediumPriorityJob in 600 seconds
|
120
|
+
assert_queued_in(600, MediumPriorityJob) # will pass
|
121
|
+
assert_not_queued_in(300, MediumPriorityJob) # will also pass
|
122
|
+
|
123
|
+
Resque.enqueue_at(Time.now + 10, MediumPriorityJob) # enqueues MediumPriorityJob at 10 seconds from now
|
124
|
+
assert_queued_at(Time.now + 10, MediumPriorityJob) # will pass
|
125
|
+
assert_not_queued_at(Time.now + 1, MediumPriorityJob) # will also pass
|
126
|
+
|
127
|
+
For now, `assert_queued` and `assert_not_queued` will pass for any
|
128
|
+
scheduled job. `Resque.run!` will run all scheduled jobs as well.
|
129
|
+
|
130
|
+
Copyright (c) 2010 Justin Weiss, released under the MIT license
|
data/lib/resque_unit.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
# These are a group of assertions you can use in your unit tests to
|
2
|
+
# verify that your code is using Resque correctly.
|
3
|
+
module ResqueUnit::Assertions
|
4
|
+
|
5
|
+
# Asserts that +klass+ has been queued into its appropriate queue at
|
6
|
+
# least once. If +args+ is nil, it only asserts that the klass has
|
7
|
+
# been queued. Otherwise, it asserts that the klass has been queued
|
8
|
+
# with the correct arguments. Pass an empty array for +args+ if you
|
9
|
+
# want to assert that klass has been queued without arguments. Pass a block
|
10
|
+
# if you want to assert something was queued within its execution.
|
11
|
+
def assert_queued(klass, args = nil, message = nil, &block)
|
12
|
+
queue_name = Resque.queue_for(klass)
|
13
|
+
assert_job_created(queue_name, klass, args, message, &block)
|
14
|
+
end
|
15
|
+
alias assert_queues assert_queued
|
16
|
+
|
17
|
+
# The opposite of +assert_queued+.
|
18
|
+
def assert_not_queued(klass = nil, args = nil, message = nil, &block)
|
19
|
+
queue_name = Resque.queue_for(klass)
|
20
|
+
|
21
|
+
queue = if block_given?
|
22
|
+
snapshot = Resque.size(queue_name)
|
23
|
+
yield
|
24
|
+
Resque.all(queue_name)[snapshot..-1]
|
25
|
+
else
|
26
|
+
Resque.all(queue_name)
|
27
|
+
end
|
28
|
+
|
29
|
+
assert_with_custom_message(!in_queue?(queue, klass, args),
|
30
|
+
message || "#{klass}#{args ? " with #{args.inspect}" : ""} should not have been queued in #{queue_name}.")
|
31
|
+
end
|
32
|
+
|
33
|
+
# Asserts no jobs were queued within the block passed.
|
34
|
+
def assert_nothing_queued(message = nil, &block)
|
35
|
+
snapshot = Resque.size
|
36
|
+
yield
|
37
|
+
present = Resque.size
|
38
|
+
assert_equal snapshot, present, message || "No jobs should have been queued"
|
39
|
+
end
|
40
|
+
|
41
|
+
# Asserts that a job was created and queued into the specified queue
|
42
|
+
def assert_job_created(queue_name, klass, args = nil, message = nil, &block)
|
43
|
+
queue = if block_given?
|
44
|
+
snapshot = Resque.size(queue_name)
|
45
|
+
yield
|
46
|
+
Resque.all(queue_name)[snapshot..-1]
|
47
|
+
else
|
48
|
+
Resque.all(queue_name)
|
49
|
+
end
|
50
|
+
|
51
|
+
assert_with_custom_message(in_queue?(queue, klass, args),
|
52
|
+
message || "#{klass}#{args ? " with #{args.inspect}" : ""} should have been queued in #{queue_name}: #{queue.inspect}.")
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
# In Test::Unit, +assert_block+ displays only the message on a test
|
58
|
+
# failure and +assert+ always appends a message to the end of the
|
59
|
+
# passed-in assertion message. In MiniTest, it's the other way
|
60
|
+
# around. This abstracts those differences and never appends a
|
61
|
+
# message to the one the user passed in.
|
62
|
+
def assert_with_custom_message(value, message = nil)
|
63
|
+
if defined?(MiniTest::Assertions)
|
64
|
+
assert value, message
|
65
|
+
else
|
66
|
+
assert_block message do
|
67
|
+
value
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def in_queue?(queue, klass, args = nil)
|
73
|
+
!matching_jobs(queue, klass, args).empty?
|
74
|
+
end
|
75
|
+
|
76
|
+
def matching_jobs(queue, klass, args = nil)
|
77
|
+
normalized_args = Resque.decode(Resque.encode(args)) if args
|
78
|
+
queue.select {|e| e["class"] == klass.to_s && (!args || e["args"] == normalized_args )}
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# Re-define errors in from Resque, in case the 'resque' gem was not loaded.
|
2
|
+
module Resque
|
3
|
+
# Raised whenever we need a queue but none is provided.
|
4
|
+
unless defined?(NoQueueError)
|
5
|
+
class NoQueueError < RuntimeError; end
|
6
|
+
end
|
7
|
+
|
8
|
+
# Raised when trying to create a job without a class
|
9
|
+
unless defined?(NoClassError)
|
10
|
+
class NoClassError < RuntimeError; end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Raised when a worker was killed while processing a job.
|
14
|
+
unless defined?(DirtyExit)
|
15
|
+
class DirtyExit < RuntimeError; end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Resque
|
2
|
+
module Helpers
|
3
|
+
# Given a Ruby object, returns a string suitable for storage in a
|
4
|
+
# queue.
|
5
|
+
def encode(object)
|
6
|
+
if defined? Yajl
|
7
|
+
Yajl::Encoder.encode(object)
|
8
|
+
else
|
9
|
+
object.to_json
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Given a string, returns a Ruby object.
|
14
|
+
def decode(object)
|
15
|
+
return unless object
|
16
|
+
|
17
|
+
if defined? Yajl
|
18
|
+
begin
|
19
|
+
Yajl::Parser.parse(object, :check_utf8 => false)
|
20
|
+
rescue Yajl::ParseError
|
21
|
+
end
|
22
|
+
else
|
23
|
+
begin
|
24
|
+
JSON.parse(object)
|
25
|
+
rescue JSON::ParserError
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Given a word with dashes, returns a camel cased version of it.
|
31
|
+
#
|
32
|
+
# classify('job-name') # => 'JobName'
|
33
|
+
def classify(dashed_word)
|
34
|
+
dashed_word.split('-').each { |part| part[0] = part[0].chr.upcase }.join
|
35
|
+
end
|
36
|
+
|
37
|
+
# Given a camel cased word, returns the constant it represents
|
38
|
+
#
|
39
|
+
# constantize('JobName') # => JobName
|
40
|
+
def constantize(camel_cased_word)
|
41
|
+
camel_cased_word = camel_cased_word.to_s
|
42
|
+
|
43
|
+
if camel_cased_word.include?('-')
|
44
|
+
camel_cased_word = classify(camel_cased_word)
|
45
|
+
end
|
46
|
+
|
47
|
+
names = camel_cased_word.split('::')
|
48
|
+
names.shift if names.empty? || names.first.empty?
|
49
|
+
|
50
|
+
constant = Object
|
51
|
+
names.each do |name|
|
52
|
+
constant = constant.const_get(name) || constant.const_missing(name)
|
53
|
+
end
|
54
|
+
constant
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# A copy of the original Resque:Plugin class from the "resque" gem
|
2
|
+
# No need to redefine this class if the resque gem was already loaded
|
3
|
+
unless defined?(Resque::Plugin)
|
4
|
+
|
5
|
+
module Resque
|
6
|
+
module Plugin
|
7
|
+
extend self
|
8
|
+
|
9
|
+
LintError = Class.new(RuntimeError)
|
10
|
+
|
11
|
+
# Ensure that your plugin conforms to good hook naming conventions.
|
12
|
+
#
|
13
|
+
# Resque::Plugin.lint(MyResquePlugin)
|
14
|
+
def lint(plugin)
|
15
|
+
hooks = before_hooks(plugin) + around_hooks(plugin) + after_hooks(plugin)
|
16
|
+
|
17
|
+
hooks.each do |hook|
|
18
|
+
if hook =~ /perform$/
|
19
|
+
raise LintError, "#{plugin}.#{hook} is not namespaced"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
failure_hooks(plugin).each do |hook|
|
24
|
+
if hook =~ /failure$/
|
25
|
+
raise LintError, "#{plugin}.#{hook} is not namespaced"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Given an object, returns a list `before_perform` hook names.
|
31
|
+
def before_hooks(job)
|
32
|
+
job.methods.grep(/^before_perform/).sort
|
33
|
+
end
|
34
|
+
|
35
|
+
# Given an object, returns a list `around_perform` hook names.
|
36
|
+
def around_hooks(job)
|
37
|
+
job.methods.grep(/^around_perform/).sort
|
38
|
+
end
|
39
|
+
|
40
|
+
# Given an object, returns a list `after_perform` hook names.
|
41
|
+
def after_hooks(job)
|
42
|
+
job.methods.grep(/^after_perform/).sort
|
43
|
+
end
|
44
|
+
|
45
|
+
# Given an object, returns a list `on_failure` hook names.
|
46
|
+
def failure_hooks(job)
|
47
|
+
job.methods.grep(/^on_failure/).sort
|
48
|
+
end
|
49
|
+
|
50
|
+
# Given an object, returns a list `after_enqueue` hook names.
|
51
|
+
def after_enqueue_hooks(job)
|
52
|
+
job.methods.grep(/^after_enqueue/).sort
|
53
|
+
end
|
54
|
+
|
55
|
+
# Given an object, returns a list `before_enqueue` hook names.
|
56
|
+
def before_enqueue_hooks(job)
|
57
|
+
job.methods.grep(/^before_enqueue/).sort
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
unless defined?(Resque::Job::DontPerform)
|
65
|
+
module Resque
|
66
|
+
class Job
|
67
|
+
DontPerform = Class.new(StandardError)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,224 @@
|
|
1
|
+
# The fake Resque class. This needs to be loaded after the real Resque
|
2
|
+
# for the assertions in +ResqueUnit::Assertions+ to work.
|
3
|
+
module Resque
|
4
|
+
include Helpers
|
5
|
+
extend self
|
6
|
+
|
7
|
+
# Resets all the queues to the empty state. This should be called in
|
8
|
+
# your test's +setup+ method until I can figure out a way for it to
|
9
|
+
# automatically be called.
|
10
|
+
#
|
11
|
+
# If <tt>queue_name</tt> is given, then resets only that queue.
|
12
|
+
def reset!(queue_name = nil)
|
13
|
+
if @queue && queue_name
|
14
|
+
@queue[queue_name] = []
|
15
|
+
else
|
16
|
+
@queue = Hash.new { |h, k| h[k] = [] }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns a hash of all the queue names and jobs that have been queued. The
|
21
|
+
# format is <tt>{queue_name => [job, ..]}</tt>.
|
22
|
+
def self.queues
|
23
|
+
@queue || reset!
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns an array of all the jobs that have been queued. Each
|
27
|
+
# element is of the form +{"class" => klass, "args" => args}+ where
|
28
|
+
# +klass+ is the job's class and +args+ is an array of the arguments
|
29
|
+
# passed to the job.
|
30
|
+
def queue(queue_name)
|
31
|
+
queues[queue_name]
|
32
|
+
end
|
33
|
+
|
34
|
+
# Return an array of all jobs' payloads for queue
|
35
|
+
# Elements are decoded
|
36
|
+
def all(queue_name)
|
37
|
+
result = list_range(queue_name, 0, size(queue_name))
|
38
|
+
result.is_a?(Array) ? result : [ result ]
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns an array of jobs' payloads for queue.
|
42
|
+
#
|
43
|
+
# start and count should be integer and can be used for pagination.
|
44
|
+
# start is the item to begin, count is how many items to return.
|
45
|
+
#
|
46
|
+
# To get the 3rd page of a 30 item, paginatied list one would use:
|
47
|
+
# Resque.peek('my_list', 59, 30)
|
48
|
+
def peek(queue_name, start = 0, count = 1)
|
49
|
+
list_range(queue_name, start, count)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Gets a range of jobs' payloads from queue.
|
53
|
+
# Returns single element if count equal 1
|
54
|
+
# Elements are decoded
|
55
|
+
def list_range(key, start = 0, count = 1)
|
56
|
+
data = if count == 1
|
57
|
+
decode(queues[key][start])
|
58
|
+
else
|
59
|
+
(queues[key][start...start + count] || []).map { |entry| decode(entry) }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Yes, all Resque hooks!
|
64
|
+
def enable_hooks!
|
65
|
+
@hooks_enabled = true
|
66
|
+
end
|
67
|
+
|
68
|
+
def disable_hooks!
|
69
|
+
@hooks_enabled = nil
|
70
|
+
end
|
71
|
+
|
72
|
+
# Executes all jobs in all queues in an undefined order.
|
73
|
+
def run!
|
74
|
+
payloads = []
|
75
|
+
@queue.each do |queue_name, queue|
|
76
|
+
payloads.concat queue.slice!(0, queue.size)
|
77
|
+
end
|
78
|
+
exec_payloads payloads.shuffle
|
79
|
+
end
|
80
|
+
|
81
|
+
def run_for!(queue_name, limit=false)
|
82
|
+
queue = @queue[queue_name]
|
83
|
+
exec_payloads queue.slice!(0, ( limit ? limit : queue.size) ).shuffle
|
84
|
+
end
|
85
|
+
|
86
|
+
def exec_payloads(raw_payloads)
|
87
|
+
raw_payloads.each do |raw_payload|
|
88
|
+
job_payload = decode(raw_payload)
|
89
|
+
@hooks_enabled ? perform_with_hooks(job_payload) : perform_without_hooks(job_payload)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
private :exec_payloads
|
93
|
+
|
94
|
+
# 1. Execute all jobs in all queues in an undefined order,
|
95
|
+
# 2. Check if new jobs were announced, and execute them.
|
96
|
+
# 3. Repeat 3
|
97
|
+
def full_run!
|
98
|
+
run! until empty_queues?
|
99
|
+
end
|
100
|
+
|
101
|
+
# Returns the size of the given queue
|
102
|
+
def size(queue_name = nil)
|
103
|
+
if queue_name
|
104
|
+
queues[queue_name].length
|
105
|
+
else
|
106
|
+
queues.values.flatten.length
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# :nodoc:
|
111
|
+
def enqueue(klass, *args)
|
112
|
+
enqueue_to( queue_for(klass), klass, *args)
|
113
|
+
end
|
114
|
+
|
115
|
+
# :nodoc:
|
116
|
+
def enqueue_to( queue_name, klass, *args )
|
117
|
+
# Behaves like Resque, raise if no queue was specifed
|
118
|
+
raise NoQueueError.new("Jobs must be placed onto a queue.") unless queue_name
|
119
|
+
enqueue_unit(queue_name, {"class" => klass.name, "args" => args })
|
120
|
+
end
|
121
|
+
|
122
|
+
# :nodoc:
|
123
|
+
def queue_for(klass)
|
124
|
+
klass.instance_variable_get(:@queue) || (klass.respond_to?(:queue) && klass.queue)
|
125
|
+
end
|
126
|
+
alias :queue_from_class :queue_for
|
127
|
+
|
128
|
+
# :nodoc:
|
129
|
+
def empty_queues?
|
130
|
+
queues.all? do |k, v|
|
131
|
+
v.empty?
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def enqueue_unit(queue_name, hash)
|
136
|
+
klass = constantize(hash["class"])
|
137
|
+
if @hooks_enabled
|
138
|
+
before_hooks = Plugin.before_enqueue_hooks(klass).map do |hook|
|
139
|
+
klass.send(hook, *hash["args"])
|
140
|
+
end
|
141
|
+
return nil if before_hooks.any? { |result| result == false }
|
142
|
+
end
|
143
|
+
queue(queue_name) << encode(hash)
|
144
|
+
if @hooks_enabled
|
145
|
+
Plugin.after_enqueue_hooks(klass).each do |hook|
|
146
|
+
klass.send(hook, *hash["args"])
|
147
|
+
end
|
148
|
+
end
|
149
|
+
queue(queue_name).size
|
150
|
+
end
|
151
|
+
|
152
|
+
# Call perform on the job class
|
153
|
+
def perform_without_hooks(job_payload)
|
154
|
+
constantize(job_payload["class"]).perform(*job_payload["args"])
|
155
|
+
end
|
156
|
+
|
157
|
+
# Call perform on the job class, and adds support for Resque hooks.
|
158
|
+
def perform_with_hooks(job_payload)
|
159
|
+
job_class = constantize(job_payload["class"])
|
160
|
+
before_hooks = Resque::Plugin.before_hooks(job_class)
|
161
|
+
around_hooks = Resque::Plugin.around_hooks(job_class)
|
162
|
+
after_hooks = Resque::Plugin.after_hooks(job_class)
|
163
|
+
failure_hooks = Resque::Plugin.failure_hooks(job_class)
|
164
|
+
|
165
|
+
begin
|
166
|
+
# Execute before_perform hook. Abort the job gracefully if
|
167
|
+
# Resque::DontPerform is raised.
|
168
|
+
begin
|
169
|
+
before_hooks.each do |hook|
|
170
|
+
job_class.send(hook, *job_payload["args"])
|
171
|
+
end
|
172
|
+
rescue Resque::Job::DontPerform
|
173
|
+
return false
|
174
|
+
end
|
175
|
+
|
176
|
+
# Execute the job. Do it in an around_perform hook if available.
|
177
|
+
if around_hooks.empty?
|
178
|
+
perform_without_hooks(job_payload)
|
179
|
+
job_was_performed = true
|
180
|
+
else
|
181
|
+
# We want to nest all around_perform plugins, with the last one
|
182
|
+
# finally calling perform
|
183
|
+
stack = around_hooks.reverse.inject(nil) do |last_hook, hook|
|
184
|
+
if last_hook
|
185
|
+
lambda do
|
186
|
+
job_class.send(hook, *job_payload["args"]) { last_hook.call }
|
187
|
+
end
|
188
|
+
else
|
189
|
+
lambda do
|
190
|
+
job_class.send(hook, *job_payload["args"]) do
|
191
|
+
result = perform_without_hooks(job_payload)
|
192
|
+
job_was_performed = true
|
193
|
+
result
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
stack.call
|
199
|
+
end
|
200
|
+
|
201
|
+
# Execute after_perform hook
|
202
|
+
after_hooks.each do |hook|
|
203
|
+
job_class.send(hook, *job_payload["args"])
|
204
|
+
end
|
205
|
+
|
206
|
+
# Return true if the job was performed
|
207
|
+
return job_was_performed
|
208
|
+
|
209
|
+
# If an exception occurs during the job execution, look for an
|
210
|
+
# on_failure hook then re-raise.
|
211
|
+
rescue Object => e
|
212
|
+
failure_hooks.each { |hook| job_class.send(hook, e, *job_payload["args"]) }
|
213
|
+
raise e
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
class Job
|
218
|
+
extend Helpers
|
219
|
+
def self.create(queue, klass_name, *args)
|
220
|
+
Resque.enqueue_unit(queue, {"class" => constantize(klass_name), "args" => args})
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
end
|