Skip to content

Commit 8c3e7cb

Browse files
authored
Merge pull request #60 from rainforestapp/RF-30285-add-qc-job-callbacks
[RF-30285] Add QC job callbacks
2 parents 00f7487 + 8dcdb1b commit 8c3e7cb

4 files changed

Lines changed: 257 additions & 1 deletion

File tree

README.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,59 @@ class Jobs::NoTransaction < QueueClassicPlus::Base
124124
end
125125
```
126126

127+
128+
#### Callbacks
129+
130+
Jobs support the following callbacks:
131+
```
132+
before_enqueue
133+
after_enqueue
134+
before_perform
135+
after_perform
136+
```
137+
138+
- `enqueue` callbacks are called when the job is initially enqueued. Caveats:
139+
- not called on retries
140+
- not called when scheduled in the future (i.e. `Job.enqueue_perform_in`)
141+
- still called if enqueuing fails because a job with `lock!` is already enqueued.
142+
- `perform` callbacks are called any time the job is picked up and run by a worker, including retries and jobs scheduled in the future.
143+
144+
Callbacks can either be implemented by providing a method to be called:
145+
146+
```ruby
147+
class Jobs::WithCallbackMethods < QueueClassicPlus::Base
148+
before_enqueue :before_enqueue_method
149+
150+
@queue = :low
151+
152+
def self.before_enqueue_method(*args)
153+
# ...
154+
end
155+
156+
def self.perform(user_id)
157+
# ...
158+
end
159+
end
160+
```
161+
162+
or by providing a block:
163+
164+
```ruby
165+
class Jobs::WithCallbackBlocks < QueueClassicPlus::Base
166+
before_enqueue do |*args|
167+
# ...
168+
end
169+
170+
@queue = :low
171+
172+
def self.perform(user_id)
173+
# ...
174+
end
175+
end
176+
```
177+
178+
The `args` passed to the callback method/block will be the same as the arguments passed to `QueueClassicPlus::Base.do` and `QueueClassicPlus::Base.perform`.
179+
127180
## Advanced configuration
128181

129182
If you want to log exceptions in your favorite exception tracker. You can configured it like so:

lib/queue_classic_plus/base.rb

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,22 @@ def self.transaction(options = {}, &block)
136136
end
137137
end
138138

139+
def self.before_enqueue(callback_method = nil, &block)
140+
define_callback(:enqueue, :before, callback_method, &block)
141+
end
142+
143+
def self.after_enqueue(callback_method = nil, &block)
144+
define_callback(:enqueue, :after, callback_method, &block)
145+
end
146+
147+
def self.before_perform(callback_method = nil, &block)
148+
define_callback(:perform, :before, callback_method, &block)
149+
end
150+
151+
def self.after_perform(callback_method = nil, &block)
152+
define_callback(:perform, :after, callback_method, &block)
153+
end
154+
139155
# Debugging
140156
def self.list
141157
q = "SELECT * FROM queue_classic_jobs WHERE q_name = '#{@queue}'"
@@ -165,5 +181,26 @@ def self.deserialized(args)
165181
def self.execute(sql, *args)
166182
QC.default_conn_adapter.execute(sql, *args)
167183
end
184+
185+
def self.define_callback(name, type, callback_method, &_block)
186+
singleton_class.prepend(
187+
Module.new do |callback_module|
188+
callback_module.define_method(name) do |*args|
189+
callback_args = args
190+
if name == :enqueue
191+
callback_args = callback_args.drop(1) # Class/method is the first argument. Callbacks don't need that.
192+
end
193+
194+
if type == :before
195+
block_given? ? yield(*callback_args) : send(callback_method, *callback_args)
196+
end
197+
super(*args)
198+
if type == :after
199+
block_given? ? yield(*callback_args) : send(callback_method, *callback_args)
200+
end
201+
end
202+
end
203+
)
204+
end
168205
end
169206
end

lib/queue_classic_plus/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
module QueueClassicPlus
2-
VERSION = '4.0.0.alpha18'.freeze
2+
VERSION = '4.0.0.alpha19'.freeze
33
end

spec/base_spec.rb

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,172 @@ def self.perform(foo, bar)
184184
subject._perform(42, true)
185185
end
186186
end
187+
188+
context "with callbacks defined" do
189+
subject do
190+
Class.new(QueueClassicPlus::Base) do
191+
@queue = :test
192+
193+
before_enqueue :before_enqueue_method
194+
after_enqueue :after_enqueue_method
195+
196+
before_perform :before_perform_method
197+
after_perform :after_perform_method
198+
199+
def self.before_enqueue_method(*_args); end;
200+
def self.after_enqueue_method(*_args); end;
201+
def self.before_perform_method(*_args); end;
202+
def self.after_perform_method(*_args); end;
203+
204+
def self.perform(*_args); end;
205+
end
206+
end
207+
208+
it "passes enqueue arguments to callbacks" do
209+
expect(subject).to receive(:before_enqueue_method).with("enqueue_argument").once
210+
expect(subject).to receive(:after_enqueue_method).with("enqueue_argument").once
211+
212+
subject.do("enqueue_argument")
213+
end
214+
215+
it "passes perform arguments to callbacks" do
216+
expect(subject).to receive(:before_perform_method).with("perform_argument").once
217+
expect(subject).to receive(:after_perform_method).with("perform_argument").once
218+
219+
subject.perform("perform_argument")
220+
end
221+
222+
context "when enqueued" do
223+
it "calls the enqueue callback methods" do
224+
expect(subject).to receive(:before_enqueue_method).once
225+
expect(subject).to receive(:after_enqueue_method).once
226+
227+
subject.do
228+
end
229+
230+
it "does not call the perform callbacks" do
231+
expect(subject).to_not receive(:before_perform_method)
232+
expect(subject).to_not receive(:after_perform_method)
233+
234+
subject.do
235+
end
236+
end
237+
238+
context "when perform" do
239+
it "calls the perform callback methods" do
240+
expect(subject).to receive(:before_perform_method).once
241+
expect(subject).to receive(:after_perform_method).once
242+
243+
subject.perform
244+
end
245+
246+
it "does not call the enqueue callbacks" do
247+
expect(subject).to_not receive(:before_enqueue_method)
248+
expect(subject).to_not receive(:after_enqueue_method)
249+
250+
subject.perform
251+
end
252+
253+
context "when perform fails" do
254+
subject do
255+
Class.new(QueueClassicPlus::Base) do
256+
@queue = :test
257+
258+
before_perform :before_perform_method
259+
after_perform :after_perform_method
260+
261+
def self.before_perform_method(*_args); end;
262+
def self.after_perform_method(*_args); end;
263+
264+
def self.perform(*_args)
265+
raise StandardError
266+
end
267+
end
268+
end
269+
270+
it "does not call after callbacks" do
271+
expect(subject).to receive(:before_perform_method).once
272+
expect(subject).to_not receive(:after_perform_method)
273+
274+
begin
275+
subject.perform
276+
rescue StandardError
277+
end
278+
end
279+
end
280+
end
281+
282+
context "when callback defined multiple times" do
283+
subject do
284+
Class.new(QueueClassicPlus::Base) do
285+
@queue = :test
286+
287+
before_enqueue :before_enqueue_method_1
288+
before_enqueue :before_enqueue_method_2
289+
before_enqueue :before_enqueue_method_3
290+
291+
def self.before_enqueue_method_1(*_args); end;
292+
def self.before_enqueue_method_2(*_args); end;
293+
def self.before_enqueue_method_3(*_args); end;
294+
295+
def self.perform(*_args); end;
296+
end
297+
end
298+
299+
it "calls each callback" do
300+
expect(subject).to receive(:before_enqueue_method_1).once
301+
expect(subject).to receive(:before_enqueue_method_2).once
302+
expect(subject).to receive(:before_enqueue_method_3).once
303+
304+
subject.do
305+
end
306+
end
307+
end
308+
309+
context "with callback blocks defined" do
310+
subject do
311+
Class.new(QueueClassicPlus::Base) do
312+
@queue = :test
313+
class_variable_set(:@@block_result, [])
314+
315+
before_enqueue do |*_args|
316+
class_variable_get(:@@block_result).append("before_enqueue_block")
317+
end
318+
after_enqueue do |*_args|
319+
class_variable_get(:@@block_result).append("after_enqueue_block")
320+
end
321+
322+
before_perform do |*_args|
323+
class_variable_get(:@@block_result).append("before_perform_block")
324+
end
325+
after_perform do |*_args|
326+
class_variable_get(:@@block_result).append("after_perform_block")
327+
end
328+
329+
def self.perform; end;
330+
end
331+
end
332+
333+
context "when enqueued" do
334+
it "calls the enqueue callback blocks" do
335+
subject.do
336+
337+
expect(subject.class_variable_get(:@@block_result)).to eq(
338+
%w(before_enqueue_block after_enqueue_block)
339+
)
340+
end
341+
end
342+
343+
context "when perform" do
344+
it "calls the perform callback blocks" do
345+
subject.perform
346+
347+
expect(subject.class_variable_get(:@@block_result)).to eq(
348+
%w(before_perform_block after_perform_block)
349+
)
350+
end
351+
end
352+
end
187353
end
188354

189355
describe ".librato_key" do

0 commit comments

Comments
 (0)