bumbleworks 0.0.48 → 0.0.50

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.
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)] }