lev 7.0.0 → 7.0.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 +5 -13
- data/README.md +13 -31
- data/lib/lev.rb +13 -6
- data/lib/lev/active_job.rb +8 -9
- data/lib/lev/error.rb +1 -5
- data/lib/lev/errors.rb +6 -6
- data/lib/lev/null_status.rb +34 -0
- data/lib/lev/routine.rb +11 -14
- data/lib/lev/version.rb +1 -1
- data/spec/active_job_routines_spec.rb +7 -20
- data/spec/spec_helper.rb +1 -2
- data/spec/statused_routines_spec.rb +17 -139
- data/spec/support/jobba.rb +13 -0
- metadata +50 -37
- data/lib/lev/background_job.rb +0 -229
- data/lib/lev/no_background_job.rb +0 -26
- data/spec/background_job_spec.rb +0 -129
checksums.yaml
CHANGED
@@ -1,15 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
5
|
-
data.tar.gz: !binary |-
|
6
|
-
OGVmOWM3NzViMDdlY2UwNTY0MDdmZTM1MGE1MWMyMTQ5NDc1OGRlOQ==
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ef0f435c1b9d31f93f019ec4ed9a39a7f5168fd2
|
4
|
+
data.tar.gz: 7b20edd8fc30967cae1a66e409d42d5ec45a54dd
|
7
5
|
SHA512:
|
8
|
-
metadata.gz:
|
9
|
-
|
10
|
-
M2NmOWI5YWQyZTc3ZGU3MmFkNTk3YWE2YjZiZDgzZTJmMjI3M2Y5YThjMGI1
|
11
|
-
OTRjMjcyNWEyNTdlZmI2MmY5NDg5MzI4YWJmNGMwZmU2MTA3MDY=
|
12
|
-
data.tar.gz: !binary |-
|
13
|
-
ZDY5MGZiZGMwNzgxZDExZGE0Njk1ZTRiMDU0YzExYjNjZDRjNWI4NzJkZTdl
|
14
|
-
M2RjODNkNzUyMDYyNzUxNjYzZjhiYWNkZGEyOTJjY2RiMzFjOTY0MjFjNmEx
|
15
|
-
MGYzYzY5YzUzYjRkMDRkNDUyZmRhYjRjYTY4YjA0ZjNjMmVmZjE=
|
6
|
+
metadata.gz: c423328fc9d60a3583776a51d778c0b13252da9f3f93594a3765ed445893e12207b1fd11ac97394a64e8ca6443c478f8d2789596bf01bef40726a1644e2030de
|
7
|
+
data.tar.gz: 0fbf4737caa577039695222f8bf272d7c9f99aaeca9ac57116ce9be8fb139ce5ece6280c1f8fe4b2448b22d88c16a3400baf4efbf761a7011b8fb98e2f21dec9
|
data/README.md
CHANGED
@@ -432,48 +432,30 @@ end
|
|
432
432
|
|
433
433
|
Routines run as ActiveJobs can also publish their status somewhere it can be listened to (e.g. to Redis).
|
434
434
|
|
435
|
-
Routines have a `
|
435
|
+
Routines have a `status` object and can call the following methods:
|
436
436
|
|
437
437
|
* `set_progress(at, out_of = nil)` sets the current progress; can either pass a float between 0.0 and 1.0 or
|
438
438
|
a counter towards a total, e.g. `set_progress(67,212)`.
|
439
|
-
* `queued!` Sets the
|
440
|
-
* `
|
441
|
-
* `succeeded!` Sets the
|
442
|
-
* `failed!` Sets the
|
443
|
-
* `killed!` Sets the
|
444
|
-
* `save(hash)` Takes a hash of key value pairs and writes those keys and values to the
|
445
|
-
* `add_error(
|
439
|
+
* `queued!` Sets the state to 'queued'
|
440
|
+
* `started!` Sets the state to 'working'
|
441
|
+
* `succeeded!` Sets the state to 'succeeded'
|
442
|
+
* `failed!` Sets the state to 'failed'
|
443
|
+
* `killed!` Sets the state to 'killed'
|
444
|
+
* `save(hash)` Takes a hash of key value pairs and writes those keys and values to the status; there are several reserved keys which cannot be used (and which will blow up if you try to use them)
|
445
|
+
* `add_error(error)` takes a Lev `Error` object and adds its data to an array of `errors` in the job status hash.
|
446
446
|
|
447
|
-
Routine
|
447
|
+
Routine status objects also have query methods to check if a status is in a given state, e.g. `queued?`. `completed?` and `incomplete` convenience methods are provided as well. A status is complete if it is failed or succeeded; incomplete if neither. All job routines start in an `unqueued` state and will only stay there if queueing had a problem. Scope-like class methods are provided to return all statuses in a given state.
|
448
448
|
|
449
|
-
For plain vanilla routines not run as an active job, the
|
449
|
+
For plain vanilla routines not run as an active job, the status calls are no-ops. When a routine is invoked with `perform_later`, the status object is created/found using two configuration options that must be set (if you care about the status):
|
450
450
|
|
451
451
|
```ruby
|
452
452
|
Lev.configure do |config|
|
453
|
-
config.
|
453
|
+
config.create_status_proc = ->(*) { Jobba::Status.create! }
|
454
|
+
config.find_status_proc = ->(id) { Jobba::Status.find!(id) }
|
454
455
|
end
|
455
456
|
```
|
456
457
|
|
457
|
-
|
458
|
-
|
459
|
-
1. fetch(key)
|
460
|
-
2. write(key, value)
|
461
|
-
|
462
|
-
The default store is essentially a hash (implemented in `Lev::MemoryStore`). Any `ActiveSupport::Cache::Store` will work.
|
463
|
-
|
464
|
-
A routine's job can be retrieved with `Lev::BackgroundJob.get(uuid_here)`. This just returns a simple hash. Notable keys are
|
465
|
-
|
466
|
-
* `'progress'` which returns the progress as a number between 0.0 and 1.0
|
467
|
-
* `'status'` which returns one of the status strings shown above
|
468
|
-
* `'errors'` which (if present) is an error of error hashes
|
469
|
-
* `'id'` the UUID of the routine / job
|
470
|
-
|
471
|
-
Other routine-specific keys (set with a `save` call) are also present.
|
472
|
-
|
473
|
-
**Notes:**
|
474
|
-
|
475
|
-
1. Don't try to write a job store that uses the ActiveRecord database, as the database changes would only be seen when the routine completes and its transaction is committed.
|
476
|
-
2. Job killing hasn't been implemented yet, but shouldn't be too bad. For routines run in a transaction (which are frankly the only ones you'd want to kill), we can likely kill them by raising an exception or similar to cause a rollback (will need to have good tests to prove that).
|
458
|
+
See the [Jobba README](https://github.com/openstax/jobba) for full details on the status objects.
|
477
459
|
|
478
460
|
## Handlers
|
479
461
|
|
data/lib/lev.rb
CHANGED
@@ -26,12 +26,19 @@ require "lev/delegate_to_routine"
|
|
26
26
|
require "lev/transaction_isolation"
|
27
27
|
|
28
28
|
require 'lev/memory_store'
|
29
|
-
require 'lev/
|
30
|
-
require 'lev/no_background_job'
|
29
|
+
require 'lev/null_status'
|
31
30
|
|
32
31
|
module Lev
|
33
32
|
class << self
|
34
33
|
|
34
|
+
def create_status
|
35
|
+
configuration.create_status_proc.call
|
36
|
+
end
|
37
|
+
|
38
|
+
def find_status(id)
|
39
|
+
configuration.find_status_proc.call(id)
|
40
|
+
end
|
41
|
+
|
35
42
|
###########################################################################
|
36
43
|
#
|
37
44
|
# Configuration machinery.
|
@@ -64,8 +71,8 @@ module Lev
|
|
64
71
|
attr_accessor :security_transgression_error
|
65
72
|
attr_accessor :illegal_argument_error
|
66
73
|
attr_accessor :raise_fatal_errors
|
67
|
-
attr_accessor :
|
68
|
-
attr_accessor :
|
74
|
+
attr_accessor :create_status_proc
|
75
|
+
attr_accessor :find_status_proc
|
69
76
|
attr_accessor :job_class
|
70
77
|
|
71
78
|
def initialize
|
@@ -73,8 +80,8 @@ module Lev
|
|
73
80
|
@security_transgression_error = Lev::SecurityTransgression
|
74
81
|
@illegal_argument_error = Lev::IllegalArgument
|
75
82
|
@raise_fatal_errors = false
|
76
|
-
@
|
77
|
-
@
|
83
|
+
@create_status_proc = ->(*) { NullStatus.new }
|
84
|
+
@find_status_proc = ->(*) { NullStatus.new }
|
78
85
|
@job_class = ::ActiveJob::Base
|
79
86
|
super
|
80
87
|
end
|
data/lib/lev/active_job.rb
CHANGED
@@ -5,19 +5,18 @@ module Lev
|
|
5
5
|
queue_as routine_class.active_job_queue
|
6
6
|
args.push(routine_class.to_s)
|
7
7
|
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
|
12
|
-
|
13
|
-
args.push(job.id)
|
8
|
+
# Create a new status object and push its ID on to the arguments so that
|
9
|
+
# in `perform` it can be used to retrieve the status when the routine is
|
10
|
+
# initialized.
|
11
|
+
status = Lev::create_status
|
12
|
+
args.push(status.id)
|
14
13
|
|
15
14
|
# In theory we'd mark as queued right after the call to super, but this messes
|
16
15
|
# up when the activejob adapter runs the job right away
|
17
|
-
|
16
|
+
status.queued!
|
18
17
|
super(*args, &block)
|
19
18
|
|
20
|
-
|
19
|
+
status.id
|
21
20
|
end
|
22
21
|
|
23
22
|
def perform(*args, &block)
|
@@ -25,7 +24,7 @@ module Lev
|
|
25
24
|
id = args.pop
|
26
25
|
routine_class = Kernel.const_get(args.pop)
|
27
26
|
|
28
|
-
routine_instance = routine_class.new(Lev::
|
27
|
+
routine_instance = routine_class.new(Lev::find_status(id))
|
29
28
|
|
30
29
|
routine_instance.call(*args, &block)
|
31
30
|
end
|
data/lib/lev/error.rb
CHANGED
data/lib/lev/errors.rb
CHANGED
@@ -4,8 +4,8 @@ module Lev
|
|
4
4
|
#
|
5
5
|
class Errors < Array
|
6
6
|
|
7
|
-
def initialize(
|
8
|
-
@
|
7
|
+
def initialize(routine_status = nil, raise_fatal_errors = false)
|
8
|
+
@routine_status = routine_status || NullStatus.new
|
9
9
|
@raise_fatal_errors = raise_fatal_errors
|
10
10
|
end
|
11
11
|
|
@@ -16,13 +16,13 @@ module Lev
|
|
16
16
|
return if ignored_error_procs.any?{|proc| proc.call(error)}
|
17
17
|
self.push(error)
|
18
18
|
|
19
|
-
|
19
|
+
routine_status.add_error(error)
|
20
20
|
|
21
21
|
if fail
|
22
|
-
|
22
|
+
routine_status.failed!
|
23
23
|
|
24
24
|
if raise_fatal_errors
|
25
|
-
# Use special FatalError type so Routine doesn't re-add
|
25
|
+
# Use special FatalError type so Routine doesn't re-add status errors
|
26
26
|
raise Lev::FatalError, args.to_a.map { |i| i.join(' ') }.join(' - ')
|
27
27
|
else
|
28
28
|
throw :fatal_errors_encountered
|
@@ -55,7 +55,7 @@ module Lev
|
|
55
55
|
|
56
56
|
protected
|
57
57
|
|
58
|
-
attr_reader :
|
58
|
+
attr_reader :routine_status
|
59
59
|
attr_reader :raise_fatal_errors
|
60
60
|
|
61
61
|
def ignored_error_procs
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class Lev::NullStatus
|
2
|
+
attr_reader :id
|
3
|
+
|
4
|
+
def initialize(id=nil)
|
5
|
+
@id = id || "null-status:#{SecureRandom.uuid}"
|
6
|
+
@kill_requested = false
|
7
|
+
end
|
8
|
+
|
9
|
+
def request_kill!
|
10
|
+
@kill_requested = true
|
11
|
+
end
|
12
|
+
|
13
|
+
def kill_requested?
|
14
|
+
@kill_requested
|
15
|
+
end
|
16
|
+
|
17
|
+
def method_missing(*args, &block)
|
18
|
+
nil
|
19
|
+
end
|
20
|
+
|
21
|
+
# Provide null object pattern methods for status setter methods called from
|
22
|
+
# within routines; routines should not be using other query methods to check
|
23
|
+
# their own status (they should know it), with the exception of `kill_requested?`
|
24
|
+
|
25
|
+
def set_progress(*); end
|
26
|
+
def save(*); end
|
27
|
+
def add_error(*); end
|
28
|
+
|
29
|
+
def queued!; end
|
30
|
+
def started!; end
|
31
|
+
def succeeded!; end
|
32
|
+
def failed!; end
|
33
|
+
def killed!; end
|
34
|
+
end
|
data/lib/lev/routine.rb
CHANGED
@@ -265,7 +265,7 @@ module Lev
|
|
265
265
|
def call(*args, &block)
|
266
266
|
@after_transaction_blocks = []
|
267
267
|
|
268
|
-
|
268
|
+
status.started!
|
269
269
|
|
270
270
|
begin
|
271
271
|
in_transaction do
|
@@ -284,20 +284,20 @@ module Lev
|
|
284
284
|
block.call
|
285
285
|
end
|
286
286
|
rescue Exception => e
|
287
|
-
# Let exceptions escape but make sure to note the error in the
|
287
|
+
# Let exceptions escape but make sure to note the error in the status
|
288
288
|
# if not already done
|
289
289
|
if !e.is_a?(Lev::FatalError)
|
290
290
|
error = Error.new(code: :exception,
|
291
291
|
message: e.message,
|
292
292
|
data: e.backtrace.first)
|
293
|
-
|
294
|
-
|
293
|
+
status.add_error(error)
|
294
|
+
status.failed!
|
295
295
|
end
|
296
296
|
|
297
297
|
raise e
|
298
298
|
end
|
299
299
|
|
300
|
-
|
300
|
+
status.succeeded! if !errors?
|
301
301
|
|
302
302
|
result
|
303
303
|
end
|
@@ -430,23 +430,20 @@ module Lev
|
|
430
430
|
|
431
431
|
# Note that the parent may neglect to call super, leading to this method never being called.
|
432
432
|
# Do not perform any initialization here that cannot be safely skipped
|
433
|
-
def initialize(
|
434
|
-
# If someone cares about the
|
435
|
-
#
|
436
|
-
@
|
433
|
+
def initialize(status = nil)
|
434
|
+
# If someone cares about the status, they'll pass it in; otherwise all
|
435
|
+
# status updates go into the bit bucket.
|
436
|
+
@status = status || Lev::NullStatus.new
|
437
437
|
end
|
438
438
|
|
439
439
|
protected
|
440
440
|
|
441
441
|
attr_writer :runner
|
442
|
-
|
443
|
-
def job
|
444
|
-
@job ||= Lev::NoBackgroundJob.new
|
445
|
-
end
|
442
|
+
attr_reader :status
|
446
443
|
|
447
444
|
def result
|
448
445
|
@result ||= Result.new(Outputs.new,
|
449
|
-
Errors.new(
|
446
|
+
Errors.new(status, topmost_runner.class.raise_fatal_errors?))
|
450
447
|
end
|
451
448
|
|
452
449
|
def reset_result!
|
data/lib/lev/version.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
RSpec.describe 'ActiveJob routines' do
|
4
|
+
|
5
|
+
before { Lev::Jobba.use_jobba }
|
6
|
+
|
4
7
|
context 'default configuration' do
|
5
8
|
class LaterRoutine
|
6
9
|
lev_routine active_job_queue: :something_else
|
@@ -24,22 +27,6 @@ RSpec.describe 'ActiveJob routines' do
|
|
24
27
|
|
25
28
|
expect(queue_name).to eq('something_else')
|
26
29
|
end
|
27
|
-
|
28
|
-
it 'stores all the UUIDs of queued jobs' do
|
29
|
-
Lev.configuration.job_store.clear
|
30
|
-
|
31
|
-
job_id1 = LaterRoutine.perform_later
|
32
|
-
|
33
|
-
expect(Lev::BackgroundJob.send(:job_ids)).to eq([job_id1])
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
it 'does not duplicate BackgroundJobs in `all`' do
|
38
|
-
# Previous track_job_id implementation changed string objects in job_ids
|
39
|
-
# resulting in duplicate objects in `all`
|
40
|
-
Lev.configuration.job_store.clear
|
41
|
-
LaterRoutine.perform_later
|
42
|
-
expect(Lev::BackgroundJob.all.count).to eq 1
|
43
30
|
end
|
44
31
|
|
45
32
|
context 'exception raised' do
|
@@ -56,17 +43,17 @@ RSpec.describe 'ActiveJob routines' do
|
|
56
43
|
end
|
57
44
|
|
58
45
|
it 'lets exception escape, job is failed and has error details' do
|
59
|
-
|
46
|
+
Jobba.all.delete_all!
|
60
47
|
|
61
48
|
expect{
|
62
49
|
ExceptionalRoutine.perform_later
|
63
50
|
}.to raise_error(TypeError)
|
64
51
|
|
65
|
-
|
52
|
+
status = Jobba.all.run.to_a.first
|
66
53
|
|
67
|
-
expect(
|
54
|
+
expect(status).to be_failed
|
68
55
|
|
69
|
-
error =
|
56
|
+
error = status.errors.first
|
70
57
|
|
71
58
|
expect(error["code"]).to eq "exception"
|
72
59
|
expect(error["message"]).to eq "howdy there"
|
data/spec/spec_helper.rb
CHANGED
@@ -5,166 +5,44 @@ class StatusedRoutine
|
|
5
5
|
|
6
6
|
protected
|
7
7
|
def exec
|
8
|
-
|
8
|
+
status.set_progress(9, 10)
|
9
|
+
status.save({'hi' => 'there'})
|
10
|
+
fatal_error(code: 'blah', message: 'hi')
|
9
11
|
end
|
10
12
|
end
|
11
13
|
|
12
14
|
RSpec.describe 'Statused Routines' do
|
13
|
-
|
15
|
+
|
16
|
+
before { Lev::Jobba.use_jobba }
|
14
17
|
|
15
18
|
context 'in a routine' do
|
16
19
|
it 'queues the job object on queue' do
|
17
20
|
id = StatusedRoutine.perform_later
|
18
|
-
|
21
|
+
status = Jobba::Status.find(id)
|
19
22
|
|
20
|
-
expect(
|
23
|
+
expect(status).to be_queued
|
21
24
|
end
|
22
25
|
|
23
26
|
context 'inline activejob mode' do
|
24
27
|
before { ::ActiveJob::Base.queue_adapter = :inline }
|
25
28
|
after { ::ActiveJob::Base.queue_adapter = :test }
|
26
29
|
|
27
|
-
it 'sets job to
|
28
|
-
expect_any_instance_of(
|
30
|
+
it 'sets job to started when called' do
|
31
|
+
expect_any_instance_of(Jobba::Status).to receive(:started!)
|
29
32
|
StatusedRoutine.perform_later
|
30
33
|
end
|
31
34
|
|
32
|
-
it 'completes the
|
35
|
+
it 'completes the status object on completion, returning other data' do
|
33
36
|
id = StatusedRoutine.perform_later
|
34
|
-
|
35
|
-
expect(
|
36
|
-
expect(
|
37
|
+
status = Jobba::Status.find(id)
|
38
|
+
expect(status).to be_failed
|
39
|
+
expect(status.progress).to eq(0.9)
|
40
|
+
expect(status.errors).to contain_exactly(
|
41
|
+
a_hash_including({'code' => 'blah', 'message' => 'hi'})
|
42
|
+
)
|
43
|
+
expect(status.data).to eq ({'hi' => 'there'})
|
37
44
|
end
|
38
45
|
end
|
39
46
|
end
|
40
47
|
|
41
|
-
describe '#save' do
|
42
|
-
it 'saves the hash given and writes them to the job' do
|
43
|
-
job.save(something: 'else')
|
44
|
-
expect(job.something).to eq('else')
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
describe '#add_error' do
|
49
|
-
it 'adds the error object data to the job object' do
|
50
|
-
errors = Lev::Error.new(code: 'bad', message: 'awful')
|
51
|
-
job.add_error(errors)
|
52
|
-
expect(job.errors).to eq([{ is_fatal: false,
|
53
|
-
code: 'bad',
|
54
|
-
message: 'awful',
|
55
|
-
data: nil }])
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
describe '#save' do
|
60
|
-
it 'prevents the use of reserved keys' do
|
61
|
-
expect {
|
62
|
-
job.save(progress: 'blocked')
|
63
|
-
}.to raise_error(Lev::IllegalArgument)
|
64
|
-
|
65
|
-
expect {
|
66
|
-
job.save(id: 'blocked')
|
67
|
-
}.to raise_error(Lev::IllegalArgument)
|
68
|
-
|
69
|
-
expect {
|
70
|
-
job.save(status: 'blocked')
|
71
|
-
}.to raise_error(Lev::IllegalArgument)
|
72
|
-
|
73
|
-
expect {
|
74
|
-
job.save(errors: 'blocked')
|
75
|
-
}.to raise_error(Lev::IllegalArgument)
|
76
|
-
end
|
77
|
-
|
78
|
-
it 'saves the hash given and writes them to the job' do
|
79
|
-
job.save(something: 'else')
|
80
|
-
expect(job).to respond_to(:something)
|
81
|
-
expect(job.something).to eq('else')
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
describe 'dynamic job setters/getters' do
|
86
|
-
it 'is queued' do
|
87
|
-
expect(job).not_to be_queued
|
88
|
-
job.queued!
|
89
|
-
expect(job).to be_queued
|
90
|
-
end
|
91
|
-
|
92
|
-
it 'is working' do
|
93
|
-
expect(job).not_to be_working
|
94
|
-
job.working!
|
95
|
-
expect(job).to be_working
|
96
|
-
end
|
97
|
-
|
98
|
-
it 'is succeeded' do
|
99
|
-
expect(job).not_to be_succeeded
|
100
|
-
job.succeeded!
|
101
|
-
expect(job).to be_succeeded
|
102
|
-
end
|
103
|
-
|
104
|
-
it 'is failed' do
|
105
|
-
expect(job).not_to be_failed
|
106
|
-
job.failed!
|
107
|
-
expect(job).to be_failed
|
108
|
-
end
|
109
|
-
|
110
|
-
it 'is killed' do
|
111
|
-
expect(job).not_to be_killed
|
112
|
-
job.killed!
|
113
|
-
expect(job).to be_killed
|
114
|
-
end
|
115
|
-
|
116
|
-
it 'is unknown' do
|
117
|
-
expect(job).to be_unknown
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
describe '#set_progress' do
|
122
|
-
it 'sets the progress key on the job object' do
|
123
|
-
job.set_progress(8, 10)
|
124
|
-
expect(job.progress).to eq(0.8)
|
125
|
-
end
|
126
|
-
|
127
|
-
context 'when `out_of` is supplied' do
|
128
|
-
it 'requires a positive `at` float or integer' do
|
129
|
-
expect {
|
130
|
-
job.set_progress(nil, 1)
|
131
|
-
}.to raise_error(Lev::IllegalArgument)
|
132
|
-
|
133
|
-
expect {
|
134
|
-
job.set_progress(-1, 1)
|
135
|
-
}.to raise_error(Lev::IllegalArgument)
|
136
|
-
|
137
|
-
expect {
|
138
|
-
job.set_progress(2, 5)
|
139
|
-
}.not_to raise_error
|
140
|
-
end
|
141
|
-
|
142
|
-
it 'requires `out_of` to be greater than `at`' do
|
143
|
-
expect {
|
144
|
-
job.set_progress(15, 8)
|
145
|
-
}.to raise_error(Lev::IllegalArgument)
|
146
|
-
|
147
|
-
expect {
|
148
|
-
job.set_progress(5, 10)
|
149
|
-
}.not_to raise_error
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
|
-
context 'without out_of specified' do
|
154
|
-
it 'requires `at` to be a float between 0.0 and 1.0' do
|
155
|
-
expect {
|
156
|
-
job.set_progress(1.1)
|
157
|
-
}.to raise_error(Lev::IllegalArgument)
|
158
|
-
|
159
|
-
expect {
|
160
|
-
job.set_progress(-1)
|
161
|
-
}.to raise_error(Lev::IllegalArgument)
|
162
|
-
|
163
|
-
expect {
|
164
|
-
job.set_progress(0.78)
|
165
|
-
}.not_to raise_error
|
166
|
-
end
|
167
|
-
end
|
168
|
-
|
169
|
-
end
|
170
48
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lev
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 7.0.
|
4
|
+
version: 7.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- JP Slavinsky
|
@@ -14,196 +14,210 @@ dependencies:
|
|
14
14
|
name: activemodel
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '4.2'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- -
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '4.2'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: activerecord
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- -
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '4.2'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- -
|
38
|
+
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '4.2'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: actionpack
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- -
|
45
|
+
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
47
|
version: '4.2'
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- -
|
52
|
+
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '4.2'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: activejob
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- -
|
59
|
+
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: '0'
|
62
62
|
type: :runtime
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- -
|
66
|
+
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: transaction_isolation
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
|
-
- -
|
73
|
+
- - ">="
|
74
74
|
- !ruby/object:Gem::Version
|
75
75
|
version: '0'
|
76
76
|
type: :runtime
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
|
-
- -
|
80
|
+
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: transaction_retry
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
|
-
- -
|
87
|
+
- - ">="
|
88
88
|
- !ruby/object:Gem::Version
|
89
89
|
version: '0'
|
90
90
|
type: :runtime
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
|
-
- -
|
94
|
+
- - ">="
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '0'
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
98
|
name: active_attr
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
100
100
|
requirements:
|
101
|
-
- -
|
101
|
+
- - ">="
|
102
102
|
- !ruby/object:Gem::Version
|
103
103
|
version: '0'
|
104
104
|
type: :runtime
|
105
105
|
prerelease: false
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
107
107
|
requirements:
|
108
|
-
- -
|
108
|
+
- - ">="
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '0'
|
111
111
|
- !ruby/object:Gem::Dependency
|
112
112
|
name: hashie
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
114
114
|
requirements:
|
115
|
-
- -
|
115
|
+
- - ">="
|
116
116
|
- !ruby/object:Gem::Version
|
117
117
|
version: '0'
|
118
118
|
type: :runtime
|
119
119
|
prerelease: false
|
120
120
|
version_requirements: !ruby/object:Gem::Requirement
|
121
121
|
requirements:
|
122
|
-
- -
|
122
|
+
- - ">="
|
123
123
|
- !ruby/object:Gem::Version
|
124
124
|
version: '0'
|
125
125
|
- !ruby/object:Gem::Dependency
|
126
126
|
name: bundler
|
127
127
|
requirement: !ruby/object:Gem::Requirement
|
128
128
|
requirements:
|
129
|
-
- -
|
129
|
+
- - ">="
|
130
130
|
- !ruby/object:Gem::Version
|
131
131
|
version: '0'
|
132
132
|
type: :development
|
133
133
|
prerelease: false
|
134
134
|
version_requirements: !ruby/object:Gem::Requirement
|
135
135
|
requirements:
|
136
|
-
- -
|
136
|
+
- - ">="
|
137
137
|
- !ruby/object:Gem::Version
|
138
138
|
version: '0'
|
139
139
|
- !ruby/object:Gem::Dependency
|
140
140
|
name: rake
|
141
141
|
requirement: !ruby/object:Gem::Requirement
|
142
142
|
requirements:
|
143
|
-
- -
|
143
|
+
- - ">="
|
144
144
|
- !ruby/object:Gem::Version
|
145
145
|
version: '0'
|
146
146
|
type: :development
|
147
147
|
prerelease: false
|
148
148
|
version_requirements: !ruby/object:Gem::Requirement
|
149
149
|
requirements:
|
150
|
-
- -
|
150
|
+
- - ">="
|
151
151
|
- !ruby/object:Gem::Version
|
152
152
|
version: '0'
|
153
153
|
- !ruby/object:Gem::Dependency
|
154
154
|
name: rspec
|
155
155
|
requirement: !ruby/object:Gem::Requirement
|
156
156
|
requirements:
|
157
|
-
- -
|
157
|
+
- - ">="
|
158
158
|
- !ruby/object:Gem::Version
|
159
159
|
version: '0'
|
160
160
|
type: :development
|
161
161
|
prerelease: false
|
162
162
|
version_requirements: !ruby/object:Gem::Requirement
|
163
163
|
requirements:
|
164
|
-
- -
|
164
|
+
- - ">="
|
165
165
|
- !ruby/object:Gem::Version
|
166
166
|
version: '0'
|
167
167
|
- !ruby/object:Gem::Dependency
|
168
168
|
name: sqlite3
|
169
169
|
requirement: !ruby/object:Gem::Requirement
|
170
170
|
requirements:
|
171
|
-
- -
|
171
|
+
- - ">="
|
172
172
|
- !ruby/object:Gem::Version
|
173
173
|
version: '0'
|
174
174
|
type: :development
|
175
175
|
prerelease: false
|
176
176
|
version_requirements: !ruby/object:Gem::Requirement
|
177
177
|
requirements:
|
178
|
-
- -
|
178
|
+
- - ">="
|
179
179
|
- !ruby/object:Gem::Version
|
180
180
|
version: '0'
|
181
181
|
- !ruby/object:Gem::Dependency
|
182
|
-
name:
|
182
|
+
name: byebug
|
183
183
|
requirement: !ruby/object:Gem::Requirement
|
184
184
|
requirements:
|
185
|
-
- -
|
185
|
+
- - ">="
|
186
186
|
- !ruby/object:Gem::Version
|
187
187
|
version: '0'
|
188
188
|
type: :development
|
189
189
|
prerelease: false
|
190
190
|
version_requirements: !ruby/object:Gem::Requirement
|
191
191
|
requirements:
|
192
|
-
- -
|
192
|
+
- - ">="
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: '0'
|
195
|
+
- !ruby/object:Gem::Dependency
|
196
|
+
name: jobba
|
197
|
+
requirement: !ruby/object:Gem::Requirement
|
198
|
+
requirements:
|
199
|
+
- - ">="
|
200
|
+
- !ruby/object:Gem::Version
|
201
|
+
version: '0'
|
202
|
+
type: :development
|
203
|
+
prerelease: false
|
204
|
+
version_requirements: !ruby/object:Gem::Requirement
|
205
|
+
requirements:
|
206
|
+
- - ">="
|
193
207
|
- !ruby/object:Gem::Version
|
194
208
|
version: '0'
|
195
209
|
- !ruby/object:Gem::Dependency
|
196
210
|
name: rails
|
197
211
|
requirement: !ruby/object:Gem::Requirement
|
198
212
|
requirements:
|
199
|
-
- -
|
213
|
+
- - ">="
|
200
214
|
- !ruby/object:Gem::Version
|
201
215
|
version: '0'
|
202
216
|
type: :development
|
203
217
|
prerelease: false
|
204
218
|
version_requirements: !ruby/object:Gem::Requirement
|
205
219
|
requirements:
|
206
|
-
- -
|
220
|
+
- - ">="
|
207
221
|
- !ruby/object:Gem::Version
|
208
222
|
version: '0'
|
209
223
|
description: Ride the rails but don't touch them.
|
@@ -218,7 +232,6 @@ files:
|
|
218
232
|
- Rakefile
|
219
233
|
- lib/lev.rb
|
220
234
|
- lib/lev/active_job.rb
|
221
|
-
- lib/lev/background_job.rb
|
222
235
|
- lib/lev/better_active_model_errors.rb
|
223
236
|
- lib/lev/delegate_to_routine.rb
|
224
237
|
- lib/lev/error.rb
|
@@ -231,7 +244,7 @@ files:
|
|
231
244
|
- lib/lev/handler.rb
|
232
245
|
- lib/lev/handler_helper.rb
|
233
246
|
- lib/lev/memory_store.rb
|
234
|
-
- lib/lev/
|
247
|
+
- lib/lev/null_status.rb
|
235
248
|
- lib/lev/object.rb
|
236
249
|
- lib/lev/outputs.rb
|
237
250
|
- lib/lev/routine.rb
|
@@ -240,7 +253,6 @@ files:
|
|
240
253
|
- lib/lev/utilities.rb
|
241
254
|
- lib/lev/version.rb
|
242
255
|
- spec/active_job_routines_spec.rb
|
243
|
-
- spec/background_job_spec.rb
|
244
256
|
- spec/create_sprocket_spec.rb
|
245
257
|
- spec/deep_merge_spec.rb
|
246
258
|
- spec/delegates_to_spec.rb
|
@@ -254,6 +266,7 @@ files:
|
|
254
266
|
- spec/support/create_sprocket.rb
|
255
267
|
- spec/support/delegated_routine.rb
|
256
268
|
- spec/support/delegating_routine.rb
|
269
|
+
- spec/support/jobba.rb
|
257
270
|
- spec/support/paramify_handler_a.rb
|
258
271
|
- spec/support/paramify_handler_b.rb
|
259
272
|
- spec/support/sprocket.rb
|
@@ -269,23 +282,22 @@ require_paths:
|
|
269
282
|
- lib
|
270
283
|
required_ruby_version: !ruby/object:Gem::Requirement
|
271
284
|
requirements:
|
272
|
-
- -
|
285
|
+
- - ">="
|
273
286
|
- !ruby/object:Gem::Version
|
274
287
|
version: '0'
|
275
288
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
276
289
|
requirements:
|
277
|
-
- -
|
290
|
+
- - ">="
|
278
291
|
- !ruby/object:Gem::Version
|
279
292
|
version: '0'
|
280
293
|
requirements: []
|
281
294
|
rubyforge_project:
|
282
|
-
rubygems_version: 2.4.
|
295
|
+
rubygems_version: 2.4.8
|
283
296
|
signing_key:
|
284
297
|
specification_version: 4
|
285
298
|
summary: Ride the rails but don't touch them.
|
286
299
|
test_files:
|
287
300
|
- spec/active_job_routines_spec.rb
|
288
|
-
- spec/background_job_spec.rb
|
289
301
|
- spec/create_sprocket_spec.rb
|
290
302
|
- spec/deep_merge_spec.rb
|
291
303
|
- spec/delegates_to_spec.rb
|
@@ -299,6 +311,7 @@ test_files:
|
|
299
311
|
- spec/support/create_sprocket.rb
|
300
312
|
- spec/support/delegated_routine.rb
|
301
313
|
- spec/support/delegating_routine.rb
|
314
|
+
- spec/support/jobba.rb
|
302
315
|
- spec/support/paramify_handler_a.rb
|
303
316
|
- spec/support/paramify_handler_b.rb
|
304
317
|
- spec/support/sprocket.rb
|
data/lib/lev/background_job.rb
DELETED
@@ -1,229 +0,0 @@
|
|
1
|
-
require 'json'
|
2
|
-
|
3
|
-
module Lev
|
4
|
-
class BackgroundJob
|
5
|
-
attr_reader :id, :status, :progress, :errors
|
6
|
-
|
7
|
-
STATE_UNQUEUED = 'unqueued'
|
8
|
-
STATE_QUEUED = 'queued'
|
9
|
-
STATE_WORKING = 'working'
|
10
|
-
STATE_SUCCEEDED = 'succeeded'
|
11
|
-
STATE_FAILED = 'failed'
|
12
|
-
STATE_KILLED = 'killed'
|
13
|
-
STATE_UNKNOWN = 'unknown'
|
14
|
-
|
15
|
-
STATES = [
|
16
|
-
STATE_UNQUEUED,
|
17
|
-
STATE_QUEUED,
|
18
|
-
STATE_WORKING,
|
19
|
-
STATE_SUCCEEDED,
|
20
|
-
STATE_FAILED,
|
21
|
-
STATE_KILLED,
|
22
|
-
STATE_UNKNOWN
|
23
|
-
].freeze
|
24
|
-
|
25
|
-
def self.create
|
26
|
-
new(status: STATE_UNQUEUED).tap do |job|
|
27
|
-
job.save_standard_values
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
# Finds the job with the specified ID and returns it. If no such ID
|
32
|
-
# exists in the store, returns a job with 'unknown' status and sets it
|
33
|
-
# in the store
|
34
|
-
def self.find!(id)
|
35
|
-
find(id) || new({id: id}).tap do |job|
|
36
|
-
job.save_standard_values
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
# Finds the job with the specified ID and returns it. If no such ID
|
41
|
-
# exists in the store, returns nil.
|
42
|
-
def self.find(id)
|
43
|
-
raise(ArgumentError, "`id` cannot be nil") if id.nil?
|
44
|
-
|
45
|
-
attrs = { id: id }
|
46
|
-
|
47
|
-
existing_job_attrs = fetch_and_parse(job_key(id))
|
48
|
-
|
49
|
-
if existing_job_attrs.present?
|
50
|
-
attrs.merge!(existing_job_attrs)
|
51
|
-
new(attrs)
|
52
|
-
else
|
53
|
-
nil
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
def self.all
|
58
|
-
job_ids.map { |id| find!(id) }
|
59
|
-
end
|
60
|
-
|
61
|
-
def set_progress(at, out_of = nil)
|
62
|
-
progress = compute_fractional_progress(at, out_of)
|
63
|
-
set(progress: progress)
|
64
|
-
end
|
65
|
-
|
66
|
-
STATES.each do |state|
|
67
|
-
define_method("#{state}!") do
|
68
|
-
set(status: state)
|
69
|
-
end
|
70
|
-
|
71
|
-
define_method("#{state}?") do
|
72
|
-
status == state
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
(STATES + %w(completed incomplete)).each do |state|
|
77
|
-
define_singleton_method("#{state}") do
|
78
|
-
all.select{|job| job.send("#{state}?")}
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
def completed?
|
83
|
-
failed? || succeeded?
|
84
|
-
end
|
85
|
-
|
86
|
-
def incomplete?
|
87
|
-
!completed?
|
88
|
-
end
|
89
|
-
|
90
|
-
def add_error(error, options = { })
|
91
|
-
options = { is_fatal: false }.merge(options)
|
92
|
-
@errors << { is_fatal: options[:is_fatal],
|
93
|
-
code: error.code,
|
94
|
-
message: error.message,
|
95
|
-
data: error.data }
|
96
|
-
set(errors: @errors)
|
97
|
-
end
|
98
|
-
|
99
|
-
# Rails compatibility
|
100
|
-
# returns a Hash of all key-value pairs that have been #set()
|
101
|
-
def as_json(options = {})
|
102
|
-
stored
|
103
|
-
end
|
104
|
-
|
105
|
-
def save(incoming_hash)
|
106
|
-
if reserved = incoming_hash.select { |k, _| RESERVED_KEYS.include?(k) }.first
|
107
|
-
raise IllegalArgument, "Cannot set reserved key: #{reserved[0]}"
|
108
|
-
else
|
109
|
-
set(incoming_hash)
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
def save_standard_values
|
114
|
-
set({
|
115
|
-
id: id,
|
116
|
-
status: status,
|
117
|
-
progress: progress,
|
118
|
-
errors: errors
|
119
|
-
})
|
120
|
-
end
|
121
|
-
|
122
|
-
def method_missing(method_name, *args)
|
123
|
-
get_dynamic_variable(method_name) || super
|
124
|
-
end
|
125
|
-
|
126
|
-
def respond_to?(method_name)
|
127
|
-
has_dynamic_variable?(method_name) || super
|
128
|
-
end
|
129
|
-
|
130
|
-
protected
|
131
|
-
|
132
|
-
RESERVED_KEYS = [:id, :status, :progress, :errors]
|
133
|
-
|
134
|
-
def initialize(attrs = {})
|
135
|
-
attrs = attrs.stringify_keys
|
136
|
-
|
137
|
-
@id = attrs['id'] || SecureRandom.uuid
|
138
|
-
@status = attrs['status'] || STATE_UNKNOWN
|
139
|
-
@progress = attrs['progress'] || 0
|
140
|
-
@errors = attrs['errors'] || []
|
141
|
-
|
142
|
-
attrs.each do |attr, value|
|
143
|
-
if !instance_variable_defined?("@#{attr}")
|
144
|
-
instance_variable_set("@#{attr}", attrs[attr])
|
145
|
-
end
|
146
|
-
end
|
147
|
-
end
|
148
|
-
|
149
|
-
def set(incoming_hash)
|
150
|
-
apply_consistency_rules!(incoming_hash)
|
151
|
-
new_hash = stored.merge(incoming_hash)
|
152
|
-
new_hash.each { |k, v| instance_variable_set("@#{k}", v) }
|
153
|
-
self.class.store.write(job_key, new_hash.to_json)
|
154
|
-
track_job_id
|
155
|
-
end
|
156
|
-
|
157
|
-
def apply_consistency_rules!(hash)
|
158
|
-
hash.stringify_keys!
|
159
|
-
hash['progress'] = 1.0 if hash['status'] == 'succeeded'
|
160
|
-
end
|
161
|
-
|
162
|
-
def get_dynamic_variable(name)
|
163
|
-
return nil if !has_dynamic_variable?(name)
|
164
|
-
instance_variable_get("@#{name}")
|
165
|
-
end
|
166
|
-
|
167
|
-
def has_dynamic_variable?(name)
|
168
|
-
!name.match(/\?|\!/) && instance_variable_defined?("@#{name}")
|
169
|
-
end
|
170
|
-
|
171
|
-
def self.store
|
172
|
-
Lev.configuration.job_store
|
173
|
-
end
|
174
|
-
|
175
|
-
def self.fetch_and_parse(job_key)
|
176
|
-
fetched = store.fetch(job_key)
|
177
|
-
return nil if fetched.nil?
|
178
|
-
JSON.parse(fetched).stringify_keys!
|
179
|
-
end
|
180
|
-
|
181
|
-
def self.job_ids
|
182
|
-
store.fetch(job_key('lev_job_ids')) || []
|
183
|
-
end
|
184
|
-
|
185
|
-
def stored
|
186
|
-
self.class.fetch_and_parse(job_key) || {}
|
187
|
-
end
|
188
|
-
|
189
|
-
def track_job_id
|
190
|
-
ids = self.class.job_ids
|
191
|
-
return if ids.include?(@id)
|
192
|
-
ids << @id
|
193
|
-
self.class.store.write(self.class.job_key('lev_job_ids'), ids)
|
194
|
-
end
|
195
|
-
|
196
|
-
def job_key
|
197
|
-
self.class.job_key(@id)
|
198
|
-
end
|
199
|
-
|
200
|
-
def self.job_key(id)
|
201
|
-
"#{Lev.configuration.job_store_namespace}:#{id}"
|
202
|
-
end
|
203
|
-
|
204
|
-
def has_reserved_keys?(hash)
|
205
|
-
(hash.keys.collect(&:to_sym) & RESERVED_KEYS).any?
|
206
|
-
end
|
207
|
-
|
208
|
-
def push(key, new_item)
|
209
|
-
new_value = (send(key) || []).push(new_item)
|
210
|
-
set(key => new_value)
|
211
|
-
end
|
212
|
-
|
213
|
-
def compute_fractional_progress(at, out_of)
|
214
|
-
if at.nil?
|
215
|
-
raise IllegalArgument, "Must specify at least `at` argument to `progress` call"
|
216
|
-
elsif at < 0
|
217
|
-
raise IllegalArgument, "progress cannot be negative (at=#{at})"
|
218
|
-
elsif out_of && out_of < at
|
219
|
-
raise IllegalArgument, "`out_of` must be greater than `at` in `progress` calls"
|
220
|
-
elsif out_of.nil? && (at < 0 || at > 1)
|
221
|
-
raise IllegalArgument, "If `out_of` not specified, `at` must be in the range [0.0, 1.0]"
|
222
|
-
end
|
223
|
-
|
224
|
-
at.to_f / (out_of || 1).to_f
|
225
|
-
end
|
226
|
-
|
227
|
-
end
|
228
|
-
end
|
229
|
-
|
@@ -1,26 +0,0 @@
|
|
1
|
-
module Lev
|
2
|
-
class NoBackgroundJob
|
3
|
-
|
4
|
-
# Provide null object pattern methods for background jobs; routines should
|
5
|
-
# not be checking their own status (they should know it), and outside callers
|
6
|
-
# should not be checking status unless the background job is a real one.
|
7
|
-
|
8
|
-
def set_progress(*); end
|
9
|
-
def save(*); end
|
10
|
-
def add_error(*); end
|
11
|
-
|
12
|
-
Lev::BackgroundJob::STATES.each do |state|
|
13
|
-
define_method("#{state}!") do; end
|
14
|
-
end
|
15
|
-
|
16
|
-
def self.method_missing(method_sym, *args, &block)
|
17
|
-
if Lev::BackgroundJob.method_defined?(method_sym)
|
18
|
-
raise NameError,
|
19
|
-
"'#{method_sym}' is Lev::BackgroundJob query method, and those cannot be called on NoBackgroundJob"
|
20
|
-
else
|
21
|
-
super
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
end
|
26
|
-
end
|
data/spec/background_job_spec.rb
DELETED
@@ -1,129 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
require 'lev/active_job'
|
3
|
-
|
4
|
-
describe Lev::BackgroundJob do
|
5
|
-
|
6
|
-
context 'delayed routine' do
|
7
|
-
class DelayedRoutine
|
8
|
-
lev_routine
|
9
|
-
protected
|
10
|
-
def exec; end
|
11
|
-
end
|
12
|
-
|
13
|
-
subject(:job) { described_class.all.last }
|
14
|
-
|
15
|
-
before do
|
16
|
-
Lev.configuration.job_store.clear
|
17
|
-
allow(SecureRandom).to receive(:uuid) { '123abc' }
|
18
|
-
DelayedRoutine.perform_later
|
19
|
-
end
|
20
|
-
|
21
|
-
it 'behaves as a nice ruby object' do
|
22
|
-
expect(job.id).to eq('123abc')
|
23
|
-
expect(job.status).to eq(described_class::STATE_QUEUED)
|
24
|
-
expect(job.progress).to eq(0.0)
|
25
|
-
end
|
26
|
-
|
27
|
-
it 'is unknown when not found' do
|
28
|
-
foo = described_class.find!('noooooo')
|
29
|
-
expect(foo.status).to eq(described_class::STATE_UNKNOWN)
|
30
|
-
end
|
31
|
-
|
32
|
-
it 'uses as_json' do
|
33
|
-
json = job.as_json
|
34
|
-
|
35
|
-
expect(json).to eq({
|
36
|
-
'id' => '123abc',
|
37
|
-
'status' => described_class::STATE_QUEUED,
|
38
|
-
'progress' => 0.0,
|
39
|
-
'errors' => []
|
40
|
-
})
|
41
|
-
|
42
|
-
job.save(foo: :bar)
|
43
|
-
json = job.as_json
|
44
|
-
|
45
|
-
expect(json['foo']).to eq('bar')
|
46
|
-
end
|
47
|
-
|
48
|
-
it 'generates attributes for custom variables' do
|
49
|
-
job.save(foo: 'bar')
|
50
|
-
|
51
|
-
reloaded_job = Lev::BackgroundJob.find(job.id)
|
52
|
-
|
53
|
-
expect(reloaded_job.respond_to?(:foo)).to be true
|
54
|
-
expect(reloaded_job.foo).to eq('bar')
|
55
|
-
end
|
56
|
-
|
57
|
-
it 'has scopes' do
|
58
|
-
expect(described_class.incomplete.collect(&:id)).to include(job.id)
|
59
|
-
|
60
|
-
job.queued!
|
61
|
-
expect(described_class.incomplete.collect(&:id)).to include(job.id)
|
62
|
-
expect(described_class.queued.collect(&:id)).to include(job.id)
|
63
|
-
|
64
|
-
job.working!
|
65
|
-
expect(described_class.incomplete.collect(&:id)).to include(job.id)
|
66
|
-
expect(described_class.working.collect(&:id)).to include(job.id)
|
67
|
-
|
68
|
-
job.failed!
|
69
|
-
expect(described_class.incomplete.collect(&:id)).not_to include(job.id)
|
70
|
-
expect(described_class.failed.collect(&:id)).to include(job.id)
|
71
|
-
|
72
|
-
job.killed!
|
73
|
-
expect(described_class.incomplete.collect(&:id)).to include(job.id)
|
74
|
-
expect(described_class.killed.collect(&:id)).to include(job.id)
|
75
|
-
|
76
|
-
job.unknown!
|
77
|
-
expect(described_class.incomplete.collect(&:id)).to include(job.id)
|
78
|
-
expect(described_class.unknown.collect(&:id)).to include(job.id)
|
79
|
-
|
80
|
-
job.succeeded!
|
81
|
-
expect(described_class.succeeded.collect(&:id)).to include(job.id)
|
82
|
-
expect(described_class.incomplete.collect(&:id)).not_to include(job.id)
|
83
|
-
expect(described_class.succeeded.collect(&:id)).to include(job.id)
|
84
|
-
end
|
85
|
-
|
86
|
-
it 'has the unqueued scope' do
|
87
|
-
expect(described_class.unqueued.collect(&:id)).to eq []
|
88
|
-
unqueued_job = Lev::BackgroundJob.create
|
89
|
-
expect(described_class.unqueued.collect(&:id)).to include(unqueued_job.id)
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
it 'sets progress to 100% when succeeded' do
|
94
|
-
job = described_class.new
|
95
|
-
job.succeeded!
|
96
|
-
expect(job.progress).to eq 1
|
97
|
-
end
|
98
|
-
|
99
|
-
describe '.find!' do
|
100
|
-
let!(:job) { described_class.create }
|
101
|
-
|
102
|
-
it 'does not write to store when job exists' do
|
103
|
-
expect(described_class.store).to_not receive(:write)
|
104
|
-
found_job = described_class.find!(job.id)
|
105
|
-
expect(found_job.as_json).to eq(job.as_json)
|
106
|
-
end
|
107
|
-
|
108
|
-
it 'finds jobs that are not in the store' do
|
109
|
-
found_job = described_class.find!('not-a-real-id')
|
110
|
-
expect(found_job.as_json).to include('status' => 'unknown')
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
|
-
describe '.find' do
|
115
|
-
let!(:job) { described_class.create }
|
116
|
-
|
117
|
-
it 'finds jobs that are in the store' do
|
118
|
-
expect(described_class.store).to_not receive(:write)
|
119
|
-
found_job = described_class.find(job.id)
|
120
|
-
expect(found_job.as_json).to eq(job.as_json)
|
121
|
-
end
|
122
|
-
|
123
|
-
it 'returns nil for jobs not in the store' do
|
124
|
-
found_job = described_class.find('not-a-real-id')
|
125
|
-
expect(found_job).to be_nil
|
126
|
-
end
|
127
|
-
end
|
128
|
-
|
129
|
-
end
|