queue_classic_plus 4.0.0.alpha18 → 4.0.0.alpha20

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f4194444b2e3b6bc8d08ebfe7393e9efa53bc31fdb8c7e79c5b1a6aff31e8776
4
- data.tar.gz: a12f2b01acea5e2fb4fc6e6b257c6aafc228ff38f73280a3deb974452f2e7220
3
+ metadata.gz: 4e0d46c9b50526efda508c6b06114bff55fe3fe3d34f519b2fb65d55f4b16848
4
+ data.tar.gz: fd2fa603af9a4e4f65f3021b8e84b1c25f6effa68aed4d5b81b980b04def5b09
5
5
  SHA512:
6
- metadata.gz: 42399e627f40795df59f4235e6d616527506758af7a988c2e7e923ce3fbad2bac49b188563ad508d66e95f9935149eaafd1928d6eac6c97bfeb9f1a437fffabe
7
- data.tar.gz: b08d05305a8772d2aacf0e5984b5d2254f3b1f83cb8e6d4d02b7f1bb3d929d70e3ddd02b1130f9845ebec3d9ef409d7a86d63f44ff04ea3fb998dd0cab2080a0
6
+ metadata.gz: d19aa544d277fcf0f04954fa9f3dfeb476435e2a4342663c73faa3150d40175b7ab7079550fa469e8b488b41a97e55d46950990714ba4898710bb42e27493672
7
+ data.tar.gz: c3ed2d8b5cbfcf0e8d1ecfd9bc0432f9c2b371061d5b5473df96d7e299a1ccb4d283a66dc7316de8a6719312254d3eafd9e3ae735b5876b7000334fda040f52e
data/README.md CHANGED
@@ -124,6 +124,59 @@ class Jobs::NoTransaction < QueueClassicPlus::Base
124
124
  end
125
125
  ```
126
126
 
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
+
127
180
  ## Advanced configuration
128
181
 
129
182
  If you want to log exceptions in your favorite exception tracker. You can configured it like so:
@@ -136,6 +136,22 @@ module QueueClassicPlus
136
136
  end
137
137
  end
138
138
 
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
+
139
155
  # Debugging
140
156
  def self.list
141
157
  q = "SELECT * FROM queue_classic_jobs WHERE q_name = '#{@queue}'"
@@ -165,5 +181,26 @@ module QueueClassicPlus
165
181
  def self.execute(sql, *args)
166
182
  QC.default_conn_adapter.execute(sql, *args)
167
183
  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
168
205
  end
169
206
  end
@@ -1,3 +1,3 @@
1
1
  module QueueClassicPlus
2
- VERSION = '4.0.0.alpha18'.freeze
2
+ VERSION = '4.0.0.alpha20'.freeze
3
3
  end
@@ -26,15 +26,19 @@ module QueueClassicPlus
26
26
 
27
27
  @failed_job = job
28
28
  @raw_args = job[:args]
29
- @failed_job_args = failed_job_class ? failed_job_class.deserialized(@raw_args) : @raw_args
30
-
31
- if force_retry && !(failed_job_class.respond_to?(:disable_retries) && failed_job_class.disable_retries)
32
- Metrics.increment("qc.force_retry", source: @q_name)
33
- retry_with_remaining(e)
34
- # The mailers doesn't have a retries_on?
35
- elsif failed_job_class.respond_to?(:retries_on?) && failed_job_class.retries_on?(e)
36
- Metrics.increment("qc.retry", source: @q_name)
37
- retry_with_remaining(e)
29
+
30
+ if queue_classic_plus_job?
31
+ @failed_job_args = failed_job_class.deserialized(@raw_args)
32
+
33
+ if force_retry && !failed_job_class.disable_retries
34
+ Metrics.increment("qc.force_retry", source: @q_name)
35
+ retry_with_remaining(e)
36
+ elsif failed_job_class.retries_on?(e)
37
+ Metrics.increment("qc.retry", source: @q_name)
38
+ retry_with_remaining(e)
39
+ else
40
+ enqueue_failed(e)
41
+ end
38
42
  else
39
43
  enqueue_failed(e)
40
44
  end
@@ -60,10 +64,20 @@ module QueueClassicPlus
60
64
  (@failed_job[:remaining_retries] || max_retries).to_i
61
65
  end
62
66
 
67
+ def queue_classic_plus_job?
68
+ failed_job_class && failed_job_class < Base
69
+ end
70
+
63
71
  def failed_job_class
64
- Object.const_get(@failed_job[:method].split('.')[0])
65
- rescue NameError
66
- nil
72
+ return @failed_job_class if @failed_job_class_memoized
73
+
74
+ @failed_job_class_memoized = true
75
+ @failed_job_class =
76
+ begin
77
+ Object.const_get(@failed_job[:method].split('.')[0])
78
+ rescue NameError
79
+ nil
80
+ end
67
81
  end
68
82
 
69
83
  def backoff
data/spec/base_spec.rb CHANGED
@@ -184,6 +184,172 @@ describe QueueClassicPlus::Base do
184
184
  subject._perform(42, true)
185
185
  end
186
186
  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
187
353
  end
188
354
 
189
355
  describe ".librato_key" do
data/spec/worker_spec.rb CHANGED
@@ -45,6 +45,48 @@ describe QueueClassicPlus::CustomWorker do
45
45
  expect(job).to_not be_nil
46
46
  expect(job['last_error']).to_not be_nil
47
47
  end
48
+
49
+ context 'for a non-QueueClassicPlus job' do
50
+ let(:job_type) do
51
+ Class.new(ActiveJob::Base) do
52
+ def self.name
53
+ "NonQcJob"
54
+ end
55
+
56
+ self.queue_adapter = :queue_classic
57
+
58
+ queue_as :test
59
+
60
+ def perform(boom)
61
+ raise boom.to_s
62
+ end
63
+ end.tap { Object.const_set(:NonQcJob, _1) }
64
+ end
65
+ let(:queue) { QC::Queue.new("test") }
66
+
67
+ it 'properly enqueues for jobs in the failed queue' do
68
+ non_qc_job = job_type.new(:boom).enqueue
69
+ job_id = non_qc_job.provider_job_id
70
+
71
+ expect(failed_queue.count).to eq(0)
72
+ job = find_job(job_id)
73
+ expect(job).to_not be_nil
74
+ expect(find_job(job_id.succ)).to be_nil
75
+ args = JSON.load(job['args']).first
76
+ expect(args['arguments']).to eq(QueueClassicPlus::Base.serialized([:boom]))
77
+
78
+ worker.work
79
+
80
+ expect(failed_queue.count).to eq(1)
81
+ expect(find_job(job_id)).to be_nil
82
+ job = find_job(job_id.succ)
83
+ expect(job).to_not be_nil
84
+ failed_args = JSON.load(job['args']).first
85
+ expect(failed_args['arguments']).to eq(QueueClassicPlus::Base.serialized([:boom]))
86
+ expect(job['last_error']).to_not be_nil
87
+ expect(job['last_error']).to start_with("boom\n")
88
+ end
89
+ end
48
90
  end
49
91
  end
50
92
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: queue_classic_plus
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.0.alpha18
4
+ version: 4.0.0.alpha20
5
5
  platform: ruby
6
6
  authors:
7
7
  - Simon Mathieu
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2023-09-06 00:00:00.000000000 Z
13
+ date: 2023-11-06 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: queue_classic