lev 5.0.0 → 6.0.0

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 CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- MGNhMzZiZWRhNDllMzMwYjBhMmFkZTQ5YzJiYWNkNjYxMzQ4NmMzMg==
4
+ YmVhODFiNzc4NGRkZTAxYTg5MmVjZjg5OWRlMDVlNDUwMmVlNDIyYg==
5
5
  data.tar.gz: !binary |-
6
- NTdmM2U2NWY0YjlkNDM3N2ViZWU5NzQ3NmYyYjkyZDVkZDUwMzMyNw==
6
+ ZDU5YjNkMDRjYmE1N2VkYzQ1Njg5OWZjMDJlNzgyMjlmZDFiMzU1Yg==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- NDMyM2UzNzY2MDQxODlkNmNlMWJhOTRjYjJkZmI2OWE3ZWM1ZDAyZDU1OWZk
10
- ZmRhOWVjZWI5MGNhYWJmNWViMzJlNzlmMTQ0ZjVlMDNjODA1YzJkMDYwMzNj
11
- OTc4NGVmYzI2ZDUzOWUwNTVmM2E5NzMyOGQ0MjQ5MWMzMTJhNjM=
9
+ NDRlMDY4Y2NjMTg2OWJlNjVjZDVjNWVmYzRlNDRiOGYyZGEwNzFmYWUwNWRm
10
+ YWEwODRjZjcxNTU3ZjExNTYxYWZkYzI4NDRmMjUzZDk3ZWMyN2UzMjE4MDA1
11
+ NDU0YmI1MDljNGNkZDlhMTI5YWUzN2ZkMGZiMjYzMjVhMzhhMzk=
12
12
  data.tar.gz: !binary |-
13
- M2I4ODI3Mjg0MWJiYzYyZGM0ZDA0OTVmMmJhOTZmMjk0NGIxMDA5ZjUxNTlm
14
- NmI2ZmZhMTFmMWUyMDU0NzUwMjFlOGRkYjVhNWE3YjBhNTBkYmM0MmIxYTI0
15
- ZDAzYzMyNWYyOTExNjA1NTJjMTJhM2RlZjY3YzNhNzU4NzdkZGE=
13
+ NzJjOGNjZmEyZDJmYWQxY2U1Y2I5Yjc5Y2I3NzIxYzE3YjAxNDQ1ZjMzMjdj
14
+ MTJlNzRkN2U1MGU4MDM2NWRlMGYzNTlkZWI5N2UyZjc4M2RlODAxYmZmZjFj
15
+ YTc5MjExNjdhMzM2NWMyYTliYjNhZDA5N2RlM2Q3MDk1MjFhZTc=
data/README.md CHANGED
@@ -100,10 +100,7 @@ end
100
100
 
101
101
  Additionally, see below for a discussion on how to transfer errors from ActiveRecord models.
102
102
 
103
- Any `StandardError` raised within a routine will be caught and transformed into a fatal error with `:kind` set to `:exception`. The caller of this routine can choose to reraise this exception by calling `reraise_exception!` on the returned errors object:
104
-
105
- result = MyRoutine.call(42)
106
- result.errors.reraise_exception! # does nothing if there were no exception errors
103
+ If an exception is raised in a routine, it will bubble out. It will also fail the job status and add information about the exception to the job error list.
107
104
 
108
105
  Relatedly, a convenience method is provided if the caller wants to raise an exception if there were any errors returned (whether or not they themselves were caused by an exception)
109
106
 
@@ -23,7 +23,7 @@ module Lev
23
23
  def initialize(attrs = {})
24
24
  @id = attrs[:id] || attrs['id'] || SecureRandom.uuid
25
25
  @status = attrs[:status] || attrs['status'] || STATE_UNKNOWN
26
- @progress = attrs[:progress] || attrs['progress'] || set_progress(0)
26
+ @progress = attrs[:progress] || attrs['progress'] || 0
27
27
  @errors = attrs[:errors] || attrs['errors'] || []
28
28
 
29
29
  set({ id: id,
@@ -35,8 +35,8 @@ module Lev
35
35
  def self.find(id)
36
36
  attrs = { id: id }
37
37
 
38
- if job = store.fetch(job_key(id))
39
- attrs.merge!(JSON.parse(job))
38
+ if job = fetch_and_parse(job_key(id))
39
+ attrs.merge!(job)
40
40
  else
41
41
  attrs.merge!(status: STATE_UNKNOWN)
42
42
  end
@@ -59,17 +59,22 @@ module Lev
59
59
  progress
60
60
  end
61
61
 
62
- STATES.each do |state|
62
+ (STATES - [STATE_COMPLETED]).each do |state|
63
63
  define_method("#{state}!") do
64
64
  set(status: state)
65
65
  end
66
66
  end
67
67
 
68
+ def completed!
69
+ set({status: STATE_COMPLETED, progress: 1.0})
70
+ end
71
+
68
72
  def add_error(error, options = { })
69
73
  options = { is_fatal: false }.merge(options)
70
74
  @errors << { is_fatal: options[:is_fatal],
71
75
  code: error.code,
72
- message: error.message }
76
+ message: error.message,
77
+ data: error.data }
73
78
  set(errors: @errors)
74
79
  end
75
80
 
@@ -103,6 +108,7 @@ module Lev
103
108
  RESERVED_KEYS = [:id, :status, :progress, :errors]
104
109
 
105
110
  def set(incoming_hash)
111
+ incoming_hash = incoming_hash.stringify_keys
106
112
  incoming_hash = stored.merge(incoming_hash)
107
113
  incoming_hash.each { |k, v| instance_variable_set("@#{k}", v) }
108
114
  self.class.store.write(job_key, incoming_hash.to_json)
@@ -113,22 +119,25 @@ module Lev
113
119
  Lev.configuration.job_store
114
120
  end
115
121
 
122
+ def self.fetch_and_parse(job_key)
123
+ fetched = store.fetch(job_key)
124
+ return nil if fetched.nil?
125
+ JSON.parse(fetched).stringify_keys!
126
+ end
127
+
116
128
  def self.job_ids
117
129
  store.fetch(job_key('lev_job_ids')) || []
118
130
  end
119
131
 
120
132
  def stored
121
- if found = self.class.store.fetch(job_key)
122
- JSON.parse(found)
123
- else
124
- {}
125
- end
133
+ self.class.fetch_and_parse(job_key) || {}
126
134
  end
127
135
 
128
136
  def track_job_id
129
137
  ids = self.class.job_ids
138
+ return if ids.include?(@id)
130
139
  ids << @id
131
- self.class.store.write(self.class.job_key('lev_job_ids'), ids.uniq)
140
+ self.class.store.write(self.class.job_key('lev_job_ids'), ids)
132
141
  end
133
142
 
134
143
  def job_key
@@ -7,11 +7,11 @@ module Lev
7
7
  when ActiveRecord::Base, Lev::Paramifier
8
8
  source.errors.each_with_type_and_message do |attribute, type, message|
9
9
  target_routine.nonfatal_error(
10
- code: type,
10
+ code: type,
11
11
  data: {
12
12
  model: source,
13
13
  attribute: attribute
14
- },
14
+ },
15
15
  kind: :activerecord,
16
16
  message: message,
17
17
  offending_inputs: input_mapper.map(attribute)
@@ -38,4 +38,4 @@ module Lev
38
38
 
39
39
  end
40
40
 
41
- end
41
+ end
@@ -22,7 +22,8 @@ module Lev
22
22
  routine_job.failed!
23
23
 
24
24
  if raise_fatal_errors
25
- raise StandardError, args.to_a.map { |i| i.join(' ') }.join(' - ')
25
+ # Use special FatalError type so Routine doesn't re-add job errors
26
+ raise Lev::FatalError, args.to_a.map { |i| i.join(' ') }.join(' - ')
26
27
  else
27
28
  throw :fatal_errors_encountered
28
29
  end
@@ -3,3 +3,4 @@ class Lev::SecurityTransgression < StandardError; end
3
3
  class Lev::AlgorithmError < StandardError; end
4
4
  class Lev::AbstractMethodCalled < StandardError; end
5
5
  class Lev::IllegalArgument < StandardError; end
6
+ class Lev::FatalError < StandardError; end
@@ -269,9 +269,9 @@ module Lev
269
269
 
270
270
  job.working!
271
271
 
272
- in_transaction do
273
- catch :fatal_errors_encountered do
274
- begin
272
+ begin
273
+ in_transaction do
274
+ catch :fatal_errors_encountered do
275
275
  if self.class.delegates_to
276
276
  run(self.class.delegates_to, *args, &block)
277
277
  else
@@ -279,10 +279,22 @@ module Lev
279
279
  end
280
280
  end
281
281
  end
282
- end
283
282
 
284
- @after_transaction_blocks.each do |block|
285
- block.call
283
+ @after_transaction_blocks.each do |block|
284
+ block.call
285
+ end
286
+ rescue Exception => e
287
+ # Let exceptions escape but make sure to note the error in the job
288
+ # if not already done
289
+ if !e.is_a?(Lev::FatalError)
290
+ error = Error.new(code: :exception,
291
+ message: e.message,
292
+ data: e.backtrace.first)
293
+ job.add_error(error, is_fatal: true)
294
+ job.failed!
295
+ end
296
+
297
+ raise e
286
298
  end
287
299
 
288
300
  job.completed! if !errors?
@@ -1,3 +1,3 @@
1
1
  module Lev
2
- VERSION = "5.0.0"
2
+ VERSION = "6.0.0"
3
3
  end
@@ -33,4 +33,44 @@ RSpec.describe 'ActiveJob routines' do
33
33
  expect(Lev::BackgroundJob.send(:job_ids)).to eq([job_id1])
34
34
  end
35
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
+ end
44
+
45
+ context 'exception raised' do
46
+ before { ::ActiveJob::Base.queue_adapter = :inline }
47
+ after { ::ActiveJob::Base.queue_adapter = :test }
48
+
49
+ class ExceptionalRoutine
50
+ lev_routine
51
+
52
+ protected
53
+ def exec
54
+ raise TypeError, 'howdy there'
55
+ end
56
+ end
57
+
58
+ it 'lets exception escape, job is failed and has error details' do
59
+ Lev.configuration.job_store.clear
60
+
61
+ expect{
62
+ ExceptionalRoutine.perform_later
63
+ }.to raise_error(TypeError)
64
+
65
+ job = Lev::BackgroundJob.all.first
66
+
67
+ expect(job.status).to eq Lev::BackgroundJob::STATE_FAILED
68
+
69
+ error = job.errors.first
70
+
71
+ expect(error["code"]).to eq "exception"
72
+ expect(error["message"]).to eq "howdy there"
73
+ expect(error["data"]).to be_a String
74
+ end
75
+ end
36
76
  end
@@ -0,0 +1,54 @@
1
+ require 'spec_helper'
2
+
3
+ describe Lev::BackgroundJob do
4
+
5
+ context 'delayed routine' do
6
+ class DelayedRoutine
7
+ lev_routine
8
+ protected
9
+ def exec; end
10
+ end
11
+
12
+ subject(:job) { described_class.all.last }
13
+
14
+ before do
15
+ Lev.configuration.job_store.clear
16
+ allow(SecureRandom).to receive(:uuid) { '123abc' }
17
+ DelayedRoutine.perform_later
18
+ end
19
+
20
+ it 'behaves as a nice ruby object' do
21
+ expect(job.id).to eq('123abc')
22
+ expect(job.status).to eq(Lev::BackgroundJob::STATE_QUEUED)
23
+ expect(job.progress).to eq(0.0)
24
+ end
25
+
26
+ it 'is unknown when not found' do
27
+ foo = described_class.find('noooooo')
28
+ expect(foo.status).to eq(Lev::BackgroundJob::STATE_UNKNOWN)
29
+ end
30
+
31
+ it 'uses as_json' do
32
+ json = job.as_json
33
+
34
+ expect(json).to eq({
35
+ 'id' => '123abc',
36
+ 'status' => Lev::BackgroundJob::STATE_QUEUED,
37
+ 'progress' => 0.0,
38
+ 'errors' => []
39
+ })
40
+
41
+ job.save(foo: :bar)
42
+ json = job.as_json
43
+
44
+ expect(json['foo']).to eq('bar')
45
+ end
46
+ end
47
+
48
+ it 'sets progress to 100% when completed' do
49
+ job = Lev::BackgroundJob.new
50
+ job.completed!
51
+ expect(job.progress).to eq 1
52
+ end
53
+
54
+ end
@@ -33,7 +33,7 @@ RSpec.describe 'Statused Routines' do
33
33
  id = StatusedRoutine.perform_later
34
34
  job = Lev::BackgroundJob.find(id)
35
35
  expect(job.status).to eq(Lev::BackgroundJob::STATE_COMPLETED)
36
- expect(job.progress).to eq(0.9)
36
+ expect(job.progress).to eq(1.0)
37
37
  end
38
38
  end
39
39
  end
@@ -51,7 +51,8 @@ RSpec.describe 'Statused Routines' do
51
51
  job.add_error(errors)
52
52
  expect(job.errors).to eq([{ is_fatal: false,
53
53
  code: 'bad',
54
- message: 'awful' }])
54
+ message: 'awful',
55
+ data: nil }])
55
56
  end
56
57
  end
57
58
 
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: 5.0.0
4
+ version: 6.0.0
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-07-30 00:00:00.000000000 Z
11
+ date: 2015-08-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -240,11 +240,10 @@ files:
240
240
  - lib/lev/utilities.rb
241
241
  - lib/lev/version.rb
242
242
  - spec/active_job_routines_spec.rb
243
+ - spec/background_job_spec.rb
243
244
  - spec/create_sprocket_spec.rb
244
245
  - spec/deep_merge_spec.rb
245
246
  - spec/delegates_to_spec.rb
246
- - spec/errors_spec.rb
247
- - spec/lev/status_spec.rb
248
247
  - spec/outputs_spec.rb
249
248
  - spec/paramify_handler_spec.rb
250
249
  - spec/routine_spec.rb
@@ -286,11 +285,10 @@ specification_version: 4
286
285
  summary: Ride the rails but don't touch them.
287
286
  test_files:
288
287
  - spec/active_job_routines_spec.rb
288
+ - spec/background_job_spec.rb
289
289
  - spec/create_sprocket_spec.rb
290
290
  - spec/deep_merge_spec.rb
291
291
  - spec/delegates_to_spec.rb
292
- - spec/errors_spec.rb
293
- - spec/lev/status_spec.rb
294
292
  - spec/outputs_spec.rb
295
293
  - spec/paramify_handler_spec.rb
296
294
  - spec/routine_spec.rb
@@ -1,5 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Lev::Errors do
4
-
5
- end
@@ -1,44 +0,0 @@
1
- require 'spec_helper'
2
-
3
- RSpec.describe Lev::BackgroundJob do
4
- class DelayedRoutine
5
- lev_routine
6
- protected
7
- def exec; end
8
- end
9
-
10
- subject(:job) { described_class.all.last }
11
-
12
- before do
13
- Lev.configuration.job_store.clear
14
- allow(SecureRandom).to receive(:uuid) { '123abc' }
15
- DelayedRoutine.perform_later
16
- end
17
-
18
- it 'behaves as a nice ruby object' do
19
- expect(job.id).to eq('123abc')
20
- expect(job.status).to eq(Lev::BackgroundJob::STATE_QUEUED)
21
- expect(job.progress).to eq(0.0)
22
- end
23
-
24
- it 'is unknown when not found' do
25
- foo = described_class.find('noooooo')
26
- expect(foo.status).to eq(Lev::BackgroundJob::STATE_UNKNOWN)
27
- end
28
-
29
- it 'uses as_json' do
30
- json = job.as_json
31
-
32
- expect(json).to eq({
33
- 'id' => '123abc',
34
- 'status' => Lev::BackgroundJob::STATE_QUEUED,
35
- 'progress' => 0.0,
36
- 'errors' => []
37
- })
38
-
39
- job.save(foo: :bar)
40
- json = job.as_json
41
-
42
- expect(json['foo']).to eq('bar')
43
- end
44
- end