pipeline 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,16 @@
1
+ 0.0.7
2
+ =====
3
+
4
+ Features:
5
+ * Allowing for user-defined default failure mode: when generic errors are raised, pipeline can be paused (default) or fail. Usage:
6
+ class MySamplePipeline < Pipeline::Base
7
+ self.default_failure_mode = :cancel
8
+ end
9
+
10
+ Bug Fix:
11
+ * Refreshes the pipeline and stage statuses before execution (at runtime) in case of offline status update
12
+ * Rescue from ActiveRecord::RecordNotFound instead of relying on ActiveRecord#find(id) returning nil
13
+
1
14
  0.0.6
2
15
  =====
3
16
 
data/Rakefile CHANGED
@@ -77,7 +77,7 @@ begin
77
77
  Synthesis::Task.new("spec:synthesis") do |t|
78
78
  t.adapter = :rspec
79
79
  t.pattern = 'spec/**/*_spec.rb'
80
- t.ignored = ['Pipeline::FakePipeline', 'Delayed::Job']
80
+ t.ignored = ['Pipeline::FakePipeline', 'Delayed::Job', 'SampleStage', 'Logger']
81
81
  end
82
82
  rescue LoadError
83
83
  desc 'Synthesis rake task not available'
data/TODO CHANGED
@@ -4,6 +4,7 @@
4
4
  * Improve documentation (RDoc)
5
5
  * Adapter for other persistence frameworks (ActiveModel?)
6
6
  * Adapter for other background processing mechanisms (currently relying on dj's auto-retry)
7
+ * Max attempts on pipeline (or stage)
7
8
 
8
9
  Think more about:
9
10
  - Repeated code on status handling on pipeline instance/pipeline stage
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.6
1
+ 0.0.7
@@ -9,15 +9,17 @@ module Pipeline
9
9
 
10
10
  def resume(id)
11
11
  pipeline = Base.find(id)
12
- raise InvalidPipelineError.new("Invalid pipeline") unless pipeline
13
12
  raise InvalidStatusError.new(pipeline.status) unless pipeline.ok_to_resume?
14
13
  Delayed::Job.enqueue(pipeline)
14
+ rescue ActiveRecord::RecordNotFound
15
+ raise InvalidPipelineError.new("Invalid pipeline")
15
16
  end
16
17
 
17
18
  def cancel(id)
18
19
  pipeline = Base.find(id)
19
- raise InvalidPipelineError.new("Invalid pipeline") unless pipeline
20
20
  pipeline.cancel
21
+ rescue ActiveRecord::RecordNotFound
22
+ raise InvalidPipelineError.new("Invalid pipeline")
21
23
  end
22
24
  end
23
25
  end
@@ -14,11 +14,19 @@ module Pipeline
14
14
 
15
15
  class_inheritable_accessor :defined_stages, :instance_writer => false
16
16
  self.defined_stages = []
17
+
18
+ class_inheritable_accessor :failure_mode, :instance_writer => false
19
+ self.failure_mode = :pause
17
20
 
18
21
  def self.define_stages(stages)
19
22
  self.defined_stages = stages.build_chain
20
23
  end
21
24
 
25
+ def self.default_failure_mode=(mode)
26
+ new_mode = [:pause, :cancel].include?(mode) ? mode : :pause
27
+ self.failure_mode = new_mode
28
+ end
29
+
22
30
  def after_initialize
23
31
  self[:status] = :not_started if new_record?
24
32
  if new_record?
@@ -29,6 +37,7 @@ module Pipeline
29
37
  end
30
38
 
31
39
  def perform
40
+ reload unless new_record?
32
41
  raise InvalidStatusError.new(status) unless ok_to_resume?
33
42
  begin
34
43
  _setup
@@ -45,7 +54,7 @@ module Pipeline
45
54
  raise e
46
55
  end
47
56
  rescue
48
- self.status = :paused
57
+ self.status = (failure_mode == :cancel ? :failed : :paused)
49
58
  end
50
59
  end
51
60
 
@@ -37,6 +37,7 @@ module Pipeline
37
37
  end
38
38
 
39
39
  def perform
40
+ reload unless new_record?
40
41
  raise InvalidStatusError.new(status) unless [:not_started, :failed].include?(status)
41
42
  begin
42
43
  _setup
@@ -2,11 +2,11 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{pipeline}
5
- s.version = "0.0.6"
5
+ s.version = "0.0.7"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Danilo Sato"]
9
- s.date = %q{2009-08-12}
9
+ s.date = %q{2009-08-18}
10
10
  s.description = %q{Pipeline is a Rails plugin/gem to run asynchronous processes in a configurable pipeline.}
11
11
  s.email = %q{danilo@dtsato.com}
12
12
  s.extra_rdoc_files = [
@@ -50,8 +50,10 @@ module Pipeline
50
50
  Pipeline.resume('1')
51
51
  end
52
52
 
53
- it "should raise error is trying to resume invalid pipeline" do
54
- Pipeline::Base.should_receive(:find).and_return(nil)
53
+ it "should raise error if trying to resume invalid pipeline" do
54
+ Pipeline::Base.should_receive(:find).
55
+ with('1').
56
+ and_raise(ActiveRecord::RecordNotFound.new)
55
57
 
56
58
  lambda {Pipeline.resume('1')}.should raise_error(InvalidPipelineError, "Invalid pipeline")
57
59
  end
@@ -83,7 +85,9 @@ module Pipeline
83
85
  end
84
86
 
85
87
  it "should raise error is trying to cancel invalid pipeline" do
86
- Pipeline::Base.should_receive(:find).and_return(nil)
88
+ Pipeline::Base.should_receive(:find).
89
+ with('1').
90
+ and_raise(ActiveRecord::RecordNotFound.new)
87
91
 
88
92
  lambda {Pipeline.cancel('1')}.should raise_error(InvalidPipelineError, "Invalid pipeline")
89
93
  end
@@ -12,6 +12,34 @@ end
12
12
 
13
13
  class SecondStage < FirstStage; end # Ugly.. just so I don't have to write stub again
14
14
 
15
+ class IrrecoverableStage < FirstStage
16
+ def run
17
+ super
18
+ raise Pipeline::IrrecoverableError.new
19
+ end
20
+ end
21
+
22
+ class RecoverableInputRequiredStage < FirstStage
23
+ def run
24
+ super
25
+ raise Pipeline::RecoverableError.new("message", true)
26
+ end
27
+ end
28
+
29
+ class RecoverableStage < FirstStage
30
+ def run
31
+ super
32
+ raise Pipeline::RecoverableError.new("message")
33
+ end
34
+ end
35
+
36
+ class GenericErrorStage < FirstStage
37
+ def run
38
+ super
39
+ raise StandardError.new
40
+ end
41
+ end
42
+
15
43
  class SamplePipeline < Pipeline::Base
16
44
  define_stages FirstStage >> SecondStage
17
45
  end
@@ -20,9 +48,26 @@ module Pipeline
20
48
  describe Base do
21
49
 
22
50
  describe "- configuring" do
51
+ before(:each) do
52
+ class ::SamplePipeline
53
+ define_stages FirstStage >> SecondStage
54
+ end
55
+ end
56
+
23
57
  it "should allow accessing stages" do
24
58
  SamplePipeline.defined_stages.should == [FirstStage, SecondStage]
25
59
  end
60
+
61
+ it "should allow configuring default failure mode (pause by default)" do
62
+ SamplePipeline.default_failure_mode = :pause
63
+ SamplePipeline.failure_mode.should == :pause
64
+
65
+ SamplePipeline.default_failure_mode = :cancel
66
+ SamplePipeline.failure_mode.should == :cancel
67
+
68
+ SamplePipeline.default_failure_mode = :something_else
69
+ SamplePipeline.failure_mode.should == :pause
70
+ end
26
71
  end
27
72
 
28
73
  describe "- setup" do
@@ -59,6 +104,8 @@ module Pipeline
59
104
 
60
105
  retrieved_pipeline = Base.find(@pipeline.id.to_s)
61
106
  retrieved_pipeline.should === @pipeline
107
+
108
+ lambda {Base.find('invalid_id')}.should raise_error(ActiveRecord::RecordNotFound)
62
109
  end
63
110
 
64
111
  it "should persist type as single table inheritance" do
@@ -104,21 +151,18 @@ module Pipeline
104
151
 
105
152
  describe "- execution (success)" do
106
153
  before(:each) do
107
- @pipeline = SamplePipeline.new
154
+ class ::SamplePipeline
155
+ define_stages FirstStage >> SecondStage
156
+ end
157
+ @pipeline = ::SamplePipeline.new
108
158
  end
109
159
 
110
160
  it "should increment attempts" do
111
- failed_stage = SecondStage.new
112
- failed_stage.stub!(:run).and_raise(RecoverableError.new("message", true))
113
- SecondStage.stub!(:new).and_return(failed_stage)
114
-
115
161
  pipeline = SamplePipeline.new
116
162
 
163
+ pipeline.attempts.should == 0
117
164
  pipeline.perform
118
165
  pipeline.attempts.should == 1
119
-
120
- pipeline.perform
121
- pipeline.attempts.should == 2
122
166
  end
123
167
 
124
168
  it "should perform each stage" do
@@ -151,10 +195,10 @@ module Pipeline
151
195
 
152
196
  describe "- execution (irrecoverable error)" do
153
197
  before(:each) do
154
- failed_stage = SecondStage.new
155
- failed_stage.stub!(:run).and_raise(IrrecoverableError.new)
156
- SecondStage.stub!(:new).and_return(failed_stage)
157
- @pipeline = SamplePipeline.new
198
+ class ::SamplePipeline
199
+ define_stages FirstStage >> IrrecoverableStage
200
+ end
201
+ @pipeline = ::SamplePipeline.new
158
202
  end
159
203
 
160
204
  it "should not re-raise error" do
@@ -175,9 +219,9 @@ module Pipeline
175
219
 
176
220
  describe "- execution (recoverable error that doesn't require user input)" do
177
221
  before(:each) do
178
- failed_stage = SecondStage.new
179
- failed_stage.stub!(:run).and_raise(RecoverableError.new)
180
- SecondStage.stub!(:new).and_return(failed_stage)
222
+ class ::SamplePipeline
223
+ define_stages FirstStage >> RecoverableStage
224
+ end
181
225
  @pipeline = SamplePipeline.new
182
226
  end
183
227
 
@@ -199,9 +243,9 @@ module Pipeline
199
243
 
200
244
  describe "- execution (recoverable error that requires user input)" do
201
245
  before(:each) do
202
- failed_stage = SecondStage.new
203
- failed_stage.stub!(:run).and_raise(RecoverableError.new('message', true))
204
- SecondStage.stub!(:new).and_return(failed_stage)
246
+ class ::SamplePipeline
247
+ define_stages FirstStage >> RecoverableInputRequiredStage
248
+ end
205
249
  @pipeline = SamplePipeline.new
206
250
  end
207
251
 
@@ -221,11 +265,11 @@ module Pipeline
221
265
  end
222
266
  end
223
267
 
224
- describe "- execution (other errors will pause the pipeline)" do
268
+ describe "- execution (other errors will use failure mode to pause/cancel pipeline)" do
225
269
  before(:each) do
226
- failed_stage = SecondStage.new
227
- failed_stage.stub!(:run).and_raise(StandardError.new)
228
- SecondStage.stub!(:new).and_return(failed_stage)
270
+ class ::SamplePipeline
271
+ define_stages FirstStage >> GenericErrorStage
272
+ end
229
273
  @pipeline = SamplePipeline.new
230
274
  end
231
275
 
@@ -233,26 +277,38 @@ module Pipeline
233
277
  lambda {@pipeline.perform}.should_not raise_error(StandardError)
234
278
  end
235
279
 
236
- it "should update status" do
280
+ it "should update status (pause mode)" do
281
+ SamplePipeline.default_failure_mode = :pause
237
282
  @pipeline.perform
238
283
  @pipeline.status.should == :paused
239
284
  end
240
285
 
241
- it "should save status" do
286
+ it "should save status (pause mode)" do
287
+ SamplePipeline.default_failure_mode = :pause
242
288
  @pipeline.save!
243
289
  @pipeline.perform
244
290
  @pipeline.reload.status.should == :paused
245
291
  end
292
+
293
+ it "should update status (cancel mode)" do
294
+ SamplePipeline.default_failure_mode = :cancel
295
+ @pipeline.perform
296
+ @pipeline.status.should == :failed
297
+ end
298
+
299
+ it "should save status (cancel mode)" do
300
+ SamplePipeline.default_failure_mode = :cancel
301
+ @pipeline.save!
302
+ @pipeline.perform
303
+ @pipeline.reload.status.should == :failed
304
+ end
246
305
  end
247
306
 
248
307
  describe "- execution (retrying)" do
249
308
  before(:each) do
250
- @passing_stage = FirstStage.new
251
- FirstStage.stub!(:new).and_return(@passing_stage)
252
-
253
- @failed_stage = SecondStage.new
254
- @failed_stage.stub!(:run).and_raise(RecoverableError.new('message', true))
255
- SecondStage.stub!(:new).and_return(@failed_stage)
309
+ class ::SamplePipeline
310
+ define_stages FirstStage >> RecoverableInputRequiredStage
311
+ end
256
312
  @pipeline = SamplePipeline.new
257
313
  end
258
314
 
@@ -273,12 +329,26 @@ module Pipeline
273
329
 
274
330
  it "should skip completed stages" do
275
331
  @pipeline.perform
276
- @passing_stage.attempts.should == 1
277
- @failed_stage.attempts.should == 1
332
+ @pipeline.stages[0].attempts.should == 1
333
+ @pipeline.stages[1].attempts.should == 1
278
334
 
279
335
  @pipeline.perform
280
- @passing_stage.attempts.should == 1
281
- @failed_stage.attempts.should == 2
336
+ @pipeline.stages[0].attempts.should == 1
337
+ @pipeline.stages[1].attempts.should == 2
338
+ end
339
+
340
+ it "should refresh object (in case it was cancelled after job was scheduled)" do
341
+ # Gets paused on the first time
342
+ @pipeline.save!
343
+ @pipeline.perform
344
+
345
+ # Status gets updated to failed on the database (not on the current instance)
346
+ same_pipeline = SamplePipeline.find(@pipeline.id)
347
+ same_pipeline.send(:status=, :failed)
348
+ same_pipeline.save!
349
+
350
+ # Retrying should fail because pipeline is now failed
351
+ lambda {@pipeline.perform}.should raise_error(InvalidStatusError, "Status is already failed")
282
352
  end
283
353
  end
284
354
 
@@ -323,12 +393,9 @@ module Pipeline
323
393
 
324
394
  describe "- cancelling" do
325
395
  before(:each) do
326
- @passing_stage = FirstStage.new
327
- FirstStage.stub!(:new).and_return(@passing_stage)
328
-
329
- @failed_stage = SecondStage.new
330
- @failed_stage.stub!(:run).and_raise(RecoverableError.new('message', true))
331
- SecondStage.stub!(:new).and_return(@failed_stage)
396
+ class ::SamplePipeline
397
+ define_stages FirstStage >> RecoverableInputRequiredStage
398
+ end
332
399
  @pipeline = SamplePipeline.new
333
400
  @pipeline.perform
334
401
  end
@@ -76,6 +76,8 @@ module Pipeline
76
76
 
77
77
  s = SampleStage.find(@stage.id)
78
78
  s.should === @stage
79
+
80
+ lambda {Base.find('invalid_id')}.should raise_error(ActiveRecord::RecordNotFound)
79
81
  end
80
82
 
81
83
  it "should persist type as single table inheritance" do
@@ -113,12 +115,9 @@ module Pipeline
113
115
  end
114
116
 
115
117
  it "should increment attempts" do
116
- @stage.stub!(:run).and_raise(StandardError.new)
117
- lambda {@stage.perform}.should raise_error(StandardError)
118
+ @stage.attempts.should == 0
119
+ @stage.perform
118
120
  @stage.attempts.should == 1
119
-
120
- lambda {@stage.perform}.should raise_error(StandardError)
121
- @stage.attempts.should == 2
122
121
  end
123
122
 
124
123
  it "should call template method #run" do
@@ -126,6 +125,7 @@ module Pipeline
126
125
  @stage.perform
127
126
  @stage.should be_executed
128
127
  end
128
+
129
129
  end
130
130
 
131
131
  describe "- execution (failure)" do
@@ -184,6 +184,21 @@ module Pipeline
184
184
  lambda {@stage.perform}.should raise_error
185
185
  end
186
186
 
187
+ it "should refresh object (in case it was cancelled after job was scheduled)" do
188
+ # Gets failed on the first time
189
+ @stage.save!
190
+ @stage.should_receive(:run).and_raise(RecoverableError.new("message"))
191
+ lambda {@stage.perform}.should raise_error(RecoverableError)
192
+
193
+ # Status gets updated to completed on the database (not on the current instance)
194
+ same_stage = SampleStage.find(@stage.id)
195
+ same_stage.send(:status=, :completed)
196
+ same_stage.save!
197
+
198
+ # Retrying should fail because stage is now completed
199
+ lambda {@stage.perform}.should raise_error(InvalidStatusError, "Status is already completed")
200
+ end
201
+
187
202
  end
188
203
 
189
204
  describe "- execution (in progress)" do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pipeline
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.6
4
+ version: 0.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Danilo Sato
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-08-12 00:00:00 +01:00
12
+ date: 2009-08-18 00:00:00 +01:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency