bumbleworks 0.0.48 → 0.0.50

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/.gitignore +4 -1
  2. data/lib/bumbleworks.rb +9 -8
  3. data/lib/bumbleworks/configuration.rb +8 -5
  4. data/lib/bumbleworks/entity.rb +88 -0
  5. data/lib/bumbleworks/participant.rb +6 -7
  6. data/lib/bumbleworks/participant/base.rb +11 -0
  7. data/lib/bumbleworks/participant/entity_interactor.rb +29 -0
  8. data/lib/bumbleworks/participant/error_handler.rb +14 -0
  9. data/lib/bumbleworks/participant/local_participant.rb +11 -0
  10. data/lib/bumbleworks/participant/storage_participant.rb +11 -0
  11. data/lib/bumbleworks/process.rb +64 -0
  12. data/lib/bumbleworks/ruote.rb +12 -4
  13. data/lib/bumbleworks/support.rb +3 -5
  14. data/lib/bumbleworks/task.rb +6 -4
  15. data/lib/bumbleworks/{tasks → task}/base.rb +1 -1
  16. data/lib/bumbleworks/task/finder.rb +39 -3
  17. data/lib/bumbleworks/version.rb +1 -1
  18. data/spec/fixtures/entities/furby.rb +30 -0
  19. data/spec/integration/entity_spec.rb +49 -0
  20. data/spec/integration/history_storage_spec.rb +4 -4
  21. data/spec/integration/sample_application_spec.rb +8 -2
  22. data/spec/lib/bumbleworks/entity_spec.rb +208 -0
  23. data/spec/lib/bumbleworks/{participant_spec.rb → participant/base_spec.rb} +3 -3
  24. data/spec/lib/bumbleworks/participant/entity_interactor_spec.rb +91 -0
  25. data/spec/lib/bumbleworks/{error_handler_participant_spec.rb → participant/error_handler_spec.rb} +1 -1
  26. data/spec/lib/bumbleworks/{local_participant_spec.rb → participant/local_participant_spec.rb} +1 -1
  27. data/spec/lib/bumbleworks/process_spec.rb +147 -0
  28. data/spec/lib/bumbleworks/ruote/exp/broadcast_event_expression_spec.rb +1 -1
  29. data/spec/lib/bumbleworks/ruote/exp/wait_for_event_expression_spec.rb +3 -3
  30. data/spec/lib/bumbleworks/ruote_spec.rb +28 -21
  31. data/spec/lib/bumbleworks/task/finder_spec.rb +9 -0
  32. data/spec/lib/bumbleworks/task_spec.rb +98 -3
  33. data/spec/lib/bumbleworks_spec.rb +14 -7
  34. data/spec/spec_helper.rb +0 -1
  35. data/spec/support/process_testing_helpers.rb +9 -0
  36. metadata +34 -13
  37. data/lib/bumbleworks/error_handler_participant.rb +0 -12
  38. data/lib/bumbleworks/local_participant.rb +0 -9
  39. data/lib/bumbleworks/storage_participant.rb +0 -9
@@ -1,3 +1,3 @@
1
1
  module Bumbleworks
2
- VERSION = "0.0.48"
2
+ VERSION = "0.0.50"
3
3
  end
@@ -0,0 +1,30 @@
1
+ class Furby
2
+ include Bumbleworks::Entity
3
+
4
+ process :make_honey
5
+
6
+ attr_reader :identifier
7
+ attr_accessor :make_honey_process_identifier
8
+
9
+ def initialize(identifier)
10
+ @identifier = identifier
11
+ end
12
+
13
+ def ==(other)
14
+ other.identifier == identifier
15
+ end
16
+
17
+ def update(attributes)
18
+ attributes.each { |k, v| self.send(:"#{k}=", v) }
19
+ end
20
+
21
+ def cook_it_up(*foods)
22
+ foods.join(' and ')
23
+ end
24
+
25
+ class << self
26
+ def first_by_identifier(identifier)
27
+ new(identifier)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,49 @@
1
+ require File.expand_path(File.join(fixtures_path, 'entities', 'furby'))
2
+
3
+ describe 'Entity Module' do
4
+ let(:app_root) {
5
+ File.expand_path(File.join(fixtures_path, 'apps', 'with_default_directories'))
6
+ }
7
+
8
+ before :each do
9
+ Bumbleworks.reset!
10
+ load File.join(app_root, 'full_initializer.rb')
11
+ end
12
+
13
+ describe 'launching a process' do
14
+ it 'assigns entity to process and subsequent tasks' do
15
+ furby = Furby.new('12345')
16
+ furby.launch_process(:make_honey)
17
+ Bumbleworks.dashboard.wait_for(:dave)
18
+ task = Bumbleworks::Task.for_role('dave').first
19
+ task.entity.should == furby
20
+ end
21
+
22
+ it 'links processes with identifiers' do
23
+ furby = Furby.new('12345')
24
+ process = furby.launch_process(:make_honey)
25
+ furby.processes_by_name.should == {
26
+ :make_honey => process
27
+ }
28
+ end
29
+
30
+ it 'persists process identifier' do
31
+ furby = Furby.new('12345')
32
+ process = furby.launch_process(:make_honey)
33
+ furby.make_honey_process_identifier.should == process.wfid
34
+ end
35
+ end
36
+
37
+ describe 'using the entity interactor participant' do
38
+ it 'calls a method on the entity' do
39
+ Bumbleworks.define_process 'here_we_go' do
40
+ tell_entity :to => 'cook_it_up', :with => [2, 'orange'], :and_save_as => 'yum'
41
+ critic :task => 'checkit_that_foods'
42
+ end
43
+ furby = Furby.new('12345')
44
+ process = Bumbleworks.launch!('here_we_go', :entity => furby)
45
+ Bumbleworks.dashboard.wait_for(:critic)
46
+ process.tasks.first['yum'].should == "2 and orange"
47
+ end
48
+ end
49
+ end
@@ -25,10 +25,10 @@ describe 'History storage' do
25
25
 
26
26
  it 'keeps history of messages' do
27
27
  Bumbleworks::Ruote.storage.get_many('history').should be_empty
28
- wfid = Bumbleworks.launch!('make_honey')
28
+ process = Bumbleworks.launch!('make_honey')
29
29
  Bumbleworks.dashboard.wait_for(:dave)
30
30
  Bumbleworks::Ruote.storage.get_many('history').should_not be_empty
31
- Bumbleworks.dashboard.history.wfids.should include(wfid)
31
+ Bumbleworks.dashboard.history.wfids.should include(process.wfid)
32
32
  end
33
33
  end
34
34
 
@@ -43,10 +43,10 @@ describe 'History storage' do
43
43
 
44
44
  it 'keeps history of messages' do
45
45
  Bumbleworks.dashboard.history.all.should be_empty
46
- wfid = Bumbleworks.launch!('make_honey')
46
+ process = Bumbleworks.launch!('make_honey')
47
47
  Bumbleworks.dashboard.wait_for(:dave)
48
48
  Bumbleworks.dashboard.history.all.should_not be_empty
49
- Bumbleworks.dashboard.history.wfids.should include(wfid)
49
+ Bumbleworks.dashboard.history.wfids.should include(process.wfid)
50
50
  end
51
51
  end
52
52
  end
@@ -10,8 +10,14 @@ describe 'Bumbleworks Sample Application' do
10
10
 
11
11
  describe 'initializer' do
12
12
  it 'registers participants' do
13
- Bumbleworks.dashboard.participant_list.should have(4).items
14
- Bumbleworks.dashboard.participant_list.map(&:classname).should =~ ['HoneyParticipant', 'MolassesParticipant', 'Bumbleworks::ErrorHandlerParticipant', 'Bumbleworks::StorageParticipant']
13
+ Bumbleworks.dashboard.participant_list.should have(5).items
14
+ Bumbleworks.dashboard.participant_list.map(&:classname).should == [
15
+ 'Bumbleworks::Participant::ErrorHandler',
16
+ 'Bumbleworks::Participant::EntityInteractor',
17
+ 'HoneyParticipant',
18
+ 'MolassesParticipant',
19
+ 'Bumbleworks::Participant::StorageParticipant'
20
+ ]
15
21
  end
16
22
 
17
23
  it 'loads process definitions' do
@@ -0,0 +1,208 @@
1
+ describe Bumbleworks::Entity do
2
+ let(:entity_class) { Class.new { include Bumbleworks::Entity } }
3
+
4
+ describe '#processes_by_name' do
5
+ it 'returns hash of process names and process instances' do
6
+ [:zoom, :foof, :nook].each do |pname|
7
+ entity_class.send(:attr_accessor, :"#{pname}_pid")
8
+ entity_class.process pname, :column => :"#{pname}_pid"
9
+ end
10
+
11
+ entity = entity_class.new
12
+ entity.foof_pid = '1234'
13
+ entity.nook_pid = 'pickles'
14
+ entity.processes_by_name.should == {
15
+ :zoom => nil,
16
+ :foof => Bumbleworks::Process.new('1234'),
17
+ :nook => Bumbleworks::Process.new('pickles')
18
+ }
19
+ end
20
+
21
+ it 'returns empty hash if no processes' do
22
+ entity_class.new.processes_by_name.should == {}
23
+ end
24
+ end
25
+
26
+ describe '#processes' do
27
+ it 'returns array of process instances for all running processes' do
28
+ [:zoom, :foof, :nook].each do |pname|
29
+ entity_class.send(:attr_accessor, :"#{pname}_pid")
30
+ entity_class.process pname, :column => :"#{pname}_pid"
31
+ end
32
+ entity = entity_class.new
33
+ entity.foof_pid = '1234'
34
+ entity.nook_pid = 'pickles'
35
+ entity.processes.should =~ [
36
+ Bumbleworks::Process.new('1234'),
37
+ Bumbleworks::Process.new('pickles')
38
+ ]
39
+ end
40
+
41
+ it 'returns empty array if no processes' do
42
+ entity_class.new.processes.should == []
43
+ end
44
+ end
45
+
46
+ describe '#cancel_all_processes!' do
47
+ it 'cancels all processes with registered identifiers' do
48
+ entity = entity_class.new
49
+ bp1 = Bumbleworks::Process.new('1234')
50
+ bp2 = Bumbleworks::Process.new('pickles')
51
+ entity.stub(:processes).and_return([bp1, bp2])
52
+ bp1.should_receive(:cancel!)
53
+ bp2.should_receive(:cancel!)
54
+ entity.cancel_all_processes!
55
+ end
56
+ end
57
+
58
+ describe '#process_fields' do
59
+ it 'returns hash with entity' do
60
+ entity = entity_class.new
61
+ entity.process_fields.should == { :entity => entity }
62
+ end
63
+
64
+ it 'accepts but ignores process name argument' do
65
+ entity = entity_class.new
66
+ entity.process_fields('ignore_me').should == { :entity => entity }
67
+ end
68
+ end
69
+
70
+ describe '#process_variables' do
71
+ it 'returns empty hash' do
72
+ entity_class.new.process_variables.should == {}
73
+ end
74
+
75
+ it 'accepts but ignores process name argument' do
76
+ entity_class.new.process_variables('ignore me').should == {}
77
+ end
78
+ end
79
+
80
+ describe '#persist_process_identifier' do
81
+ it 'calls #update if method exists' do
82
+ entity = entity_class.new
83
+ entity.should_receive(:update).with(:a_column => :a_value)
84
+ entity.persist_process_identifier(:a_column, :a_value)
85
+ end
86
+
87
+ it 'raises exception if #update method does not exist' do
88
+ entity = entity_class.new
89
+ entity.stub(:respond_to?).with(:update).and_return(false)
90
+ entity.should_receive(:update).never
91
+ expect {
92
+ entity.persist_process_identifier(:a_column, :a_value)
93
+ }.to raise_error("Entity must define #persist_process_identifier method if missing #update method.")
94
+ end
95
+ end
96
+
97
+ describe '#launch_process' do
98
+ it 'launches process and return process if identifier not set' do
99
+ bp = Bumbleworks::Process.new('12345')
100
+ entity_class.process :noodles, :column => :noodles_pid
101
+ entity = entity_class.new
102
+ entity.stub(:noodles_pid)
103
+ entity.stub(:process_fields).with(:noodles).and_return('the_fields')
104
+ entity.stub(:process_variables).with(:noodles).and_return('the_variables')
105
+ Bumbleworks.stub(:launch!).with('noodles', 'the_fields', 'the_variables').and_return(bp)
106
+ entity.should_receive(:persist_process_identifier).with(:noodles_pid, '12345')
107
+ entity.launch_process('noodles').should == bp
108
+ end
109
+
110
+ it 'does nothing but returns existing process if identifier column already set' do
111
+ bp = Bumbleworks::Process.new('already set')
112
+ entity_class.process :noodles, :column => :noodles_pid
113
+ entity = entity_class.new
114
+ entity.stub(:noodles_pid => 'already set')
115
+ Bumbleworks.should_receive(:launch!).never
116
+ entity.launch_process('noodles').should == bp
117
+ end
118
+
119
+ it 'launches new process anyway if identifier column already set but force is true' do
120
+ bp = Bumbleworks::Process.new('12345')
121
+ entity_class.process :noodles, :column => :noodles_pid
122
+ entity = entity_class.new
123
+ entity.stub(:noodles_pid => 'already set')
124
+ Bumbleworks.stub(:launch! => bp)
125
+ entity.should_receive(:persist_process_identifier).with(:noodles_pid, '12345')
126
+ entity.launch_process('noodles', :force => true).should == bp
127
+ end
128
+ end
129
+
130
+ describe '#subscribed_events' do
131
+ it 'returns aggregate of subscribed events from all processes' do
132
+ bp1 = double('bp1', :subscribed_events => ['chewing', 'popping', 'yakking'])
133
+ bp2 = double('bp2', :subscribed_events => ['moon', 'yakking'])
134
+ entity = entity_class.new
135
+ entity.stub(:processes).and_return({
136
+ :zip => bp1,
137
+ :yip => bp2
138
+ })
139
+ entity.subscribed_events.should =~ ['chewing', 'moon', 'popping', 'yakking']
140
+ end
141
+ end
142
+
143
+ describe '#is_waiting_for?' do
144
+ it 'returns true if any processes are waiting for event' do
145
+ bp1 = double('bp1', :subscribed_events => ['chewing', 'popping', 'yakking'])
146
+ bp2 = double('bp2', :subscribed_events => ['moon', 'yakking'])
147
+ entity = entity_class.new
148
+ entity.stub(:processes).and_return({
149
+ :zip => bp1,
150
+ :yip => bp2
151
+ })
152
+ entity.is_waiting_for?('chewing').should be_true
153
+ entity.is_waiting_for?('yakking').should be_true
154
+ entity.is_waiting_for?(:moon).should be_true
155
+ entity.is_waiting_for?('fruiting').should be_false
156
+ end
157
+ end
158
+
159
+ describe '#tasks' do
160
+ it 'returns task query for all entity tasks by default' do
161
+ entity = entity_class.new
162
+ Bumbleworks::Task.stub(:for_entity).with(entity).and_return(:full_task_query)
163
+ entity.tasks.should == :full_task_query
164
+ end
165
+
166
+ it 'filters task query by nickname if provided' do
167
+ entity = entity_class.new
168
+ task_finder = double('task_finder')
169
+ task_finder.stub(:by_nickname).with(:smooface).and_return(:partial_task_query)
170
+ Bumbleworks::Task.stub(:for_entity).with(entity).and_return(task_finder)
171
+ entity.tasks(:smooface).should == :partial_task_query
172
+ end
173
+ end
174
+
175
+ describe '.process' do
176
+ it 'registers a new process' do
177
+ entity_class.stub(:process_identifier_column).with(:whatever).and_return('loob')
178
+ entity_class.process :whatever
179
+ entity_class.processes.should == {
180
+ :whatever => {
181
+ :column => 'loob'
182
+ }
183
+ }
184
+ end
185
+ end
186
+
187
+ describe '.process_identifier_column' do
188
+ it 'adds _process_identifier to end of given process name' do
189
+ entity_class.process_identifier_column('zoof').should == :zoof_process_identifier
190
+ end
191
+
192
+ it 'ensures no duplication of _process' do
193
+ entity_class.process_identifier_column('zoof_process').should == :zoof_process_identifier
194
+ end
195
+
196
+ it 'removes entity_type from beginning of identifier' do
197
+ entity_class.stub(:entity_type).and_return('zoof')
198
+ entity_class.process_identifier_column('zoof_process').should == :process_identifier
199
+ end
200
+ end
201
+
202
+ describe '.entity_type' do
203
+ it 'returns underscored version of class name' do
204
+ entity_class.stub(:name).and_return('MyClass')
205
+ entity_class.entity_type.should == 'my_class'
206
+ end
207
+ end
208
+ end
@@ -1,6 +1,6 @@
1
- describe Bumbleworks::Participant do
2
- it 'includes Bumbleworks::LocalParticipant' do
3
- described_class.included_modules.should include(Bumbleworks::LocalParticipant)
1
+ describe Bumbleworks::Participant::Base do
2
+ it 'includes Bumbleworks::Participant::LocalParticipant' do
3
+ described_class.included_modules.should include(Bumbleworks::Participant::LocalParticipant)
4
4
  end
5
5
 
6
6
  it 'defines #on_cancel' do
@@ -0,0 +1,91 @@
1
+ describe Bumbleworks::Participant::EntityInteractor do
2
+ let(:entity) { double('entity', :cheek_depth => 'crazy deep') }
3
+ let(:workitem) { Ruote::Workitem.new('fields' => { 'params' => { 'method' => 'cheek_depth' }}) }
4
+ subject { part = described_class.new; part.stub(:entity => entity, :reply => nil); part }
5
+
6
+ describe '#on_workitem' do
7
+ it 'calls method then replies' do
8
+ subject.should_receive(:call_method).ordered
9
+ subject.should_receive(:reply).ordered
10
+ subject._on_workitem(workitem)
11
+ end
12
+
13
+ it 'saves the requested attribute to a workitem field' do
14
+ workitem.fields['params']['and_save_as'] = 'entity_cheek_depth'
15
+ subject._on_workitem(workitem)
16
+ workitem.fields['entity_cheek_depth'].should == 'crazy deep'
17
+ end
18
+
19
+ it 'overwrites an existing workitem field value' do
20
+ workitem.fields['entity_cheek_depth'] = '14'
21
+ workitem.fields['params']['and_save_as'] = 'entity_cheek_depth'
22
+ subject._on_workitem(workitem)
23
+ workitem.fields['entity_cheek_depth'].should == 'crazy deep'
24
+ end
25
+
26
+ it 'calls the method even if no save_as to store the result' do
27
+ subject.entity.should_receive(:cheek_depth)
28
+ subject._on_workitem(workitem)
29
+ workitem.fields['entity_cheek_depth'].should be_nil
30
+ end
31
+
32
+ it 'passes arguments to method' do
33
+ workitem.fields['params']['arguments'] = [1, 3, ['apple', 'fish']]
34
+ workitem.fields['params']['and_save_as'] = 'entity_cheek_depth'
35
+ subject.entity.should_receive(:cheek_depth).with(1, 3, ['apple', 'fish']).and_return('what')
36
+ subject._on_workitem(workitem)
37
+ workitem.fields['entity_cheek_depth'].should == 'what'
38
+ end
39
+
40
+ it 'can accept "with" for arguments' do
41
+ workitem.fields['params']['with'] = { :regular => 'joes' }
42
+ subject.entity.should_receive(:cheek_depth).with({ :regular => 'joes' })
43
+ subject._on_workitem(workitem)
44
+ end
45
+
46
+ it 'can use "for" param for method' do
47
+ workitem.fields['params'] = { 'for' => 'grass_seed', 'and_save_as' => 'how_tasty' }
48
+ entity.should_receive(:grass_seed).and_return('tasty-o')
49
+ subject._on_workitem(workitem)
50
+ workitem.fields['how_tasty'].should == 'tasty-o'
51
+ end
52
+
53
+ it 'can use "to" param for method' do
54
+ workitem.fields['params'] = { 'to' => 'chew_things' }
55
+ entity.should_receive(:chew_things).and_return(nil)
56
+ subject._on_workitem(workitem)
57
+ workitem.fields['who_cares'].should be_nil
58
+ end
59
+
60
+ it 'defaults to "method" when multiple options exist' do
61
+ workitem.fields['params'] = { 'method' => 'really_do_this', 'to' => 'not_this', 'for' => 'definitely_not_this' }
62
+ entity.should_receive(:really_do_this)
63
+ subject._on_workitem(workitem)
64
+ end
65
+
66
+ it 'defaults to "to" when both "to" and "for"' do
67
+ workitem.fields['params'] = { 'to' => 'please_call_me', 'for' => 'call_me_maybe' }
68
+ entity.should_receive(:please_call_me)
69
+ subject._on_workitem(workitem)
70
+ end
71
+ end
72
+
73
+ describe '#call_method' do
74
+ it 'calls the requested method on the entity' do
75
+ subject.call_method('cheek_depth').should == 'crazy deep'
76
+ end
77
+
78
+ it 'saves the result if given a target' do
79
+ subject.workitem = workitem
80
+ subject.call_method('cheek_depth', :save_as => 'entity_cheek_depth')
81
+ workitem.fields['entity_cheek_depth'].should == 'crazy deep'
82
+ end
83
+
84
+ it 'raises an exception if no method on entity' do
85
+ subject.stub(:entity).and_return('just an unassuming little string!')
86
+ expect {
87
+ subject.call_method('eat_television')
88
+ }.to raise_error(NoMethodError)
89
+ end
90
+ end
91
+ end
@@ -1,4 +1,4 @@
1
- describe Bumbleworks::ErrorHandlerParticipant do
1
+ describe Bumbleworks::Participant::ErrorHandler do
2
2
  describe '#on_workitem' do
3
3
  let(:workitem) { double('workitem') }
4
4
  let(:instances) { [double(:on_error => nil), double(:on_error => nil)] }