lev 7.0.0 → 7.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- NjE5NzA3YmUwMzVmNTljMWZlMjE3MWMzNGEzODE4MjcyOTY5YWU5Zg==
5
- data.tar.gz: !binary |-
6
- OGVmOWM3NzViMDdlY2UwNTY0MDdmZTM1MGE1MWMyMTQ5NDc1OGRlOQ==
2
+ SHA1:
3
+ metadata.gz: ef0f435c1b9d31f93f019ec4ed9a39a7f5168fd2
4
+ data.tar.gz: 7b20edd8fc30967cae1a66e409d42d5ec45a54dd
7
5
  SHA512:
8
- metadata.gz: !binary |-
9
- N2E2ZTBiYTM2M2JhNDI1YmRiMmFiMDU5Y2IxMDY0N2IxMzMwMzQ2NGY1YjFi
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 `job` object and can call the following methods:
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 job status to 'queued'
440
- * `working!` Sets the job status to 'working'
441
- * `succeeded!` Sets the job status to 'succeeded'
442
- * `failed!` Sets the job status to 'failed'
443
- * `killed!` Sets the job status to 'killed'
444
- * `save(hash)` Takes a hash of key value pairs and writes those keys and values to the job status; there are several reserved keys which cannot be used (and which will blow up if you try to use them)
445
- * `add_error(is_fatal, error)` takes a boolean and a Lev `Error` object and adds its data to an array of `errors` in the job status hash.
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 job objects also have query methods to check if a job is in a given state, e.g. `queued?`. `completed?` and `incomplete` convenience methods are provided as well. A job 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 (e.g. `BackgroundJob.queued`) are provided to return all jobs in a given state.
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 job calls are no-ops. When a routine is invoked with `perform_later`, the job object actually records the jobs to a store of your choice. The store is configured in the Lev configuration block, e.g.:
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.job_store = whatever
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
- The store needs to respond to the following methods:
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/background_job'
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 :job_store
68
- attr_accessor :job_store_namespace
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
- @job_store = Lev::MemoryStore.new
77
- @job_store_namespace = "lev_job"
83
+ @create_status_proc = ->(*) { NullStatus.new }
84
+ @find_status_proc = ->(*) { NullStatus.new }
78
85
  @job_class = ::ActiveJob::Base
79
86
  super
80
87
  end
@@ -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
- # To enable tracking of this job's status, create a new BackgroundJob object
9
- # and push it on to the arguments so that in `perform` it can be peeled
10
- # off and handed to the routine instance. The BackgroundJob UUID is returned
11
- # so that callers can track the status.
12
- job = Lev::BackgroundJob.create
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
- job.queued!
16
+ status.queued!
18
17
  super(*args, &block)
19
18
 
20
- job.id
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::BackgroundJob.find!(id))
27
+ routine_instance = routine_class.new(Lev::find_status(id))
29
28
 
30
29
  routine_instance.call(*args, &block)
31
30
  end
@@ -24,10 +24,6 @@ module Lev
24
24
  ErrorTranslator.translate(self)
25
25
  end
26
26
 
27
- def to_s
28
- inspect
29
- end
30
-
31
27
  end
32
28
 
33
- end
29
+ end
@@ -4,8 +4,8 @@ module Lev
4
4
  #
5
5
  class Errors < Array
6
6
 
7
- def initialize(routine_job = nil, raise_fatal_errors = false)
8
- @routine_job = routine_job || NoBackgroundJob.new
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
- routine_job.add_error(error, is_fatal: fail)
19
+ routine_status.add_error(error)
20
20
 
21
21
  if fail
22
- routine_job.failed!
22
+ routine_status.failed!
23
23
 
24
24
  if raise_fatal_errors
25
- # Use special FatalError type so Routine doesn't re-add job errors
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 :routine_job
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
@@ -265,7 +265,7 @@ module Lev
265
265
  def call(*args, &block)
266
266
  @after_transaction_blocks = []
267
267
 
268
- job.working!
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 job
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
- job.add_error(error, is_fatal: true)
294
- job.failed!
293
+ status.add_error(error)
294
+ status.failed!
295
295
  end
296
296
 
297
297
  raise e
298
298
  end
299
299
 
300
- job.succeeded! if !errors?
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(job = nil)
434
- # If someone cares about the job, they'll pass it in; otherwise all
435
- # job updates go into the bit bucket.
436
- @job = job
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(job, topmost_runner.class.raise_fatal_errors?))
446
+ Errors.new(status, topmost_runner.class.raise_fatal_errors?))
450
447
  end
451
448
 
452
449
  def reset_result!
@@ -1,3 +1,3 @@
1
1
  module Lev
2
- VERSION = "7.0.0"
2
+ VERSION = "7.0.1"
3
3
  end
@@ -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
- Lev.configuration.job_store.clear
46
+ Jobba.all.delete_all!
60
47
 
61
48
  expect{
62
49
  ExceptionalRoutine.perform_later
63
50
  }.to raise_error(TypeError)
64
51
 
65
- job = Lev::BackgroundJob.all.first
52
+ status = Jobba.all.run.to_a.first
66
53
 
67
- expect(job.status).to eq Lev::BackgroundJob::STATE_FAILED
54
+ expect(status).to be_failed
68
55
 
69
- error = job.errors.first
56
+ error = status.errors.first
70
57
 
71
58
  expect(error["code"]).to eq "exception"
72
59
  expect(error["message"]).to eq "howdy there"
@@ -21,9 +21,8 @@ RSpec.configure do |config|
21
21
  config.order = 'random'
22
22
  end
23
23
 
24
-
25
24
  require 'lev'
26
- require 'debugger'
25
+ require 'byebug'
27
26
 
28
27
  require 'transaction_retry'
29
28
  TransactionRetry.apply_activerecord_patch
@@ -5,166 +5,44 @@ class StatusedRoutine
5
5
 
6
6
  protected
7
7
  def exec
8
- job.set_progress(9, 10)
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
- subject(:job) { Lev::BackgroundJob.new }
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
- job = Lev::BackgroundJob.find(id)
21
+ status = Jobba::Status.find(id)
19
22
 
20
- expect(job.status).to eq(Lev::BackgroundJob::STATE_QUEUED)
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 working when called' do
28
- expect_any_instance_of(Lev::BackgroundJob).to receive(:working!)
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 job object on completion, returning other data' do
35
+ it 'completes the status object on completion, returning other data' do
33
36
  id = StatusedRoutine.perform_later
34
- job = Lev::BackgroundJob.find(id)
35
- expect(job.status).to eq(Lev::BackgroundJob::STATE_SUCCEEDED)
36
- expect(job.progress).to eq(1.0)
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
@@ -0,0 +1,13 @@
1
+ require 'jobba'
2
+
3
+ module Lev::Jobba
4
+
5
+ def self.use_jobba
6
+ Lev.configure do |config|
7
+ config.create_status_proc = ->(*) { Jobba::Status.create! }
8
+ config.find_status_proc = ->(id) { Jobba::Status.find!(id) }
9
+ end
10
+ true
11
+ end
12
+
13
+ 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.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: debugger
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/no_background_job.rb
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.6
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
@@ -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
@@ -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