postburner 0.7.2 → 0.9.0.rc.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +0 -13
- data/README.md +441 -17
- data/Rakefile +1 -1
- data/app/concerns/postburner/callbacks.rb +286 -0
- data/app/models/postburner/job.rb +609 -44
- data/lib/postburner/strategies/immediate_test_queue.rb +133 -0
- data/lib/postburner/strategies/nice_queue.rb +85 -0
- data/lib/postburner/strategies/null_queue.rb +132 -0
- data/lib/postburner/strategies/queue.rb +105 -0
- data/lib/postburner/strategies/test_queue.rb +128 -0
- data/lib/postburner/time_helpers.rb +75 -0
- data/lib/postburner/version.rb +1 -1
- data/lib/postburner.rb +359 -0
- metadata +26 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5bef243523d66bb1c5d803e826f55fa047c20b7876b804cb82ecb6a82b235a38
|
|
4
|
+
data.tar.gz: f97a82f504e19e297fcb2f8b99b238b8804fc7e566fba23f082d092c79b88101
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e990e976405963feeb3b5c8f9de98e1a2553f2820f363ef67ffc8c31cf54fa1e6cb6a9e9a8cb53b01ef9f8c644fe0fcc5a13fd7dc2677796401f66f2c7318f93
|
|
7
|
+
data.tar.gz: c2e829e9f8fc65020cfc080e19d4f26c4d5befa5c5855a73c4225cfaec557144a5237451d129a1e3cae554575bd3053401ead7bcc4c79c524282f6d3dfb4c370
|
data/CHANGELOG.md
CHANGED
|
@@ -1,18 +1,5 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## v0.7.1 - 2022-10-30
|
|
4
|
-
|
|
5
|
-
### Required Changes
|
|
6
|
-
- rename `enqueue` callbacks to `insert`.
|
|
7
|
-
|
|
8
|
-
### Changed
|
|
9
|
-
- convert to ActiveModel callbacks.
|
|
10
|
-
|
|
11
|
-
## v0.7.1 - 2022-10-30
|
|
12
|
-
|
|
13
|
-
### Changed
|
|
14
|
-
- return true from insert_if_queued! and log response to Rails logger.
|
|
15
|
-
|
|
16
3
|
## v0.7.0 - 2022-10-24
|
|
17
4
|
|
|
18
5
|
### Added
|
data/README.md
CHANGED
|
@@ -11,6 +11,27 @@ If you need something faster, check out Que - we love it! Postburner is built fo
|
|
|
11
11
|
simple, safe, and inspectable. All of which Que has (or can be added), but are included by default with some architecture
|
|
12
12
|
differences. See [Comparison to Que](#comparison-to-que) for more.
|
|
13
13
|
|
|
14
|
+
## Queue Strategies
|
|
15
|
+
|
|
16
|
+
Postburner uses different strategies to control how jobs are queued and executed:
|
|
17
|
+
|
|
18
|
+
| Strategy | When to Use | Behavior | Requires Beanstalkd |
|
|
19
|
+
|----------|-------------|----------|---------------------|
|
|
20
|
+
| **NiceQueue** (default) | Production | Async via Beanstalkd, gracefully re-queues premature jobs | Yes |
|
|
21
|
+
| **Queue** (suggested) | Production | Async via Beanstalkd, raises error on premature execution | Yes |
|
|
22
|
+
| **TestQueue** | Testing with explicit time control | Inline execution, raises error for scheduled jobs | No |
|
|
23
|
+
| **ImmediateTestQueue** | Testing with automatic time travel | Inline execution, auto time-travels for scheduled jobs | No |
|
|
24
|
+
| **NullQueue** | Batch processing / deferred execution | Jobs created but not queued, manual execution via `Postburner::Job.perform(id)` | No |
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
# Switch strategies
|
|
28
|
+
Postburner.nice_async_strategy! # Default production (NiceQueue)
|
|
29
|
+
Postburner.async_strategy! # Strict production (Queue)
|
|
30
|
+
Postburner.inline_test_strategy! # Testing (TestQueue)
|
|
31
|
+
Postburner.inline_immediate_test_strategy! # Testing with time travel (ImmediateTestQueue)
|
|
32
|
+
Postburner.null_strategy! # Deferred execution (NullQueue)
|
|
33
|
+
```
|
|
34
|
+
|
|
14
35
|
## Usage
|
|
15
36
|
|
|
16
37
|
```ruby
|
|
@@ -47,6 +68,57 @@ RunDonation.create!(args: {donation_id: 123}).queue! delay: 1.hour
|
|
|
47
68
|
=> {:status=>"INSERTED", :id=>"1141"}
|
|
48
69
|
```
|
|
49
70
|
|
|
71
|
+
### Backburner Queue Configuration
|
|
72
|
+
|
|
73
|
+
Postburner jobs inherit from `Backburner::Queue`, giving you access to Backburner's queue configuration methods. These are **optional** - Backburner provides sensible defaults.
|
|
74
|
+
|
|
75
|
+
```ruby
|
|
76
|
+
class CriticalJob < Postburner::Job
|
|
77
|
+
# Specify which Beanstalkd tube to use (default: 'backburner-jobs')
|
|
78
|
+
queue 'critical'
|
|
79
|
+
|
|
80
|
+
# Set job priority - lower numbers = higher priority (default: 65536)
|
|
81
|
+
# Most urgent priority is 0
|
|
82
|
+
queue_priority 0
|
|
83
|
+
|
|
84
|
+
# Set maximum retry attempts before burying (default: from Backburner config)
|
|
85
|
+
queue_max_job_retries 3
|
|
86
|
+
|
|
87
|
+
# Set job timeout in seconds (default: from Backburner config)
|
|
88
|
+
queue_respond_timeout 300
|
|
89
|
+
|
|
90
|
+
def perform(args)
|
|
91
|
+
# your job logic
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Common configurations:**
|
|
97
|
+
|
|
98
|
+
```ruby
|
|
99
|
+
# High priority job with no retries
|
|
100
|
+
class PaymentJob < Postburner::Job
|
|
101
|
+
queue 'payments'
|
|
102
|
+
queue_priority 0
|
|
103
|
+
queue_max_job_retries 0 # Don't retry - handle failures manually
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Background job with retries
|
|
107
|
+
class EmailJob < Postburner::Job
|
|
108
|
+
queue 'emails'
|
|
109
|
+
queue_priority 1000 # Lower priority
|
|
110
|
+
queue_max_job_retries 5 # Retry up to 5 times
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Long-running job
|
|
114
|
+
class DataExportJob < Postburner::Job
|
|
115
|
+
queue 'exports'
|
|
116
|
+
queue_respond_timeout 3600 # 1 hour timeout
|
|
117
|
+
end
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
These methods come from [Backburner](https://github.com/nesquena/backburner#configure-backburner) and work seamlessly with Postburner. See Backburner's documentation for all available options.
|
|
121
|
+
|
|
50
122
|
### Mailers
|
|
51
123
|
|
|
52
124
|
```ruby
|
|
@@ -58,31 +130,96 @@ j.queue!
|
|
|
58
130
|
=> {:status=>"INSERTED", :id=>"1139"}
|
|
59
131
|
```
|
|
60
132
|
|
|
61
|
-
###
|
|
62
|
-
```ruby
|
|
63
|
-
# get the beanstalkd job id
|
|
64
|
-
job.bkid
|
|
65
|
-
=> 1104
|
|
133
|
+
### Job Management Methods
|
|
66
134
|
|
|
67
|
-
|
|
68
|
-
job.beanstalk_job
|
|
135
|
+
Postburner provides several methods for managing jobs in Beanstalkd and the database:
|
|
69
136
|
|
|
70
|
-
|
|
71
|
-
job.beanstalk_job.stats
|
|
137
|
+
#### delete! - Remove from Beanstalkd only
|
|
72
138
|
|
|
73
|
-
|
|
74
|
-
job.kick!
|
|
139
|
+
Deletes the job from the Beanstalkd queue but **keeps the database record**. Use this when you want to cancel a queued job while maintaining its audit trail.
|
|
75
140
|
|
|
76
|
-
|
|
141
|
+
```ruby
|
|
142
|
+
job = MyJob.create!(args: {})
|
|
143
|
+
job.queue!(delay: 1.hour)
|
|
144
|
+
job.bkid # => 12345
|
|
145
|
+
|
|
146
|
+
# Remove from Beanstalkd but keep in database
|
|
77
147
|
job.delete!
|
|
78
148
|
|
|
79
|
-
|
|
149
|
+
job.reload
|
|
150
|
+
job.bkid # => 12345 (still has bkid)
|
|
151
|
+
job.persisted? # => true (still in database)
|
|
152
|
+
# Job won't execute, but you can still inspect it
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
#### remove! - Soft delete (Beanstalkd + removed_at)
|
|
156
|
+
|
|
157
|
+
Removes the job from Beanstalkd **and** sets `removed_at` timestamp. The database record is preserved for audit trails. This is the recommended way to cancel jobs.
|
|
158
|
+
|
|
159
|
+
```ruby
|
|
160
|
+
job = MyJob.create!(args: {})
|
|
161
|
+
job.queue!(delay: 1.hour)
|
|
162
|
+
|
|
163
|
+
# Soft delete - removes from queue and marks as removed
|
|
80
164
|
job.remove!
|
|
81
165
|
|
|
82
|
-
|
|
83
|
-
job.
|
|
166
|
+
job.reload
|
|
167
|
+
job.removed_at # => 2025-11-01 12:34:56 UTC
|
|
168
|
+
job.persisted? # => true (still in database)
|
|
169
|
+
# Job won't execute and is marked as removed
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
#### destroy / destroy! - Delete from both
|
|
173
|
+
|
|
174
|
+
Destroys the database record **and** removes from Beanstalkd via `before_destroy` callback. Use when you want to completely eliminate the job.
|
|
175
|
+
|
|
176
|
+
```ruby
|
|
177
|
+
job = MyJob.create!(args: {})
|
|
178
|
+
job.queue!(delay: 1.hour)
|
|
179
|
+
|
|
180
|
+
# Completely remove job from database and Beanstalkd
|
|
181
|
+
job.destroy!
|
|
182
|
+
|
|
183
|
+
# Job no longer exists in database or Beanstalkd
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
#### kick! - Retry buried jobs
|
|
187
|
+
|
|
188
|
+
Moves a buried job back into the ready queue. Buried jobs are those that failed too many times or were explicitly buried by Beanstalkd.
|
|
189
|
+
|
|
190
|
+
```ruby
|
|
191
|
+
# Job failed and was buried
|
|
192
|
+
job.beanstalk_job.stats['state'] # => 'buried'
|
|
193
|
+
|
|
194
|
+
# Move it back to ready queue for retry
|
|
195
|
+
job.kick!
|
|
196
|
+
|
|
197
|
+
job.beanstalk_job.stats['state'] # => 'ready'
|
|
198
|
+
```
|
|
84
199
|
|
|
85
|
-
|
|
200
|
+
**Summary:**
|
|
201
|
+
- **`delete!`** - Removes from Beanstalkd, keeps full database record (keeps `bkid`)
|
|
202
|
+
- **`remove!`** - Removes from Beanstalkd, marks `removed_at`, keeps database record (**recommended for canceling jobs**)
|
|
203
|
+
- **`destroy!`** - Removes from both Beanstalkd and database (complete deletion)
|
|
204
|
+
- **`kick!`** - Moves buried job back to ready queue (for retrying failed jobs)
|
|
205
|
+
|
|
206
|
+
### [Beaneater](https://github.com/beanstalkd/beaneater) and [beanstalkd](https://raw.githubusercontent.com/beanstalkd/beanstalkd/master/doc/protocol.txt) Integration
|
|
207
|
+
|
|
208
|
+
Access Beanstalkd directly for advanced queue operations:
|
|
209
|
+
|
|
210
|
+
```ruby
|
|
211
|
+
# Get the beanstalkd job id
|
|
212
|
+
job.bkid
|
|
213
|
+
=> 1104
|
|
214
|
+
|
|
215
|
+
# Get the beaneater job object - call any beaneater methods
|
|
216
|
+
job.beanstalk_job
|
|
217
|
+
|
|
218
|
+
# Get beanstalkd stats for this job
|
|
219
|
+
job.beanstalk_job.stats
|
|
220
|
+
=> {"id"=>1104, "tube"=>"critical", "state"=>"ready", "pri"=>0, ...}
|
|
221
|
+
|
|
222
|
+
# Get a cached Backburner connection
|
|
86
223
|
c = Postburner.connection
|
|
87
224
|
c.beanstalk.tubes.to_a
|
|
88
225
|
c.beanstalk.tubes.to_a.map{|t| c.tubes[t.name].peek(:buried)}
|
|
@@ -91,10 +228,12 @@ c.beanstalk.tubes['ss.development.caching'].peek(:buried).kick
|
|
|
91
228
|
c.beanstalk.tubes['ss.development.caching'].kick(3)
|
|
92
229
|
c.close
|
|
93
230
|
|
|
94
|
-
#
|
|
231
|
+
# Automatically close connection (recommended)
|
|
95
232
|
Postburner.connected do |connection|
|
|
96
233
|
# do stuff with connection
|
|
234
|
+
connection.tubes['my.tube'].stats
|
|
97
235
|
end
|
|
236
|
+
# Connection automatically closed
|
|
98
237
|
```
|
|
99
238
|
|
|
100
239
|
Read about the [beanstalkd protocol](https://raw.githubusercontent.com/beanstalkd/beanstalkd/master/doc/protocol.txt).
|
|
@@ -188,6 +327,52 @@ class RunDonation < Postburner::Job
|
|
|
188
327
|
end
|
|
189
328
|
```
|
|
190
329
|
|
|
330
|
+
### Callbacks
|
|
331
|
+
|
|
332
|
+
Postburner provides ActiveJob-style lifecycle callbacks for job execution:
|
|
333
|
+
|
|
334
|
+
```ruby
|
|
335
|
+
class ProcessPayment < Postburner::Job
|
|
336
|
+
queue 'critical'
|
|
337
|
+
|
|
338
|
+
before_enqueue :validate_payment
|
|
339
|
+
after_enqueue :send_confirmation
|
|
340
|
+
|
|
341
|
+
before_attempt :log_attempt
|
|
342
|
+
after_attempt :update_metrics
|
|
343
|
+
|
|
344
|
+
before_processing :acquire_lock
|
|
345
|
+
after_processing :release_lock
|
|
346
|
+
|
|
347
|
+
after_processed :send_receipt # Only runs on success
|
|
348
|
+
|
|
349
|
+
def perform(args)
|
|
350
|
+
payment = Payment.find(args['payment_id'])
|
|
351
|
+
payment.process!
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
private
|
|
355
|
+
|
|
356
|
+
def validate_payment
|
|
357
|
+
raise "Invalid payment" unless args['payment_id'].present?
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
def send_confirmation
|
|
361
|
+
PaymentMailer.queued(args['payment_id']).deliver_later
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
def send_receipt
|
|
365
|
+
PaymentMailer.receipt(args['payment_id']).deliver_later
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
**Available callbacks:**
|
|
371
|
+
- `before_enqueue`, `around_enqueue`, `after_enqueue` - Run when `queue!` is called
|
|
372
|
+
- `before_attempt`, `around_attempt`, `after_attempt` - Run on every execution attempt (including retries)
|
|
373
|
+
- `before_processing`, `around_processing`, `after_processing` - Run during job execution
|
|
374
|
+
- `after_processed` - Only runs after successful completion (not on errors)
|
|
375
|
+
|
|
191
376
|
### Optionally, mount the engine
|
|
192
377
|
|
|
193
378
|
```ruby
|
|
@@ -203,6 +388,199 @@ to add your own authentication or changes - or just create your own routes, cont
|
|
|
203
388
|
[Override the views](https://guides.rubyonrails.org/engines.html#overriding-views) to make them
|
|
204
389
|
prettier - or follow the suggestion above and use your own.
|
|
205
390
|
|
|
391
|
+
## Testing
|
|
392
|
+
|
|
393
|
+
Postburner provides multiple testing strategies to make your tests fast and predictable without requiring Beanstalkd. Tests automatically use `TestQueue` when Rails test mode is detected.
|
|
394
|
+
|
|
395
|
+
### Rails and ActiveJob Test Configuration
|
|
396
|
+
|
|
397
|
+
Rails provides several configuration options that affect how background jobs behave in test environments:
|
|
398
|
+
|
|
399
|
+
#### ActiveJob Queue Adapter
|
|
400
|
+
|
|
401
|
+
The `config.active_job.queue_adapter` setting controls how ActiveJob queues jobs. Rails supports several built-in adapters:
|
|
402
|
+
|
|
403
|
+
```ruby
|
|
404
|
+
# config/environments/test.rb (or config/application.rb)
|
|
405
|
+
|
|
406
|
+
# :test adapter (Rails default for test environment)
|
|
407
|
+
# - Jobs are NOT executed automatically
|
|
408
|
+
# - Jobs accumulate in ActiveJob::Base.queue_adapter.enqueued_jobs
|
|
409
|
+
# - You must explicitly call perform_enqueued_jobs to execute them
|
|
410
|
+
config.active_job.queue_adapter = :test
|
|
411
|
+
|
|
412
|
+
# :inline adapter
|
|
413
|
+
# - Jobs execute immediately in the same process
|
|
414
|
+
# - No queueing, synchronous execution like Postburner's test strategies
|
|
415
|
+
config.active_job.queue_adapter = :inline
|
|
416
|
+
|
|
417
|
+
# :async adapter
|
|
418
|
+
# - Jobs execute asynchronously in a thread pool
|
|
419
|
+
# - Not recommended for tests due to race conditions
|
|
420
|
+
config.active_job.queue_adapter = :async
|
|
421
|
+
|
|
422
|
+
# :backburner adapter (for production use with Postburner)
|
|
423
|
+
# - Requires Beanstalkd to be running
|
|
424
|
+
# - Jobs queued to Beanstalkd tubes
|
|
425
|
+
config.active_job.queue_adapter = :backburner
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
#### ActiveJob Test Helpers
|
|
429
|
+
|
|
430
|
+
When using the `:test` adapter, Rails provides test helpers in `ActiveJob::TestHelper`:
|
|
431
|
+
|
|
432
|
+
```ruby
|
|
433
|
+
# test/test_helper.rb
|
|
434
|
+
require 'test_helper'
|
|
435
|
+
|
|
436
|
+
class MyTest < ActiveSupport::TestCase
|
|
437
|
+
include ActiveJob::TestHelper
|
|
438
|
+
|
|
439
|
+
test "job is enqueued" do
|
|
440
|
+
assert_enqueued_with(job: MyJob, args: [{user_id: 123}]) do
|
|
441
|
+
MyJob.perform_later(user_id: 123)
|
|
442
|
+
end
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
test "job is performed" do
|
|
446
|
+
perform_enqueued_jobs do
|
|
447
|
+
MyJob.perform_later(user_id: 123)
|
|
448
|
+
end
|
|
449
|
+
# Job has now executed
|
|
450
|
+
end
|
|
451
|
+
end
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
**Available helpers:**
|
|
455
|
+
- `perform_enqueued_jobs` - Executes all enqueued jobs
|
|
456
|
+
- `assert_enqueued_jobs` - Asserts number of jobs enqueued
|
|
457
|
+
- `assert_enqueued_with` - Asserts specific job was enqueued with args
|
|
458
|
+
- `assert_no_enqueued_jobs` - Asserts no jobs were enqueued
|
|
459
|
+
- `assert_performed_jobs` - Asserts number of jobs performed
|
|
460
|
+
- `queue_adapter.enqueued_jobs` - Array of enqueued jobs for inspection
|
|
461
|
+
|
|
462
|
+
#### Other ActiveJob Configuration Options
|
|
463
|
+
|
|
464
|
+
```ruby
|
|
465
|
+
# config/application.rb or config/environments/test.rb
|
|
466
|
+
|
|
467
|
+
# Set default queue name for all jobs
|
|
468
|
+
config.active_job.queue_name_prefix = "myapp_#{Rails.env}"
|
|
469
|
+
|
|
470
|
+
# Skip after_enqueue callbacks if before_enqueue halts
|
|
471
|
+
config.active_job.skip_after_callbacks_if_terminated = true
|
|
472
|
+
|
|
473
|
+
# Log arguments when jobs are enqueued (useful for debugging)
|
|
474
|
+
config.active_job.log_arguments = true # default: true
|
|
475
|
+
|
|
476
|
+
# Set queue priority (if adapter supports it)
|
|
477
|
+
config.active_job.default_queue_name = 'default'
|
|
478
|
+
|
|
479
|
+
# Configure job serialization
|
|
480
|
+
config.active_job.custom_serializers << MyCustomSerializer
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
### Postburner Test Modes vs ActiveJob Adapters
|
|
484
|
+
|
|
485
|
+
**Key Difference:** Postburner jobs (subclasses of `Postburner::Job`) do NOT use ActiveJob's queue adapter. Postburner has its own queue strategies that control execution independently of ActiveJob settings.
|
|
486
|
+
|
|
487
|
+
| Postburner Strategy | Execution | Use Case | ActiveJob Equivalent |
|
|
488
|
+
|---------------------|-----------|----------|----------------------|
|
|
489
|
+
| `TestQueue` | Inline, raises on scheduled | Explicit time control | `:inline` adapter |
|
|
490
|
+
| `ImmediateTestQueue` | Inline with auto time-travel | Convenience testing | `:inline` adapter + `travel_to` |
|
|
491
|
+
| `NullQueue` | Deferred, manual execution | Batch processing | `:test` adapter (manual `perform_enqueued_jobs`) |
|
|
492
|
+
| `Queue` / `NiceQueue` | Async via Beanstalkd | Production | `:backburner` adapter |
|
|
493
|
+
|
|
494
|
+
**When to use what:**
|
|
495
|
+
- **ActiveJob with `:test` adapter:** For testing standard ActiveJob classes (mailers, ActiveStorage, etc.)
|
|
496
|
+
- **Postburner test strategies:** For testing `Postburner::Job` subclasses
|
|
497
|
+
- Both can coexist in the same test suite with different behaviors
|
|
498
|
+
|
|
499
|
+
### Automatic Test Mode Detection
|
|
500
|
+
|
|
501
|
+
In Rails test environments, Postburner automatically switches to `TestQueue`:
|
|
502
|
+
|
|
503
|
+
```ruby
|
|
504
|
+
# Happens automatically when:
|
|
505
|
+
# - Rails.env.test? is true
|
|
506
|
+
# - ActiveJob::Base.queue_adapter_name == :test
|
|
507
|
+
|
|
508
|
+
# Verify you're in test mode
|
|
509
|
+
Postburner.testing? # => true in tests
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
**Note:** Even though `TestQueue` is set automatically in Rails test environments, you can override it at any time to use a different test strategy:
|
|
513
|
+
|
|
514
|
+
```ruby
|
|
515
|
+
# In test_helper.rb or specific tests
|
|
516
|
+
Postburner.inline_immediate_test_strategy! # Use automatic time travel
|
|
517
|
+
# OR
|
|
518
|
+
Postburner.null_strategy! # Use deferred execution
|
|
519
|
+
|
|
520
|
+
# The auto-detection only sets the initial strategy
|
|
521
|
+
# You can change it anytime during tests
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
### TestQueue: Explicit Time Control (Default for Tests)
|
|
525
|
+
|
|
526
|
+
The `TestQueue` strategy forces explicit time management, helping catch scheduling bugs:
|
|
527
|
+
|
|
528
|
+
```ruby
|
|
529
|
+
test "processes job immediately" do
|
|
530
|
+
job = MyJob.create!(args: { user_id: 123 })
|
|
531
|
+
job.queue!
|
|
532
|
+
assert job.reload.processed_at
|
|
533
|
+
end
|
|
534
|
+
|
|
535
|
+
test "processes scheduled job with time travel" do
|
|
536
|
+
job = MyJob.create!(args: { user_id: 123 })
|
|
537
|
+
job.queue!(delay: 2.hours)
|
|
538
|
+
|
|
539
|
+
# Must use travel_to for scheduled jobs
|
|
540
|
+
travel_to(job.run_at) do
|
|
541
|
+
assert job.reload.processed_at
|
|
542
|
+
end
|
|
543
|
+
end
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
### ImmediateTestQueue: Automatic Time Travel
|
|
547
|
+
|
|
548
|
+
For convenience in integration tests, `ImmediateTestQueue` automatically handles scheduled jobs:
|
|
549
|
+
|
|
550
|
+
```ruby
|
|
551
|
+
test "scheduled jobs execute immediately" do
|
|
552
|
+
Postburner.inline_immediate_test_strategy!
|
|
553
|
+
|
|
554
|
+
job = SendReminderEmail.create!(args: { user_id: 123 })
|
|
555
|
+
job.queue!(delay: 24.hours)
|
|
556
|
+
|
|
557
|
+
# Automatically time-travels to scheduled time
|
|
558
|
+
assert job.reload.processed_at
|
|
559
|
+
assert_emails 1
|
|
560
|
+
end
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
### NullQueue: Deferred Execution
|
|
564
|
+
|
|
565
|
+
Create jobs without queueing, then manually execute them:
|
|
566
|
+
|
|
567
|
+
```ruby
|
|
568
|
+
test "batch processes jobs" do
|
|
569
|
+
Postburner.null_strategy!
|
|
570
|
+
|
|
571
|
+
jobs = 5.times.map do |i|
|
|
572
|
+
job = ProcessRecord.create!(args: { id: i })
|
|
573
|
+
job.queue!
|
|
574
|
+
job
|
|
575
|
+
end
|
|
576
|
+
|
|
577
|
+
# Manually execute when ready
|
|
578
|
+
jobs.each { |job| Postburner::Job.perform(job.id) }
|
|
579
|
+
|
|
580
|
+
assert jobs.all? { |j| j.reload.processed_at }
|
|
581
|
+
end
|
|
582
|
+
```
|
|
583
|
+
|
|
206
584
|
## Installation
|
|
207
585
|
|
|
208
586
|
First [install beanstalkd](https://beanstalkd.github.io/download.html). On Debian-based systems, that goes like this:
|
|
@@ -343,5 +721,51 @@ bundle exec backburner
|
|
|
343
721
|
Where <nac> is an authorized key with push capabilities.
|
|
344
722
|
|
|
345
723
|
|
|
724
|
+
## FAQ
|
|
725
|
+
|
|
726
|
+
### Can I save a Postburner::Job without enqueuing it?
|
|
727
|
+
|
|
728
|
+
Yes! You can absolutely save a `Postburner::Job` without enqueuing it. Looking at the code, here's how it works:
|
|
729
|
+
|
|
730
|
+
#### Creating vs. Queueing
|
|
731
|
+
|
|
732
|
+
When you use `create!` or `save!`, the job is persisted to the database but **not** sent to Beanstalkd:
|
|
733
|
+
|
|
734
|
+
```ruby
|
|
735
|
+
# Creates job in database but does NOT enqueue
|
|
736
|
+
job = RunDonation.create!(args: {donation_id: 123})
|
|
737
|
+
job.persisted? # => true
|
|
738
|
+
job.queued_at # => nil
|
|
739
|
+
job.bkid # => nil (no Beanstalkd ID)
|
|
740
|
+
```
|
|
741
|
+
|
|
742
|
+
To actually enqueue it, you must explicitly call `queue!`:
|
|
743
|
+
|
|
744
|
+
```ruby
|
|
745
|
+
# Now it's enqueued to Beanstalkd
|
|
746
|
+
job.queue!(delay: 1.hour)
|
|
747
|
+
job.queued_at # => Time.zone.now
|
|
748
|
+
job.bkid # => 12345 (Beanstalkd job ID)
|
|
749
|
+
```
|
|
750
|
+
|
|
751
|
+
#### How it Works
|
|
752
|
+
|
|
753
|
+
The implementation uses a clever pattern (app/models/postburner/job.rb:86-724):
|
|
754
|
+
|
|
755
|
+
1. **after_save_commit callback** (`insert_if_queued!` at line 86) runs on every save
|
|
756
|
+
2. **Conditional insertion** (line 722) - only inserts if `will_insert?` returns true
|
|
757
|
+
3. **Flag check** (lines 242-244) - `will_insert?` only returns true if `@_insert_options` is set
|
|
758
|
+
4. **Flag set** (line 199) - `@_insert_options` is only set inside the `queue!` method
|
|
759
|
+
|
|
760
|
+
So the job is safely persisted without queueing until you explicitly call `queue!`.
|
|
761
|
+
|
|
762
|
+
#### Use Cases
|
|
763
|
+
|
|
764
|
+
This is useful for:
|
|
765
|
+
- Creating jobs conditionally queued based on business logic
|
|
766
|
+
- Building jobs within transactions that might roll back
|
|
767
|
+
- Testing job creation without running workers
|
|
768
|
+
- Creating foreign key relationships before queueing
|
|
769
|
+
|
|
346
770
|
## License
|
|
347
771
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|