lev 5.0.0 → 6.0.0

Sign up to get free protection for your applications and to get access to all the features.
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