orchestrated 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +5 -5
- data/lib/orchestrated/completion.rb +1 -1
- data/lib/orchestrated/dependency.rb +4 -4
- data/lib/orchestrated/version.rb +1 -1
- data/spec/spec_helper.rb +10 -0
- data/spec/unit/cancellation_spec.rb +46 -12
- data/spec/unit/orchestrated_spec.rb +54 -1
- metadata +1 -1
data/README.markdown
CHANGED
@@ -5,7 +5,7 @@ The [delayed_job](https://github.com/collectiveidea/delayed_job) Ruby Gem provid
|
|
5
5
|
|
6
6
|
By breaking up otherwise serial execution into multiple queued jobs, a program can be made more scalable. This sort of distributed queue-processing architecture has a long and successful history in data processing.
|
7
7
|
|
8
|
-
Queuing works well for simple tasks. By simple we mean
|
8
|
+
Queuing works well for simple tasks. By simple we mean the task can be done all at once, in one piece. It has no dependencies on other tasks. This works well for performing a file upload task in the background (to avoid tying up a Ruby virtual machine process/thread). More complex (compound) multi-part tasks, however, do not fit this model. Examples of complex (compound) tasks include:
|
9
9
|
|
10
10
|
1. pipelined (multi-step) generation of complex PDF documents
|
11
11
|
2. extract/transfer/load (ETL) jobs that may load thousands of database records
|
@@ -40,15 +40,15 @@ The API
|
|
40
40
|
To orchestrate (methods) on your own classes you simply call ```acts_as_orchestrated``` in the class definition like this:
|
41
41
|
|
42
42
|
```ruby
|
43
|
-
class
|
43
|
+
class Xform
|
44
44
|
|
45
45
|
acts_as_orchestrated
|
46
46
|
|
47
|
-
def
|
47
|
+
def merge(many, one)
|
48
48
|
...
|
49
49
|
end
|
50
50
|
|
51
|
-
def
|
51
|
+
def load(stuff)
|
52
52
|
...
|
53
53
|
end
|
54
54
|
|
@@ -109,7 +109,7 @@ See the completion_spec for examples of how to combine these different prerequis
|
|
109
109
|
Key Concept: Orchestration State
|
110
110
|
--------------------------------
|
111
111
|
|
112
|
-
An orchestration can be in one of
|
112
|
+
An orchestration can be in one of a few states:
|
113
113
|
|
114
114
|
![Alt text](https://github.com/paydici/orchestrated/raw/master/Orchestrated::Orchestration_state.png 'Orchestration States')
|
115
115
|
|
@@ -72,7 +72,7 @@ module Orchestrated
|
|
72
72
|
notify_dependents_of_completion
|
73
73
|
end
|
74
74
|
def prerequisite_canceled
|
75
|
-
|
75
|
+
notify_dependents_of_cancellation unless prerequisite_associations.without_states('canceled').exists?
|
76
76
|
end
|
77
77
|
end
|
78
78
|
class OrchestrationCompletionShim < CompletionExpression
|
@@ -16,11 +16,11 @@ module Orchestrated
|
|
16
16
|
event :prerequisite_canceled do
|
17
17
|
transition :incomplete => :canceled
|
18
18
|
end
|
19
|
-
after_transition any => :complete do |
|
20
|
-
|
19
|
+
after_transition any => :complete do |dependency, transition|
|
20
|
+
dependency.dependent.prerequisite_complete
|
21
21
|
end
|
22
|
-
after_transition any => :canceled do |
|
23
|
-
|
22
|
+
after_transition any => :canceled do |dependency, transition|
|
23
|
+
dependency.dependent.prerequisite_canceled
|
24
24
|
end
|
25
25
|
end
|
26
26
|
end
|
data/lib/orchestrated/version.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -84,6 +84,7 @@ require 'spec_helper_methods'
|
|
84
84
|
require 'database_cleaner' # see comments below
|
85
85
|
|
86
86
|
RSpec.configure do |config|
|
87
|
+
|
87
88
|
# This standard Rails approach won't work in this project (which is not
|
88
89
|
# _really_ a Rails app after all.
|
89
90
|
# config.use_transactional_fixtures = true
|
@@ -107,5 +108,14 @@ RSpec.configure do |config|
|
|
107
108
|
# --seed 1234
|
108
109
|
config.order = "random"
|
109
110
|
|
111
|
+
# Use color in STDOUT
|
112
|
+
config.color_enabled = true
|
113
|
+
|
114
|
+
# Use color not only in STDOUT but also in pagers and files
|
115
|
+
config.tty = true
|
116
|
+
|
117
|
+
# Use the specified formatter
|
118
|
+
config.formatter = :progress # :documentation :progress, :html, :textmate
|
119
|
+
|
110
120
|
config.include SpecHelperMethods
|
111
121
|
end
|
@@ -2,27 +2,42 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
require 'orchestrated'
|
4
4
|
|
5
|
-
|
5
|
+
shared_context 'cancelling first prerequisite' do
|
6
6
|
before(:each) do
|
7
|
-
|
7
|
+
first_prerequisite.cancel!
|
8
8
|
end
|
9
|
+
end
|
10
|
+
shared_context 'cancelling all prerequisites' do
|
11
|
+
before(:each) do
|
12
|
+
first_prerequisite.cancel!
|
13
|
+
last_prerequisite.cancel!
|
14
|
+
end
|
15
|
+
end
|
16
|
+
shared_examples_for 'cancellation:' do
|
9
17
|
it 'dependent should be in the "canceled" state' do
|
10
|
-
expect(
|
18
|
+
expect(dependent.reload.canceled?).to be_true
|
19
|
+
end
|
20
|
+
end
|
21
|
+
shared_examples_for "cancellation doesn't (cancel):" do
|
22
|
+
it 'dependent not should be in the "canceled" state' do
|
23
|
+
expect(dependent.reload.canceled?).to be_false
|
11
24
|
end
|
12
25
|
end
|
13
|
-
|
14
26
|
shared_examples_for 'cannot cancel:' do
|
15
|
-
it '
|
16
|
-
expect{@
|
27
|
+
it 'should raise an error when we try to cancel it' do
|
28
|
+
expect{@first_prerequisite.cancel!}.to raise_error(StateMachine::InvalidTransition)
|
17
29
|
end
|
18
30
|
end
|
19
31
|
|
20
32
|
describe 'cancellation' do
|
33
|
+
attr_accessor :first_prerequisite, :last_prerequisite, :dependent
|
34
|
+
def dependent;@dependent;end
|
21
35
|
context 'directly on an orchestration' do
|
22
36
|
before(:each) do
|
23
|
-
@
|
37
|
+
@first_prerequisite = @dependent = First.new.orchestrated.do_first_thing(1)
|
24
38
|
end
|
25
39
|
context 'that is ready' do
|
40
|
+
include_context 'cancelling first prerequisite'
|
26
41
|
it_should_behave_like 'cancellation:'
|
27
42
|
it 'should never subsequently deliver the orchestrated message' do
|
28
43
|
First.any_instance.should_not_receive(:do_first_thing)
|
@@ -31,36 +46,55 @@ describe 'cancellation' do
|
|
31
46
|
end
|
32
47
|
context 'that is succeeded' do
|
33
48
|
before(:each) do
|
34
|
-
@
|
49
|
+
@first_prerequisite.orchestration.state = 'succeeded'
|
35
50
|
end
|
36
51
|
it_should_behave_like 'cannot cancel:'
|
37
52
|
end
|
38
53
|
context 'that is failed' do
|
39
54
|
before(:each) do
|
40
|
-
@
|
55
|
+
@first_prerequisite.orchestration.state = 'failed'
|
41
56
|
end
|
42
57
|
it_should_behave_like 'cannot cancel:'
|
43
58
|
end
|
44
59
|
context 'that is canceled' do
|
45
60
|
before(:each) do
|
46
|
-
@
|
61
|
+
@first_prerequisite.orchestration.state = 'canceled'
|
47
62
|
end
|
48
63
|
it_should_behave_like 'cannot cancel:'
|
49
64
|
end
|
50
65
|
end
|
51
66
|
context 'of an orchestration that is depended on directly' do
|
52
67
|
before(:each) do
|
53
|
-
@dependent = Second.new.orchestrated( @
|
68
|
+
@dependent = Second.new.orchestrated( @first_prerequisite = First.new.orchestrated.do_first_thing(1)).do_second_thing(2)
|
54
69
|
end
|
70
|
+
include_context 'cancelling first prerequisite'
|
55
71
|
it_should_behave_like 'cancellation:'
|
56
72
|
end
|
57
73
|
context 'of an orchestration that is depended on through a LastCompletion' do
|
58
74
|
before(:each) do
|
59
75
|
@dependent = Second.new.orchestrated(
|
60
76
|
Orchestrated::LastCompletion.new <<
|
61
|
-
(@
|
77
|
+
(@first_prerequisite = First.new.orchestrated.do_first_thing(1))
|
62
78
|
).do_second_thing(2)
|
63
79
|
end
|
80
|
+
include_context 'cancelling first prerequisite'
|
64
81
|
it_should_behave_like 'cancellation:'
|
65
82
|
end
|
83
|
+
context 'of an orchestration that is depended on through a FirstCompletion with two prerequisites' do
|
84
|
+
before(:each) do
|
85
|
+
@dependent = Second.new.orchestrated(
|
86
|
+
Orchestrated::FirstCompletion.new <<
|
87
|
+
(@first_prerequisite = First.new.orchestrated.do_first_thing(3)) <<
|
88
|
+
(@last_prerequisite = First.new.orchestrated.do_first_thing(1))
|
89
|
+
).do_second_thing(2)
|
90
|
+
end
|
91
|
+
context 'after first prerequisite is canceled' do
|
92
|
+
include_context 'cancelling first prerequisite'
|
93
|
+
it_should_behave_like "cancellation doesn't (cancel):"
|
94
|
+
end
|
95
|
+
context 'after last prerequisite is canceled' do
|
96
|
+
include_context 'cancelling all prerequisites'
|
97
|
+
it_should_behave_like "cancellation:"
|
98
|
+
end
|
99
|
+
end
|
66
100
|
end
|
@@ -2,6 +2,16 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
require 'orchestrated'
|
4
4
|
|
5
|
+
|
6
|
+
shared_examples_for 'orchestration accessing prerequisite and dependent' do
|
7
|
+
it 'should reach interest at prerequisite' do
|
8
|
+
expect(@orchestration.prerequisite).to eq(@interest)
|
9
|
+
end
|
10
|
+
it 'should reach completion at dependent' do
|
11
|
+
expect(@orchestration.dependent).to eq(@completion)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
5
15
|
describe Orchestrated do
|
6
16
|
context 'initializing' do
|
7
17
|
it 'should not define orchestrated on Object' do
|
@@ -14,7 +24,7 @@ describe Orchestrated do
|
|
14
24
|
expect(First.public_method_defined?(:orchestrated)).to be_true
|
15
25
|
end
|
16
26
|
end
|
17
|
-
context 'a new
|
27
|
+
context 'a new object' do
|
18
28
|
let(:f){First.new}
|
19
29
|
context 'responding to messages without orchestration' do
|
20
30
|
let(:result){f.do_first_thing(2)} # 2 is a prime number
|
@@ -22,6 +32,49 @@ describe Orchestrated do
|
|
22
32
|
expect(result).to eq(5 * 2)
|
23
33
|
end
|
24
34
|
end
|
35
|
+
# I don't trust the has_one associations inside Orchestration to work right with the STI Completion hierarchy
|
36
|
+
# after spec'ing this I see that ActiveRecord does indeed qualify the has_one lookup (both of them)
|
37
|
+
# with the "type" field yay!
|
38
|
+
context 'creating orchestrated' do
|
39
|
+
# TODO: reimplement the next three functions as Factory Girl factories!
|
40
|
+
def create_orchestration
|
41
|
+
Orchestrated::Orchestration.new.tap do |orchestration|
|
42
|
+
orchestration.handler = Orchestrated::Orchestration::Handler.new( f, :do_first_thing, [1])
|
43
|
+
orchestration.save!
|
44
|
+
end
|
45
|
+
end
|
46
|
+
def create_interest(orchestration, prerequisite=Orchestrated::Complete.new)
|
47
|
+
prerequisite.save!
|
48
|
+
Orchestrated::OrchestrationInterest.new.tap do |interest|
|
49
|
+
interest.prerequisite = prerequisite
|
50
|
+
interest.orchestration = orchestration
|
51
|
+
interest.save!
|
52
|
+
end
|
53
|
+
end
|
54
|
+
def create_completion(orchestration)
|
55
|
+
Orchestrated::OrchestrationCompletion.new.tap do |completion|
|
56
|
+
completion.orchestration = @orchestration
|
57
|
+
completion.save!
|
58
|
+
end
|
59
|
+
end
|
60
|
+
before(:each) do
|
61
|
+
@orchestration = create_orchestration
|
62
|
+
end
|
63
|
+
context 'with prerequisite created before dependent' do
|
64
|
+
before(:each) do
|
65
|
+
@interest = create_interest(@orchestration)
|
66
|
+
@completion = create_completion(@orchestration)
|
67
|
+
end
|
68
|
+
it_should_behave_like 'orchestration accessing prerequisite and dependent'
|
69
|
+
end
|
70
|
+
context 'with dependent created before prerequisite' do
|
71
|
+
before(:each) do
|
72
|
+
@completion = create_completion(@orchestration)
|
73
|
+
@interest = create_interest(@orchestration)
|
74
|
+
end
|
75
|
+
it_should_behave_like 'orchestration accessing prerequisite and dependent'
|
76
|
+
end
|
77
|
+
end
|
25
78
|
context 'orchestrating with no prerequisites' do
|
26
79
|
before(:each){@result = f.orchestrated.do_first_thing(2)}
|
27
80
|
after(:each){DJ.clear_all_jobs}
|