lev 4.3.0 → 4.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/README.md +37 -2
- data/lib/lev.rb +8 -2
- data/lib/lev/active_job.rb +33 -0
- data/lib/lev/black_hole_status.rb +26 -0
- data/lib/lev/errors.rb +21 -1
- data/lib/lev/memory_store.rb +17 -0
- data/lib/lev/routine.rb +32 -31
- data/lib/lev/status.rb +125 -0
- data/lib/lev/version.rb +1 -1
- data/spec/active_job_routines_spec.rb +0 -33
- data/spec/routine_spec.rb +1 -1
- data/spec/spec_helper.rb +6 -0
- data/spec/statused_routines_spec.rb +158 -0
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
NTU2OGIyM2MxZDQzZmYwMGVkNmZkZWRiZmE2YjMzZmE2NDQ2MDM3Zg==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
Mjk0OTU3NDBhM2E4MjNmMWRhNmZlMzFhYWU4YjMxNTdiMjEwOWE0MQ==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
YTU4ZmQ3YjExMGNlNjI4MjNkYWRkOWNkM2YxOTkwYTM4ZGEyY2VlMjM4Y2Ew
|
10
|
+
MmFiOTg4YTE3YTU5OTdlZWY3NmQzNWEyNGQyMmFiMzNjZmRjMDA3ODZmYzFk
|
11
|
+
OWE0NjU2ZDgyOTljZTg2MGU1MmJjMWEyODIxYjI4ZTI2NzQ5ZTE=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
ZjM2ZjFmMTBkMGVkMDgxMWMwOGQ2ZDBmYjY1YWI5ZmFkMTc5ODdlYWFkMDFj
|
14
|
+
YmEwMWE0NzU5ODg4MDY0ODA2NTE2ZjdlNWU0NDk0MjI0NDgxOGZkNTA2MGNj
|
15
|
+
YThlY2E2NDMxNzEyZjNiNjExMzFmN2IzMzI0NTdjOTI5ZjY3MzY=
|
data/README.md
CHANGED
@@ -433,14 +433,49 @@ class MyRoutine
|
|
433
433
|
end
|
434
434
|
```
|
435
435
|
|
436
|
-
|
436
|
+
Routines run as ActiveJobs can also publish their status somewhere it can be listened to (e.g. to Redis).
|
437
|
+
|
438
|
+
Routines have a `status` object and can call the following methods:
|
439
|
+
|
440
|
+
* `set_progress(at, out_of = nil)` sets the current progress; can either pass a float between 0.0 and 1.0 or
|
441
|
+
a counter towards a total, e.g. `set_progress(67,212)`.
|
442
|
+
* `queued!` Sets the status to 'queued'
|
443
|
+
* `working!` Sets the status to 'working'
|
444
|
+
* `completed!` Sets the status to 'completed'
|
445
|
+
* `failed!` Sets the status to 'failed'
|
446
|
+
* `killed!` Sets the status to 'killed'
|
447
|
+
* `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)
|
448
|
+
* `add_error(is_fatal, error)` takes a boolean and a Lev `Error` object and adds its data to an array of `errors` in the status hash.
|
449
|
+
|
450
|
+
All routines have such a status object. 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 actually records the statuses to a store of your choice. The store is configured in the Lev configuration block, e.g.:
|
437
451
|
|
438
452
|
```ruby
|
439
453
|
Lev.configure do |config|
|
440
|
-
config.
|
454
|
+
config.status_store = whatever
|
441
455
|
end
|
442
456
|
```
|
443
457
|
|
458
|
+
The store needs to respond to the following methods:
|
459
|
+
|
460
|
+
1. fetch(key)
|
461
|
+
2. write(key, value)
|
462
|
+
|
463
|
+
The default store is essentially a hash (implemented in `Lev::MemoryStore`). Any `ActiveSupport::Cache::Store` will work.
|
464
|
+
|
465
|
+
A routine's status can be retrieved with `Lev::Status.get(uuid_here)`. This just returns a simple hash. Notable keys are
|
466
|
+
|
467
|
+
* `'progress'` which returns the progress as a number between 0.0 and 1.0
|
468
|
+
* `'status'` which returns one of the status strings shown above
|
469
|
+
* `'errors'` which (if present) is an error of error hashes
|
470
|
+
* `'uuid'` the UUID of the routine / status
|
471
|
+
|
472
|
+
Other routine-specific keys (set with a `save` call) are also present.
|
473
|
+
|
474
|
+
**Notes:**
|
475
|
+
|
476
|
+
1. Don't try to write a status store that uses the ActiveRecord database, as the database changes would only be seen when the routine completes and its transaction is committed.
|
477
|
+
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).
|
478
|
+
|
444
479
|
## Handlers
|
445
480
|
|
446
481
|
Handlers are specialized routines that take user input (e.g. form data) and then take an action based on that input. Because all Handlers are Routines, everything discussed above applies to them.
|
data/lib/lev.rb
CHANGED
@@ -24,6 +24,10 @@ require "lev/form_builder"
|
|
24
24
|
require "lev/delegate_to_routine"
|
25
25
|
require "lev/transaction_isolation"
|
26
26
|
|
27
|
+
require 'lev/active_job'
|
28
|
+
require 'lev/memory_store'
|
29
|
+
require 'lev/status'
|
30
|
+
require 'lev/black_hole_status'
|
27
31
|
|
28
32
|
module Lev
|
29
33
|
class << self
|
@@ -55,14 +59,16 @@ module Lev
|
|
55
59
|
attr_accessor :security_transgression_error
|
56
60
|
attr_accessor :illegal_argument_error
|
57
61
|
attr_accessor :raise_fatal_errors
|
58
|
-
attr_accessor :
|
62
|
+
attr_accessor :status_store
|
63
|
+
attr_accessor :status_store_namespace
|
59
64
|
|
60
65
|
def initialize
|
61
66
|
@form_error_class = 'error'
|
62
67
|
@security_transgression_error = Lev::SecurityTransgression
|
63
68
|
@illegal_argument_error = Lev::IllegalArgument
|
64
69
|
@raise_fatal_errors = false
|
65
|
-
@
|
70
|
+
@status_store = Lev::MemoryStore.new
|
71
|
+
@status_store_namespace = "lev_status"
|
66
72
|
super
|
67
73
|
end
|
68
74
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
if defined?(::ActiveJob)
|
2
|
+
module Lev
|
3
|
+
module ActiveJob
|
4
|
+
class Base < ::ActiveJob::Base
|
5
|
+
def self.perform_later(routine_class, *args, &block)
|
6
|
+
queue_as routine_class.active_job_queue
|
7
|
+
args.push(routine_class.to_s)
|
8
|
+
|
9
|
+
# To enable tracking of this job's status, create a new Status object
|
10
|
+
# and push it on to the arguments so that in `perform` it can be peeled
|
11
|
+
# off and handed to the routine instance. The Status UUID is returned
|
12
|
+
# so that callers can track the status.
|
13
|
+
status = Lev::Status.new
|
14
|
+
status.queued!
|
15
|
+
args.push(status.uuid)
|
16
|
+
|
17
|
+
super(*args, &block)
|
18
|
+
|
19
|
+
status.uuid
|
20
|
+
end
|
21
|
+
|
22
|
+
def perform(*args, &block)
|
23
|
+
# Pop arguments added by perform_later
|
24
|
+
uuid = args.pop
|
25
|
+
routine_class = Kernel.const_get(args.pop)
|
26
|
+
|
27
|
+
routine_instance = routine_class.new(Lev::Status.new(uuid))
|
28
|
+
routine_instance.call(*args, &block)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Lev
|
2
|
+
class BlackHoleStatus
|
3
|
+
|
4
|
+
# Provide null object pattern methods for status setters; routines should
|
5
|
+
# not be checking their own status (they should know it), and outside callers
|
6
|
+
# should not be checking status unless the status object is a real one.
|
7
|
+
|
8
|
+
def set_progress(*); end
|
9
|
+
def save(*); end
|
10
|
+
def add_error(*); end
|
11
|
+
|
12
|
+
Lev::Status::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::Status.new.respond_to?(method_sym)
|
18
|
+
raise NameError,
|
19
|
+
"'#{method_sym}' is Lev::Status query method, and those cannot be called on BlackHoleStatus"
|
20
|
+
else
|
21
|
+
super
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
data/lib/lev/errors.rb
CHANGED
@@ -4,12 +4,29 @@ module Lev
|
|
4
4
|
#
|
5
5
|
class Errors < Array
|
6
6
|
|
7
|
+
def initialize(routine_status = nil, raise_fatal_errors = false)
|
8
|
+
@routine_status = routine_status || BlackHoleStatus.new
|
9
|
+
@raise_fatal_errors = raise_fatal_errors
|
10
|
+
end
|
11
|
+
|
7
12
|
def add(fail, args={})
|
8
13
|
args[:kind] ||= :lev
|
9
14
|
error = Error.new(args)
|
15
|
+
|
10
16
|
return if ignored_error_procs.any?{|proc| proc.call(error)}
|
11
17
|
self.push(error)
|
12
|
-
|
18
|
+
|
19
|
+
routine_status.add_error(error, is_fatal: fail)
|
20
|
+
|
21
|
+
if fail
|
22
|
+
routine_status.failed!
|
23
|
+
|
24
|
+
if raise_fatal_errors
|
25
|
+
raise StandardError, args.to_a.map { |i| i.join(' ') }.join(' - ')
|
26
|
+
else
|
27
|
+
throw :fatal_errors_encountered
|
28
|
+
end
|
29
|
+
end
|
13
30
|
end
|
14
31
|
|
15
32
|
def ignore(arg)
|
@@ -37,6 +54,9 @@ module Lev
|
|
37
54
|
|
38
55
|
protected
|
39
56
|
|
57
|
+
attr_reader :routine_status
|
58
|
+
attr_reader :raise_fatal_errors
|
59
|
+
|
40
60
|
def ignored_error_procs
|
41
61
|
@ignored_error_procs ||= []
|
42
62
|
end
|
data/lib/lev/routine.rb
CHANGED
@@ -185,12 +185,14 @@ module Lev
|
|
185
185
|
attr_reader :outputs
|
186
186
|
attr_reader :errors
|
187
187
|
|
188
|
-
def initialize
|
189
|
-
@outputs =
|
190
|
-
@errors =
|
188
|
+
def initialize(outputs, errors)
|
189
|
+
@outputs = outputs
|
190
|
+
@errors = errors
|
191
191
|
end
|
192
192
|
end
|
193
193
|
|
194
|
+
attr_reader :uuid
|
195
|
+
|
194
196
|
def self.included(base)
|
195
197
|
base.extend(ClassMethods)
|
196
198
|
end
|
@@ -206,27 +208,10 @@ module Lev
|
|
206
208
|
result.outputs.send(@express_output)
|
207
209
|
end
|
208
210
|
|
209
|
-
if defined?(ActiveJob)
|
210
|
-
def active_job_class
|
211
|
-
@active_job_class ||= const_set("ActiveJob",
|
212
|
-
Class.new(Lev.configuration.active_job_class) do
|
213
|
-
queue_as do
|
214
|
-
parent_routine.active_job_queue
|
215
|
-
end
|
216
|
-
|
217
|
-
def parent_routine
|
218
|
-
self.class.parent
|
219
|
-
end
|
220
|
-
|
221
|
-
def perform(*args, &block)
|
222
|
-
parent_routine.call(*args, &block)
|
223
|
-
end
|
224
|
-
end
|
225
|
-
)
|
226
|
-
end
|
227
|
-
|
211
|
+
if defined?(::ActiveJob)
|
228
212
|
def perform_later(*args, &block)
|
229
|
-
|
213
|
+
# Delegate to a subclass of Lev::Routine::ActiveJob::Base
|
214
|
+
Lev::ActiveJob::Base.perform_later(self, *args, &block)
|
230
215
|
end
|
231
216
|
|
232
217
|
def active_job_queue
|
@@ -282,6 +267,8 @@ module Lev
|
|
282
267
|
def call(*args, &block)
|
283
268
|
@after_transaction_blocks = []
|
284
269
|
|
270
|
+
status.working!
|
271
|
+
|
285
272
|
in_transaction do
|
286
273
|
catch :fatal_errors_encountered do
|
287
274
|
begin
|
@@ -298,6 +285,8 @@ module Lev
|
|
298
285
|
block.call
|
299
286
|
end
|
300
287
|
|
288
|
+
status.completed! if !errors?
|
289
|
+
|
301
290
|
result
|
302
291
|
end
|
303
292
|
|
@@ -402,11 +391,7 @@ module Lev
|
|
402
391
|
end
|
403
392
|
|
404
393
|
def fatal_error(args={})
|
405
|
-
|
406
|
-
raise StandardError, args.to_a.map { |i| i.join(' ') }.join(' - ')
|
407
|
-
else
|
408
|
-
errors.add(true, args)
|
409
|
-
end
|
394
|
+
errors.add(true, args)
|
410
395
|
end
|
411
396
|
|
412
397
|
def nonfatal_error(args={})
|
@@ -431,13 +416,29 @@ module Lev
|
|
431
416
|
@after_transaction_blocks.push(block)
|
432
417
|
end
|
433
418
|
|
419
|
+
# Note that the parent may neglect to call super, leading to this method never being called.
|
420
|
+
# Do not perform any initialization here that cannot be safely skipped
|
421
|
+
def initialize(status = nil)
|
422
|
+
# If someone cares about the status, they'll pass it in; otherwise all
|
423
|
+
# status updates go into the bit bucket.
|
424
|
+
@status = status
|
425
|
+
end
|
426
|
+
|
434
427
|
protected
|
435
428
|
|
436
|
-
|
437
|
-
|
429
|
+
attr_writer :runner
|
430
|
+
|
431
|
+
def status
|
432
|
+
@status ||= Lev::BlackHoleStatus.new
|
438
433
|
end
|
439
434
|
|
440
|
-
|
435
|
+
def result
|
436
|
+
@result ||= Result.new(
|
437
|
+
Outputs.new,
|
438
|
+
Errors.new(status,
|
439
|
+
topmost_runner.class.raise_fatal_errors?)
|
440
|
+
)
|
441
|
+
end
|
441
442
|
|
442
443
|
def outputs
|
443
444
|
result.outputs
|
data/lib/lev/status.rb
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Lev
|
4
|
+
class Status
|
5
|
+
STATE_QUEUED = 'queued'
|
6
|
+
STATE_WORKING = 'working'
|
7
|
+
STATE_COMPLETED = 'completed'
|
8
|
+
STATE_FAILED = 'failed'
|
9
|
+
STATE_KILLED = 'killed'
|
10
|
+
|
11
|
+
STATES = [
|
12
|
+
STATE_QUEUED,
|
13
|
+
STATE_WORKING,
|
14
|
+
STATE_COMPLETED,
|
15
|
+
STATE_FAILED,
|
16
|
+
STATE_KILLED
|
17
|
+
].freeze
|
18
|
+
|
19
|
+
attr_reader :uuid
|
20
|
+
|
21
|
+
def initialize(uuid = nil)
|
22
|
+
@uuid = uuid || SecureRandom.uuid
|
23
|
+
save
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.find(uuid)
|
27
|
+
if status = store.fetch(status_key(uuid))
|
28
|
+
JSON.parse(status)
|
29
|
+
else
|
30
|
+
nil
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def set_progress(at, out_of = nil)
|
35
|
+
progress = compute_fractional_progress(at, out_of)
|
36
|
+
|
37
|
+
data_to_set = { progress: progress }
|
38
|
+
data_to_set[:state] = STATE_COMPLETED if 1.0 == progress
|
39
|
+
|
40
|
+
set(data_to_set)
|
41
|
+
end
|
42
|
+
|
43
|
+
STATES.each do |state|
|
44
|
+
define_method("#{state}!") do
|
45
|
+
set(state: state)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def save(hash = {})
|
50
|
+
if has_reserved_keys?(hash)
|
51
|
+
raise IllegalArgument,
|
52
|
+
"Caller cannot specify any reserved keys (#{RESERVED_KEYS})"
|
53
|
+
else
|
54
|
+
set(hash)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def add_error(error, options = { })
|
59
|
+
options = { is_fatal: false }.merge(options)
|
60
|
+
push('errors', { is_fatal: options[:is_fatal],
|
61
|
+
code: error.code,
|
62
|
+
message: error.message })
|
63
|
+
end
|
64
|
+
|
65
|
+
def get(key)
|
66
|
+
self.class.find(uuid)[key]
|
67
|
+
end
|
68
|
+
|
69
|
+
protected
|
70
|
+
RESERVED_KEYS = [:progress, :uuid, :state, :errors]
|
71
|
+
|
72
|
+
def self.store
|
73
|
+
# Nice to get the store from lev config each time so it isn't serialized
|
74
|
+
# when activejobs are sent off to places like redis
|
75
|
+
Lev.configuration.status_store
|
76
|
+
end
|
77
|
+
|
78
|
+
def set(incoming_hash)
|
79
|
+
if existing_settings = self.class.find(uuid)
|
80
|
+
incoming_hash = existing_settings.merge(incoming_hash)
|
81
|
+
end
|
82
|
+
|
83
|
+
self.class.store.write(status_key, incoming_hash.to_json)
|
84
|
+
end
|
85
|
+
|
86
|
+
def status_key
|
87
|
+
self.class.status_key(uuid)
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.status_key(uuid)
|
91
|
+
"#{Lev.configuration.status_store_namespace}:#{uuid}"
|
92
|
+
end
|
93
|
+
|
94
|
+
def has_reserved_keys?(hash)
|
95
|
+
(hash.keys.collect(&:to_sym) & RESERVED_KEYS).any?
|
96
|
+
end
|
97
|
+
|
98
|
+
def push(key, new_item)
|
99
|
+
new_value = (get(key) || []).push(new_item)
|
100
|
+
set(key => new_value)
|
101
|
+
end
|
102
|
+
|
103
|
+
STATES.each do |state|
|
104
|
+
define_method("#{state}?") do
|
105
|
+
get('state') == state
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def compute_fractional_progress(at, out_of)
|
110
|
+
if at.nil?
|
111
|
+
raise IllegalArgument, "Must specify at least `at` argument to `progress` call"
|
112
|
+
elsif at < 0
|
113
|
+
raise IllegalArgument, "progress cannot be negative (at=#{at})"
|
114
|
+
elsif out_of && out_of < at
|
115
|
+
raise IllegalArgument, "`out_of` must be greater than `at` in `progress` calls"
|
116
|
+
elsif out_of.nil? && (at < 0 || at > 1)
|
117
|
+
raise IllegalArgument, "If `out_of` not specified, `at` must be in the range [0.0, 1.0]"
|
118
|
+
end
|
119
|
+
|
120
|
+
at.to_f / (out_of || 1).to_f
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
data/lib/lev/version.rb
CHANGED
@@ -1,15 +1,7 @@
|
|
1
|
-
require 'active_job'
|
2
1
|
require 'spec_helper'
|
3
2
|
|
4
|
-
ActiveJob::Base.queue_adapter = :test
|
5
|
-
ActiveJob::Base.logger = ::Logger.new(nil)
|
6
|
-
|
7
3
|
RSpec.describe 'ActiveJob routines' do
|
8
4
|
context 'default configuration' do
|
9
|
-
before do
|
10
|
-
Lev.configure { |c| c.active_job_class = ActiveJob::Base } # default
|
11
|
-
end
|
12
|
-
|
13
5
|
class LaterRoutine
|
14
6
|
lev_routine active_job_queue: :something_else
|
15
7
|
|
@@ -29,29 +21,4 @@ RSpec.describe 'ActiveJob routines' do
|
|
29
21
|
expect(queue_name).to eq('something_else')
|
30
22
|
end
|
31
23
|
end
|
32
|
-
|
33
|
-
context 'specialized configuration' do
|
34
|
-
before do
|
35
|
-
Lev.configure { |c| c.active_job_class = SomeOtherJobBase }
|
36
|
-
end
|
37
|
-
|
38
|
-
class SomeOtherJobBase
|
39
|
-
def self.queue_as(*args); end
|
40
|
-
def self.perform_later; end
|
41
|
-
end
|
42
|
-
|
43
|
-
class NewLaterRoutine
|
44
|
-
lev_routine
|
45
|
-
protected
|
46
|
-
def exec; end
|
47
|
-
end
|
48
|
-
|
49
|
-
it 'allows configuration of the class' do
|
50
|
-
allow(SomeOtherJobBase).to receive(:perform_later)
|
51
|
-
|
52
|
-
expect(Lev.configuration.active_job_class).to eq(SomeOtherJobBase)
|
53
|
-
NewLaterRoutine.perform_later
|
54
|
-
expect(SomeOtherJobBase).to have_received(:perform_later)
|
55
|
-
end
|
56
|
-
end
|
57
24
|
end
|
data/spec/routine_spec.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -4,6 +4,12 @@
|
|
4
4
|
# loaded once.
|
5
5
|
#
|
6
6
|
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
|
+
|
8
|
+
require 'active_job'
|
9
|
+
|
10
|
+
ActiveJob::Base.queue_adapter = :test
|
11
|
+
ActiveJob::Base.logger = ::Logger.new(nil)
|
12
|
+
|
7
13
|
RSpec.configure do |config|
|
8
14
|
config.run_all_when_everything_filtered = true
|
9
15
|
config.filter_run :focus
|
@@ -0,0 +1,158 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class StatusedRoutine
|
4
|
+
lev_routine
|
5
|
+
|
6
|
+
protected
|
7
|
+
def exec
|
8
|
+
status.set_progress(9, 10)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
RSpec.describe 'Statused Routines' do
|
13
|
+
subject(:status) { Lev::Status.new }
|
14
|
+
|
15
|
+
context 'in a routine' do
|
16
|
+
it 'queues the status object on queue' do
|
17
|
+
uuid = StatusedRoutine.perform_later
|
18
|
+
status = Lev::Status.find(uuid)
|
19
|
+
|
20
|
+
expect(status['state']).to eq(Lev::Status::STATE_QUEUED)
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'inline activejob mode' do
|
24
|
+
before { ::ActiveJob::Base.queue_adapter = :inline }
|
25
|
+
after { ::ActiveJob::Base.queue_adapter = :test }
|
26
|
+
|
27
|
+
it 'sets status to working when called' do
|
28
|
+
expect_any_instance_of(Lev::Status).to receive(:working!)
|
29
|
+
StatusedRoutine.perform_later
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'completes the status object on completion, returning other data' do
|
33
|
+
uuid = StatusedRoutine.perform_later
|
34
|
+
status = Lev::Status.find(uuid)
|
35
|
+
expect(status['state']).to eq(Lev::Status::STATE_COMPLETED)
|
36
|
+
expect(status['progress']).to eq(0.9)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe '#save' do
|
42
|
+
it 'prevents the use of reserved keys' do
|
43
|
+
expect {
|
44
|
+
status.save(progress: 'blocked')
|
45
|
+
}.to raise_error(Lev::IllegalArgument)
|
46
|
+
|
47
|
+
expect {
|
48
|
+
status.save(uuid: 'blocked')
|
49
|
+
}.to raise_error(Lev::IllegalArgument)
|
50
|
+
|
51
|
+
expect {
|
52
|
+
status.save(state: 'blocked')
|
53
|
+
}.to raise_error(Lev::IllegalArgument)
|
54
|
+
|
55
|
+
expect {
|
56
|
+
status.save(errors: 'blocked')
|
57
|
+
}.to raise_error(Lev::IllegalArgument)
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'saves the hash given and writes them to the status' do
|
61
|
+
status.save(something: 'else')
|
62
|
+
expect(status.get('something')).to eq('else')
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe '#add_error' do
|
67
|
+
it 'adds the error object data to the status object' do
|
68
|
+
errors = Lev::Error.new(code: 'bad', message: 'awful')
|
69
|
+
status.add_error(errors)
|
70
|
+
expect(status.get('errors')).to eq([{ 'is_fatal' => false,
|
71
|
+
'code' => 'bad',
|
72
|
+
'message' => 'awful' }])
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe 'dynamic status setters/getters' do
|
77
|
+
it 'is queued' do
|
78
|
+
expect(status).not_to be_queued
|
79
|
+
status.queued!
|
80
|
+
expect(status).to be_queued
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'is working' do
|
84
|
+
expect(status).not_to be_working
|
85
|
+
status.working!
|
86
|
+
expect(status).to be_working
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'is completed' do
|
90
|
+
expect(status).not_to be_completed
|
91
|
+
status.completed!
|
92
|
+
expect(status).to be_completed
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'is failed' do
|
96
|
+
expect(status).not_to be_failed
|
97
|
+
status.failed!
|
98
|
+
expect(status).to be_failed
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'is killed' do
|
102
|
+
expect(status).not_to be_killed
|
103
|
+
status.killed!
|
104
|
+
expect(status).to be_killed
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
describe '#set_progress' do
|
109
|
+
it 'sets the progress key on the status object' do
|
110
|
+
status.set_progress(8, 10)
|
111
|
+
progress = status.get('progress')
|
112
|
+
expect(progress).to eq(0.8)
|
113
|
+
end
|
114
|
+
|
115
|
+
context 'when `out_of` is supplied' do
|
116
|
+
it 'requires a positive `at` float or integer' do
|
117
|
+
expect {
|
118
|
+
status.set_progress(nil, 1)
|
119
|
+
}.to raise_error(Lev::IllegalArgument)
|
120
|
+
|
121
|
+
expect {
|
122
|
+
status.set_progress(-1, 1)
|
123
|
+
}.to raise_error(Lev::IllegalArgument)
|
124
|
+
|
125
|
+
expect {
|
126
|
+
status.set_progress(2, 5)
|
127
|
+
}.not_to raise_error
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'requires `out_of` to be greater than `at`' do
|
131
|
+
expect {
|
132
|
+
status.set_progress(15, 8)
|
133
|
+
}.to raise_error(Lev::IllegalArgument)
|
134
|
+
|
135
|
+
expect {
|
136
|
+
status.set_progress(5, 10)
|
137
|
+
}.not_to raise_error
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
context 'without out_of specified' do
|
142
|
+
it 'requires `at` to be a float between 0.0 and 1.0' do
|
143
|
+
expect {
|
144
|
+
status.set_progress(1.1)
|
145
|
+
}.to raise_error(Lev::IllegalArgument)
|
146
|
+
|
147
|
+
expect {
|
148
|
+
status.set_progress(-1)
|
149
|
+
}.to raise_error(Lev::IllegalArgument)
|
150
|
+
|
151
|
+
expect {
|
152
|
+
status.set_progress(0.78)
|
153
|
+
}.not_to raise_error
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
end
|
158
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lev
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.3.
|
4
|
+
version: 4.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- JP Slavinsky
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-07-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|
@@ -217,7 +217,9 @@ files:
|
|
217
217
|
- README.md
|
218
218
|
- Rakefile
|
219
219
|
- lib/lev.rb
|
220
|
+
- lib/lev/active_job.rb
|
220
221
|
- lib/lev/better_active_model_errors.rb
|
222
|
+
- lib/lev/black_hole_status.rb
|
221
223
|
- lib/lev/delegate_to_routine.rb
|
222
224
|
- lib/lev/error.rb
|
223
225
|
- lib/lev/error_transferer.rb
|
@@ -228,9 +230,11 @@ files:
|
|
228
230
|
- lib/lev/handle_with.rb
|
229
231
|
- lib/lev/handler.rb
|
230
232
|
- lib/lev/handler_helper.rb
|
233
|
+
- lib/lev/memory_store.rb
|
231
234
|
- lib/lev/object.rb
|
232
235
|
- lib/lev/outputs.rb
|
233
236
|
- lib/lev/routine.rb
|
237
|
+
- lib/lev/status.rb
|
234
238
|
- lib/lev/term_mapper.rb
|
235
239
|
- lib/lev/transaction_isolation.rb
|
236
240
|
- lib/lev/utilities.rb
|
@@ -246,6 +250,7 @@ files:
|
|
246
250
|
- spec/spec_helper.rb
|
247
251
|
- spec/sprocket_handler_spec.rb
|
248
252
|
- spec/sprocket_spec.rb
|
253
|
+
- spec/statused_routines_spec.rb
|
249
254
|
- spec/support/create_sprocket.rb
|
250
255
|
- spec/support/delegated_routine.rb
|
251
256
|
- spec/support/delegating_routine.rb
|
@@ -290,6 +295,7 @@ test_files:
|
|
290
295
|
- spec/spec_helper.rb
|
291
296
|
- spec/sprocket_handler_spec.rb
|
292
297
|
- spec/sprocket_spec.rb
|
298
|
+
- spec/statused_routines_spec.rb
|
293
299
|
- spec/support/create_sprocket.rb
|
294
300
|
- spec/support/delegated_routine.rb
|
295
301
|
- spec/support/delegating_routine.rb
|