bumbleworks 0.0.51 → 0.0.52

Sign up to get free protection for your applications and to get access to all the features.
@@ -11,6 +11,7 @@ require "bumbleworks/hash_storage"
11
11
  require "bumbleworks/error_handler"
12
12
  require "bumbleworks/entity"
13
13
  require "bumbleworks/participant"
14
+ require "bumbleworks/workitem"
14
15
 
15
16
  # default implementations
16
17
  require "bumbleworks/simple_logger"
@@ -21,6 +22,7 @@ module Bumbleworks
21
22
  class UndefinedSetting < StandardError; end
22
23
  class InvalidSetting < StandardError; end
23
24
  class InvalidEntity < StandardError; end
25
+ class EntityNotFound < StandardError; end
24
26
 
25
27
  class << self
26
28
  extend Forwardable
@@ -5,19 +5,19 @@ module Bumbleworks
5
5
  end
6
6
 
7
7
  def launch_process(process_name, options = {})
8
- identifier_column = self.class.processes[process_name.to_sym][:column]
9
- if (options[:force] == true || (process_identifier = self.send(identifier_column)).nil?)
10
- workitem_fields = process_fields(process_name.to_sym)
11
- variables = process_variables(process_name.to_sym)
8
+ identifier_attribute = attribute_for_process_name(process_name.to_sym)
9
+ if (options[:force] == true || (process_identifier = self.send(identifier_attribute)).nil?)
10
+ workitem_fields = process_fields(process_name.to_sym).merge(options[:fields] || {})
11
+ variables = process_variables(process_name.to_sym).merge(options[:variables] || {})
12
12
  process_identifier = Bumbleworks.launch!(process_name.to_s, workitem_fields, variables).wfid
13
- persist_process_identifier(identifier_column.to_sym, process_identifier)
13
+ persist_process_identifier(identifier_attribute.to_sym, process_identifier)
14
14
  end
15
15
  Bumbleworks::Process.new(process_identifier)
16
16
  end
17
17
 
18
- def persist_process_identifier(identifier_column, process_identifier)
18
+ def persist_process_identifier(identifier_attribute, process_identifier)
19
19
  if self.respond_to?(:update)
20
- update(identifier_column => process_identifier)
20
+ update(identifier_attribute => process_identifier)
21
21
  else
22
22
  raise "Entity must define #persist_process_identifier method if missing #update method."
23
23
  end
@@ -27,7 +27,7 @@ module Bumbleworks
27
27
  return {} unless self.class.processes
28
28
  process_names = self.class.processes.keys
29
29
  process_names.inject({}) do |memo, name|
30
- pid = self.send(self.class.processes[name][:column])
30
+ pid = self.send(attribute_for_process_name(name))
31
31
  memo[name] = pid ? Bumbleworks::Process.new(pid) : nil
32
32
  memo
33
33
  end
@@ -38,11 +38,18 @@ module Bumbleworks
38
38
  end
39
39
 
40
40
  def cancel_all_processes!
41
- processes.each do |process|
41
+ processes_by_name.each do |name, process|
42
+ next unless process
43
+ identifier_attribute = attribute_for_process_name(name)
42
44
  process.cancel!
45
+ persist_process_identifier(identifier_attribute, nil)
43
46
  end
44
47
  end
45
48
 
49
+ def attribute_for_process_name(name)
50
+ self.class.processes[name][:attribute]
51
+ end
52
+
46
53
  def tasks(nickname = nil)
47
54
  finder = Bumbleworks::Task.for_entity(self)
48
55
  finder = finder.by_nickname(nickname) if nickname
@@ -69,7 +76,7 @@ module Bumbleworks
69
76
  attr_reader :processes
70
77
 
71
78
  def process(process_name, options = {})
72
- options[:column] ||= process_identifier_column(process_name)
79
+ options[:attribute] ||= default_process_identifier_attribute(process_name)
73
80
  (@processes ||= {})[process_name.to_sym] = options
74
81
  end
75
82
 
@@ -77,11 +84,11 @@ module Bumbleworks
77
84
  Bumbleworks::Support.tokenize(name)
78
85
  end
79
86
 
80
- def process_identifier_column(process_name)
81
- identifier_column = "#{process_name}_process_identifier"
82
- identifier_column.gsub!(/^#{entity_type}_/, '')
83
- identifier_column.gsub!(/process_process/, 'process')
84
- identifier_column.to_sym
87
+ def default_process_identifier_attribute(process_name)
88
+ identifier_attribute = "#{process_name}_process_identifier"
89
+ identifier_attribute.gsub!(/^#{entity_type}_/, '')
90
+ identifier_attribute.gsub!(/process_process/, 'process')
91
+ identifier_attribute.to_sym
85
92
  end
86
93
  end
87
94
  end
@@ -1,5 +1,7 @@
1
1
  module Bumbleworks
2
2
  class Process
3
+ class EntityConflict < StandardError; end
4
+
3
5
  attr_reader :id
4
6
 
5
7
  def initialize(wfid)
@@ -12,6 +14,16 @@ module Bumbleworks
12
14
  wfid == other.wfid
13
15
  end
14
16
 
17
+ def entity
18
+ return nil unless process_status
19
+ workitems = leaves.map(&:applied_workitem).map { |wi| Bumbleworks::Workitem.new(wi) }
20
+ if workitems.map(&:entity_fields).uniq.length == 1
21
+ workitems.first.entity
22
+ else
23
+ raise EntityConflict
24
+ end
25
+ end
26
+
15
27
  def tasks
16
28
  Bumbleworks::Task.for_process(wfid)
17
29
  end
@@ -11,6 +11,32 @@ module Bumbleworks
11
11
  @wfids = nil
12
12
  end
13
13
 
14
+ def where(filters)
15
+ key_method_map = {
16
+ :available => :available,
17
+ :nickname => :by_nickname,
18
+ :roles => :for_roles,
19
+ :role => :for_role,
20
+ :unclaimed => :unclaimed,
21
+ :claimed => :claimed,
22
+ :fields => :with_fields,
23
+ :claimant => :for_claimant,
24
+ :entity => :for_entity,
25
+ :processes => :for_processes,
26
+ :process => :for_process,
27
+ :completable => :completable
28
+ }
29
+ fields = filters.select { |k,v| !key_method_map.keys.include? k }
30
+ methods = filters.select { |k,v| key_method_map.keys.include? k }
31
+ query = methods.inject(self) { |query, (method, args)|
32
+ query.send(key_method_map[method], args)
33
+ }
34
+ unless fields.empty?
35
+ query.with_fields(fields)
36
+ end
37
+ query
38
+ end
39
+
14
40
  def available
15
41
  unclaimed.completable
16
42
  end
@@ -39,6 +65,11 @@ module Bumbleworks
39
65
  unclaimed(false)
40
66
  end
41
67
 
68
+ def with_fields(field_hash)
69
+ @queries << proc { |wi| field_hash.all? { |k, v| wi['fields'][k.to_s] == v } }
70
+ self
71
+ end
72
+
42
73
  def for_claimant(token)
43
74
  @queries << proc { |wi| wi['fields']['params']['claimant'] == token }
44
75
  self
@@ -75,8 +106,8 @@ module Bumbleworks
75
106
  from_workitems(workitems)
76
107
  end
77
108
 
78
- def completable
79
- @task_filters << proc { |task| task.completable? }
109
+ def completable(true_or_false = true)
110
+ @task_filters << proc { |task| task.completable? == true_or_false }
80
111
  self
81
112
  end
82
113
 
@@ -1,3 +1,3 @@
1
1
  module Bumbleworks
2
- VERSION = "0.0.51"
2
+ VERSION = "0.0.52"
3
3
  end
@@ -0,0 +1,52 @@
1
+ module Bumbleworks
2
+ class Workitem
3
+ def initialize(raw_workitem)
4
+ @raw_workitem = raw_workitem
5
+ end
6
+
7
+ def entity(options = {})
8
+ @entity = nil if options[:reload] == true
9
+ @entity ||= if has_entity_fields?
10
+ klass = Bumbleworks::Support.constantize(entity_type)
11
+ entity = klass.first_by_identifier(entity_id)
12
+ end
13
+ raise EntityNotFound, {:entity_id => entity_id, :entity_type => entity_type} unless @entity
14
+ @entity
15
+ end
16
+
17
+ def has_entity?
18
+ !entity.nil?
19
+ rescue EntityNotFound
20
+ false
21
+ end
22
+
23
+ def has_entity_fields?
24
+ entity_id && entity_type
25
+ end
26
+
27
+ def entity_fields(options = {})
28
+ return {} unless has_entity_fields?
29
+ type = if options[:humanize] == true
30
+ Bumbleworks::Support.humanize(entity_type)
31
+ elsif options[:titleize] == true
32
+ Bumbleworks::Support.titleize(entity_type)
33
+ else
34
+ entity_type
35
+ end
36
+ {
37
+ :type => type,
38
+ :identifier => entity_id
39
+ }
40
+ end
41
+
42
+ private
43
+
44
+ def entity_id
45
+ @raw_workitem.fields[:entity_id] || @raw_workitem.fields['entity_id']
46
+ end
47
+
48
+ def entity_type
49
+ @raw_workitem.fields[:entity_type] || @raw_workitem.fields['entity_type']
50
+ end
51
+ end
52
+ end
@@ -1,50 +1,11 @@
1
1
  module Bumbleworks
2
2
  module WorkitemEntityStorage
3
- class EntityNotFound < StandardError; end
3
+ extend Forwardable
4
4
 
5
- def entity(options = {})
6
- @entity = nil if options[:reload] == true
7
- @entity ||= if has_entity_fields?
8
- klass = Bumbleworks::Support.constantize(entity_type)
9
- entity = klass.first_by_identifier(entity_id)
10
- end
11
- raise EntityNotFound, {:entity_id => entity_id, :entity_type => entity_type} unless @entity
12
- @entity
13
- end
14
-
15
- def has_entity?
16
- !entity.nil?
17
- rescue EntityNotFound
18
- false
19
- end
20
-
21
- def has_entity_fields?
22
- entity_id && entity_type
23
- end
24
-
25
- def entity_fields(options = {})
26
- return {} unless has_entity_fields?
27
- type = if options[:humanize] == true
28
- Bumbleworks::Support.humanize(entity_type)
29
- elsif options[:titleize] == true
30
- Bumbleworks::Support.titleize(entity_type)
31
- else
32
- entity_type
33
- end
34
- {
35
- :type => type,
36
- :identifier => entity_id
37
- }
38
- end
39
-
40
- private
41
-
42
- def entity_id
43
- workitem.fields[:entity_id] || workitem.fields['entity_id']
44
- end
5
+ delegate [:entity, :has_entity?, :has_entity_fields?, :entity_fields] => :entity_storage_workitem
45
6
 
46
- def entity_type
47
- workitem.fields[:entity_type] || workitem.fields['entity_type']
7
+ def entity_storage_workitem
8
+ @entity_storage_workitem ||= Bumbleworks::Workitem.new(workitem)
48
9
  end
49
10
  end
50
11
  end
@@ -1,10 +1,12 @@
1
- class Furby
1
+ class RainbowLoom
2
2
  include Bumbleworks::Entity
3
3
 
4
4
  process :make_honey
5
+ process :make_molasses, :attribute => :molasses_pid
5
6
 
6
- attr_reader :identifier
7
+ attr_accessor :identifier
7
8
  attr_accessor :make_honey_process_identifier
9
+ attr_accessor :molasses_pid
8
10
 
9
11
  def initialize(identifier)
10
12
  @identifier = identifier
@@ -24,6 +26,7 @@ class Furby
24
26
 
25
27
  class << self
26
28
  def first_by_identifier(identifier)
29
+ return nil unless identifier
27
30
  new(identifier)
28
31
  end
29
32
  end
@@ -1,4 +1,4 @@
1
- require File.expand_path(File.join(fixtures_path, 'entities', 'furby'))
1
+ require File.expand_path(File.join(fixtures_path, 'entities', 'rainbow_loom'))
2
2
 
3
3
  describe 'Entity Module' do
4
4
  let(:app_root) {
@@ -10,27 +10,29 @@ describe 'Entity Module' do
10
10
  load File.join(app_root, 'full_initializer.rb')
11
11
  end
12
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)
13
+ describe 'process control' do
14
+ it 'launching assigns entity to process and subsequent tasks' do
15
+ rainbow_loom = RainbowLoom.new('12345')
16
+ process = rainbow_loom.launch_process(:make_honey)
17
17
  Bumbleworks.dashboard.wait_for(:dave)
18
18
  task = Bumbleworks::Task.for_role('dave').first
19
- task.entity.should == furby
19
+ task.entity.should == rainbow_loom
20
+ process.entity.should == rainbow_loom
20
21
  end
21
22
 
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
23
+ it 'launching links processes with identifiers' do
24
+ rainbow_loom = RainbowLoom.new('12345')
25
+ process = rainbow_loom.launch_process(:make_honey)
26
+ rainbow_loom.processes_by_name.should == {
27
+ :make_honey => process,
28
+ :make_molasses => nil
27
29
  }
28
30
  end
29
31
 
30
32
  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
33
+ rainbow_loom = RainbowLoom.new('12345')
34
+ process = rainbow_loom.launch_process(:make_honey)
35
+ rainbow_loom.make_honey_process_identifier.should == process.wfid
34
36
  end
35
37
  end
36
38
 
@@ -40,8 +42,8 @@ describe 'Entity Module' do
40
42
  tell_entity :to => 'cook_it_up', :with => [2, 'orange'], :and_save_as => 'yum'
41
43
  critic :task => 'checkit_that_foods'
42
44
  end
43
- furby = Furby.new('12345')
44
- process = Bumbleworks.launch!('here_we_go', :entity => furby)
45
+ rainbow_loom = RainbowLoom.new('12345')
46
+ process = Bumbleworks.launch!('here_we_go', :entity => rainbow_loom)
45
47
  Bumbleworks.dashboard.wait_for(:critic)
46
48
  process.tasks.first['yum'].should == "2 and orange"
47
49
  end
@@ -5,7 +5,7 @@ describe Bumbleworks::Entity do
5
5
  it 'returns hash of process names and process instances' do
6
6
  [:zoom, :foof, :nook].each do |pname|
7
7
  entity_class.send(:attr_accessor, :"#{pname}_pid")
8
- entity_class.process pname, :column => :"#{pname}_pid"
8
+ entity_class.process pname, :attribute => :"#{pname}_pid"
9
9
  end
10
10
 
11
11
  entity = entity_class.new
@@ -27,7 +27,7 @@ describe Bumbleworks::Entity do
27
27
  it 'returns array of process instances for all running processes' do
28
28
  [:zoom, :foof, :nook].each do |pname|
29
29
  entity_class.send(:attr_accessor, :"#{pname}_pid")
30
- entity_class.process pname, :column => :"#{pname}_pid"
30
+ entity_class.process pname, :attribute => :"#{pname}_pid"
31
31
  end
32
32
  entity = entity_class.new
33
33
  entity.foof_pid = '1234'
@@ -45,12 +45,21 @@ describe Bumbleworks::Entity do
45
45
 
46
46
  describe '#cancel_all_processes!' do
47
47
  it 'cancels all processes with registered identifiers' do
48
+ [:zoom, :foof, :nook].each do |pname|
49
+ entity_class.send(:attr_accessor, :"#{pname}_pid")
50
+ entity_class.process pname, :attribute => :"#{pname}_pid"
51
+ end
48
52
  entity = entity_class.new
49
- bp1 = Bumbleworks::Process.new('1234')
50
- bp2 = Bumbleworks::Process.new('pickles')
51
- entity.stub(:processes).and_return([bp1, bp2])
53
+ entity.foof_pid = '1234'
54
+ entity.nook_pid = 'pickles'
55
+ entity.stub(:processes_by_name => {
56
+ :foof => bp1 = Bumbleworks::Process.new('1234'),
57
+ :nook => bp2 = Bumbleworks::Process.new('pickles')
58
+ })
52
59
  bp1.should_receive(:cancel!)
53
60
  bp2.should_receive(:cancel!)
61
+ entity.should_receive(:update).with(:foof_pid => nil)
62
+ entity.should_receive(:update).with(:nook_pid => nil)
54
63
  entity.cancel_all_processes!
55
64
  end
56
65
  end
@@ -80,8 +89,8 @@ describe Bumbleworks::Entity do
80
89
  describe '#persist_process_identifier' do
81
90
  it 'calls #update if method exists' do
82
91
  entity = entity_class.new
83
- entity.should_receive(:update).with(:a_column => :a_value)
84
- entity.persist_process_identifier(:a_column, :a_value)
92
+ entity.should_receive(:update).with(:a_attribute => :a_value)
93
+ entity.persist_process_identifier(:a_attribute, :a_value)
85
94
  end
86
95
 
87
96
  it 'raises exception if #update method does not exist' do
@@ -89,7 +98,7 @@ describe Bumbleworks::Entity do
89
98
  entity.stub(:respond_to?).with(:update).and_return(false)
90
99
  entity.should_receive(:update).never
91
100
  expect {
92
- entity.persist_process_identifier(:a_column, :a_value)
101
+ entity.persist_process_identifier(:a_attribute, :a_value)
93
102
  }.to raise_error("Entity must define #persist_process_identifier method if missing #update method.")
94
103
  end
95
104
  end
@@ -97,34 +106,72 @@ describe Bumbleworks::Entity do
97
106
  describe '#launch_process' do
98
107
  it 'launches process and return process if identifier not set' do
99
108
  bp = Bumbleworks::Process.new('12345')
100
- entity_class.process :noodles, :column => :noodles_pid
109
+ entity_class.process :noodles, :attribute => :noodles_pid
101
110
  entity = entity_class.new
102
111
  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)
112
+ entity.stub(:process_fields).with(:noodles).and_return({:f => 1})
113
+ entity.stub(:process_variables).with(:noodles).and_return({:v => 2})
114
+ Bumbleworks.stub(:launch!).with('noodles', {:f => 1}, {:v => 2}).and_return(bp)
106
115
  entity.should_receive(:persist_process_identifier).with(:noodles_pid, '12345')
107
116
  entity.launch_process('noodles').should == bp
108
117
  end
109
118
 
110
- it 'does nothing but returns existing process if identifier column already set' do
119
+ it 'does nothing but returns existing process if identifier attribute already set' do
111
120
  bp = Bumbleworks::Process.new('already set')
112
- entity_class.process :noodles, :column => :noodles_pid
121
+ entity_class.process :noodles, :attribute => :noodles_pid
113
122
  entity = entity_class.new
114
123
  entity.stub(:noodles_pid => 'already set')
115
124
  Bumbleworks.should_receive(:launch!).never
116
125
  entity.launch_process('noodles').should == bp
117
126
  end
118
127
 
119
- it 'launches new process anyway if identifier column already set but force is true' do
128
+ it 'launches new process anyway if identifier attribute already set but force is true' do
120
129
  bp = Bumbleworks::Process.new('12345')
121
- entity_class.process :noodles, :column => :noodles_pid
130
+ entity_class.process :noodles, :attribute => :noodles_pid
122
131
  entity = entity_class.new
123
132
  entity.stub(:noodles_pid => 'already set')
124
133
  Bumbleworks.stub(:launch! => bp)
125
134
  entity.should_receive(:persist_process_identifier).with(:noodles_pid, '12345')
126
135
  entity.launch_process('noodles', :force => true).should == bp
127
136
  end
137
+
138
+ it 'sends additional fields and variables to launch' do
139
+ entity_class.process :noodles, :attribute => :noodles_pid
140
+ entity = entity_class.new
141
+ entity.stub(:noodles_pid)
142
+ entity.stub(:persist_process_identifier)
143
+ Bumbleworks.should_receive(:launch!).with(
144
+ 'noodles',
145
+ { :entity => entity, :drink => 'apathy smoothie', :berry => 'black' },
146
+ { :so_you_said => :well_so_did_i }
147
+ ).and_return(Bumbleworks::Process.new(1))
148
+ entity.launch_process(:noodles,
149
+ :fields => { :drink => 'apathy smoothie', :berry => 'black' },
150
+ :variables => { :so_you_said => :well_so_did_i }
151
+ )
152
+ end
153
+ end
154
+
155
+ describe '#attribute_for_process_name' do
156
+ it 'returns attribute set for given process' do
157
+ entity_class.stub(:processes).and_return({
158
+ :goose => { :attribute => :goose_pid },
159
+ :the_punisher => { :attribute => :your_skin }
160
+ })
161
+ entity = entity_class.new
162
+ entity.attribute_for_process_name(:goose).should == :goose_pid
163
+ entity.attribute_for_process_name(:the_punisher).should == :your_skin
164
+ end
165
+
166
+ it 'raises exception if no process found for given name' do
167
+ entity_class.stub(:processes).and_return({
168
+ :goose => { :attribute => :goose_pid },
169
+ })
170
+ entity = entity_class.new
171
+ expect {
172
+ entity.attribute_for_process_name(:the_punisher)
173
+ }.to raise_error
174
+ end
128
175
  end
129
176
 
130
177
  describe '#subscribed_events' do
@@ -174,28 +221,28 @@ describe Bumbleworks::Entity do
174
221
 
175
222
  describe '.process' do
176
223
  it 'registers a new process' do
177
- entity_class.stub(:process_identifier_column).with(:whatever).and_return('loob')
224
+ entity_class.stub(:default_process_identifier_attribute).with(:whatever).and_return('loob')
178
225
  entity_class.process :whatever
179
226
  entity_class.processes.should == {
180
227
  :whatever => {
181
- :column => 'loob'
228
+ :attribute => 'loob'
182
229
  }
183
230
  }
184
231
  end
185
232
  end
186
233
 
187
- describe '.process_identifier_column' do
234
+ describe '.default_process_identifier_attribute' do
188
235
  it 'adds _process_identifier to end of given process name' do
189
- entity_class.process_identifier_column('zoof').should == :zoof_process_identifier
236
+ entity_class.default_process_identifier_attribute('zoof').should == :zoof_process_identifier
190
237
  end
191
238
 
192
239
  it 'ensures no duplication of _process' do
193
- entity_class.process_identifier_column('zoof_process').should == :zoof_process_identifier
240
+ entity_class.default_process_identifier_attribute('zoof_process').should == :zoof_process_identifier
194
241
  end
195
242
 
196
243
  it 'removes entity_type from beginning of identifier' do
197
244
  entity_class.stub(:entity_type).and_return('zoof')
198
- entity_class.process_identifier_column('zoof_process').should == :process_identifier
245
+ entity_class.default_process_identifier_attribute('zoof_process').should == :process_identifier
199
246
  end
200
247
  end
201
248
 
@@ -1,3 +1,5 @@
1
+ require File.expand_path(File.join(fixtures_path, 'entities', 'rainbow_loom'))
2
+
1
3
  describe Bumbleworks::Process do
2
4
  before :each do
3
5
  Bumbleworks.reset!
@@ -119,6 +121,45 @@ describe Bumbleworks::Process do
119
121
  end
120
122
  end
121
123
 
124
+ describe '#entity' do
125
+ it 'returns nil if process not ready yet' do
126
+ bp = described_class.new('nothing')
127
+ bp.entity.should be_nil
128
+ end
129
+
130
+ it 'returns entity provided at launch' do
131
+ rainbow_loom = RainbowLoom.new('1234')
132
+ bp = Bumbleworks.launch!('going_to_the_dance', :entity => rainbow_loom)
133
+ wait_until { bp.trackers.count > 0 }
134
+ bp.entity.should == rainbow_loom
135
+ end
136
+
137
+ it 'raises exception if multiple workitems have conflicting entity info' do
138
+ Bumbleworks.define_process 'conflict_this' do
139
+ concurrence do
140
+ sequence do
141
+ set 'entity_id' => 'swoo'
142
+ just_wait
143
+ end
144
+ sequence do
145
+ set 'entity_id' => 'fwee'
146
+ just_wait
147
+ end
148
+ end
149
+ end
150
+ bp = Bumbleworks.launch!('conflict_this', :entity => RainbowLoom.new('1234'))
151
+ Bumbleworks.dashboard.wait_for(:just_wait)
152
+ expect {
153
+ bp.entity
154
+ }.to raise_error(Bumbleworks::Process::EntityConflict)
155
+ end
156
+
157
+ it 'returns nil if no entity' do
158
+ bp = Bumbleworks.launch!('going_to_the_dance')
159
+ bp.entity.should be_nil
160
+ end
161
+ end
162
+
122
163
  describe '#process_status' do
123
164
  it 'returns a process_status instance for the wfid' do
124
165
  bp = described_class.new('frogheads')
@@ -40,4 +40,31 @@ describe Bumbleworks::Task::Finder do
40
40
  query.available
41
41
  end
42
42
  end
43
+
44
+ describe '#where' do
45
+ it 'compiles a finder' do
46
+ query = Bumbleworks::Task::Finder.new
47
+ query.should_receive(:available).and_return(query)
48
+ query.should_receive(:by_nickname).with(:nicholas).and_return(query)
49
+ query.should_receive(:for_roles).with([:dinner, :barca]).and_return(query)
50
+ query.should_receive(:unclaimed).and_return(query)
51
+ query.should_receive(:for_claimant).with(:dr_clam).and_return(query)
52
+ query.should_receive(:for_entity).with(:a_luffly_pirate).and_return(query)
53
+ query.should_receive(:for_processes).with([:jasmine, :mulan]).and_return(query)
54
+ query.should_receive(:completable).with(true).and_return(query)
55
+ query.should_receive(:with_fields).with({ :horse => :giant_pony, :pie => :silly_cake }).and_return(query)
56
+ query.where({
57
+ :available => true,
58
+ :nickname => :nicholas,
59
+ :roles => [:dinner, :barca],
60
+ :unclaimed => true,
61
+ :claimant => :dr_clam,
62
+ :entity => :a_luffly_pirate,
63
+ :processes => [:jasmine, :mulan],
64
+ :completable => true,
65
+ :horse => :giant_pony,
66
+ :pie => :silly_cake
67
+ })
68
+ end
69
+ end
43
70
  end
@@ -371,7 +371,7 @@ describe Bumbleworks::Task do
371
371
  end
372
372
 
373
373
  describe '.completable' do
374
- it 'returns only completable tasks' do
374
+ it 'filters by completability' do
375
375
  module WuggleHandsTask
376
376
  def completable?
377
377
  false
@@ -393,6 +393,11 @@ describe Bumbleworks::Task do
393
393
  ['a_fella', 'waggle_hands'],
394
394
  ['a_lady', 'wiggle_hands']
395
395
  ]
396
+ tasks = described_class.completable(false)
397
+ tasks.should have(1).item
398
+ tasks.map { |t| [t.role, t.nickname] }.should == [
399
+ ['a_monkey', 'wuggle_hands']
400
+ ]
396
401
  end
397
402
  end
398
403
 
@@ -469,7 +474,7 @@ describe Bumbleworks::Task do
469
474
  end
470
475
  end
471
476
 
472
- context '.for_claimant' do
477
+ describe '.for_claimant' do
473
478
  it 'returns all tasks claimed by given claimant' do
474
479
  Bumbleworks.define_process 'dog-lifecycle' do
475
480
  concurrence do
@@ -493,7 +498,34 @@ describe Bumbleworks::Task do
493
498
  end
494
499
  end
495
500
 
496
- context '.for_entity' do
501
+ describe '.with_fields' do
502
+ it 'returns all tasks with given fields' do
503
+ Bumbleworks.define_process 'divergination' do
504
+ concurrence do
505
+ sequence do
506
+ set 'bumby' => 'fancy'
507
+ bumber :task => 'wear_monocle'
508
+ end
509
+ sequence do
510
+ set 'bumby' => 'not_fancy'
511
+ concurrence do
512
+ bumber :task => 'wear_natties'
513
+ loofer :task => 'snuffle'
514
+ end
515
+ end
516
+ end
517
+ end
518
+ Bumbleworks.launch!('divergination', :grumbles => true)
519
+ Bumbleworks.dashboard.wait_for(:loofer)
520
+ described_class.with_fields(:grumbles => true).count.should == 3
521
+ described_class.with_fields(:bumby => 'fancy').count.should == 1
522
+ described_class.with_fields(:bumby => 'not_fancy').count.should == 2
523
+ described_class.with_fields(:grumbles => false, :bumby => 'not_fancy').should be_empty
524
+ described_class.with_fields(:what => 'ever').should be_empty
525
+ end
526
+ end
527
+
528
+ describe '.for_entity' do
497
529
  it 'returns all tasks associated with given entity' do
498
530
  fake_sandwich = OpenStruct.new(:identifier => 'rubies')
499
531
  Bumbleworks.define_process 'existential_pb_and_j' do
@@ -3,121 +3,33 @@ require "bumbleworks/workitem_entity_storage"
3
3
  describe Bumbleworks::WorkitemEntityStorage do
4
4
  class FakeEntityHolder
5
5
  include Bumbleworks::WorkitemEntityStorage
6
- def initialize(fields = {})
7
- @fields = fields
8
- end
9
-
10
- def workitem
11
- OpenStruct.new(:fields => @fields)
12
- end
13
- end
14
-
15
- describe '#has_entity_fields?' do
16
- it 'returns true if workitem fields include entity fields' do
17
- feh = FakeEntityHolder.new('entity_id' => '1', 'entity_type' => 'SomeEntity')
18
- feh.should have_entity_fields
19
- end
20
-
21
- it 'returns true if workitem fields include symbolized version of entity fields' do
22
- feh = FakeEntityHolder.new(:entity_id => '1', :entity_type => 'SomeEntity')
23
- feh.should have_entity_fields
24
- end
25
-
26
- it 'returns false if workitem fields do not include entity fields' do
27
- feh = FakeEntityHolder.new
28
- feh.should_not have_entity_fields
6
+ attr_reader :workitem
7
+ def initialize(workitem)
8
+ @workitem = workitem
29
9
  end
30
10
  end
31
11
 
32
- describe '#has_entity?' do
33
- it 'returns true if entity is not nil' do
34
- feh = FakeEntityHolder.new
35
- feh.stub(:entity).and_return(:a_real_boy_not_a_puppet)
36
- feh.has_entity?.should be_true
12
+ describe '#entity_storage_workitem' do
13
+ it 'returns new Bumbleworks::Workitem instance with workitem' do
14
+ Bumbleworks::Workitem.stub(:new).with(:a_workitem).and_return(:the_workitem)
15
+ feh = FakeEntityHolder.new(:a_workitem)
16
+ feh.entity_storage_workitem.should == :the_workitem
37
17
  end
38
18
 
39
- it 'returns false if EntityNotFound' do
40
- feh = FakeEntityHolder.new
41
- feh.stub(:entity).and_raise(Bumbleworks::WorkitemEntityStorage::EntityNotFound)
42
- feh.has_entity?.should be_false
19
+ it 'is memoized' do
20
+ feh = FakeEntityHolder.new(:a_workitem)
21
+ esw = feh.entity_storage_workitem
22
+ feh.entity_storage_workitem.should be esw
43
23
  end
44
24
  end
45
25
 
46
- describe '#entity' do
47
- before :all do
48
- class LovelyEntity
49
- attr_accessor :identifier
50
- def initialize(identifier)
51
- @identifier = identifier
52
- end
53
-
54
- def self.first_by_identifier(identifier)
55
- return nil unless identifier
56
- new(identifier)
57
- end
26
+ [:has_entity_fields?, :has_entity?, :entity, :entity_fields].each do |method|
27
+ describe "##{method}" do
28
+ it 'delegates to entity storage workitem' do
29
+ feh = FakeEntityHolder.new(:a_workitem)
30
+ feh.entity_storage_workitem.stub(method).with(1, 2, 3).and_return(:yay_for_bikes)
31
+ feh.send(method, 1, 2, 3).should == :yay_for_bikes
58
32
  end
59
33
  end
60
-
61
- after :all do
62
- Object.send(:remove_const, :LovelyEntity)
63
- end
64
-
65
- it 'attempts to instantiate business entity from _id and _type fields' do
66
- feh = FakeEntityHolder.new('entity_id' => '15', 'entity_type' => 'LovelyEntity')
67
- feh.entity.identifier.should == '15'
68
- end
69
-
70
- it 'works with symbolized _id and _type fields' do
71
- feh = FakeEntityHolder.new(:entity_id => '15', :entity_type => 'LovelyEntity')
72
- feh.entity.identifier.should == '15'
73
- end
74
-
75
- it 'throw exception if entity fields not present' do
76
- feh = FakeEntityHolder.new
77
- expect {
78
- feh.entity
79
- }.to raise_error Bumbleworks::WorkitemEntityStorage::EntityNotFound
80
- end
81
-
82
- it 'throw exception if entity returns nil' do
83
- feh = FakeEntityHolder.new('entity_id' => nil, 'entity_type' => 'LovelyEntity')
84
- expect {
85
- feh.entity
86
- }.to raise_error Bumbleworks::WorkitemEntityStorage::EntityNotFound, '{:entity_id=>nil, :entity_type=>"LovelyEntity"}'
87
- end
88
-
89
- it 'returns same instance when called twice' do
90
- feh = FakeEntityHolder.new('entity_id' => '15', 'entity_type' => 'LovelyEntity')
91
- feh.entity.identifier = 'pickles'
92
- feh.entity.identifier.should == 'pickles'
93
- end
94
-
95
- it 'reloads instance when called with reload option' do
96
- feh = FakeEntityHolder.new('entity_id' => '15', 'entity_type' => 'LovelyEntity')
97
- feh.entity.identifier = 'pickles'
98
- feh.entity(:reload => true).identifier.should == '15'
99
- end
100
- end
101
-
102
- describe "#entity_fields" do
103
- it 'returns empty hash if no entity' do
104
- feh = FakeEntityHolder.new
105
- feh.entity_fields.should == {}
106
- end
107
-
108
- it 'returns class name and identifier by default' do
109
- feh = FakeEntityHolder.new('entity_id' => '15', 'entity_type' => 'LovelyEntity')
110
- feh.entity_fields.should == { :type => 'LovelyEntity', :identifier => '15' }
111
- end
112
-
113
- it 'humanizes class name when requested' do
114
- feh = FakeEntityHolder.new('entity_id' => '15', 'entity_type' => 'LovelyEntity')
115
- feh.entity_fields(:humanize => true).should == { :type => 'Lovely entity', :identifier => '15' }
116
- end
117
-
118
- it 'titleizes class name when requested' do
119
- feh = FakeEntityHolder.new('entity_id' => '15', 'entity_type' => 'LovelyEntity')
120
- feh.entity_fields(:titleize => true).should == { :type => 'Lovely Entity', :identifier => '15' }
121
- end
122
34
  end
123
35
  end
@@ -0,0 +1,88 @@
1
+ require File.expand_path(File.join(fixtures_path, 'entities', 'rainbow_loom'))
2
+
3
+ describe Bumbleworks::Workitem do
4
+ let(:ruote_workitem) { Ruote::Workitem.new('fields' => {'entity_id' => '123', 'entity_type' => 'RainbowLoom'} ) }
5
+ let(:workitem) { Bumbleworks::Workitem.new(ruote_workitem)}
6
+
7
+ describe '#has_entity_fields?' do
8
+ it 'returns true if workitem fields include entity fields' do
9
+ workitem.should have_entity_fields
10
+ end
11
+
12
+ it 'returns true if workitem fields include symbolized version of entity fields' do
13
+ ruote_workitem.fields = { :entity_id => '123', :entity_type => 'RainbowLoom' }
14
+ workitem.should have_entity_fields
15
+ end
16
+
17
+ it 'returns false if workitem fields do not include entity fields' do
18
+ ruote_workitem.fields = {}
19
+ workitem.should_not have_entity_fields
20
+ end
21
+ end
22
+
23
+ describe '#has_entity?' do
24
+ it 'returns true if entity is not nil' do
25
+ workitem.stub(:entity).and_return(:a_real_boy_not_a_puppet)
26
+ workitem.has_entity?.should be_true
27
+ end
28
+
29
+ it 'returns false if EntityNotFound' do
30
+ workitem.stub(:entity).and_raise(Bumbleworks::EntityNotFound)
31
+ workitem.has_entity?.should be_false
32
+ end
33
+ end
34
+
35
+ describe '#entity' do
36
+ it 'attempts to instantiate business entity from _id and _type fields' do
37
+ workitem.entity.identifier.should == '123'
38
+ end
39
+
40
+ it 'works with symbolized _id and _type fields' do
41
+ ruote_workitem.fields = { :entity_id => '125', :entity_type => 'RainbowLoom' }
42
+ workitem.entity.identifier.should == '125'
43
+ end
44
+
45
+ it 'throw exception if entity fields not present' do
46
+ ruote_workitem.fields = {}
47
+ expect {
48
+ workitem.entity
49
+ }.to raise_error Bumbleworks::EntityNotFound
50
+ end
51
+
52
+ it 'throw exception if entity returns nil' do
53
+ ruote_workitem.fields['entity_id'] = nil
54
+ expect {
55
+ workitem.entity
56
+ }.to raise_error Bumbleworks::EntityNotFound, '{:entity_id=>nil, :entity_type=>"RainbowLoom"}'
57
+ end
58
+
59
+ it 'returns same instance when called twice' do
60
+ workitem.entity.identifier = 'nerfus'
61
+ workitem.entity.identifier.should == 'nerfus'
62
+ end
63
+
64
+ it 'reloads instance when called with reload option' do
65
+ workitem.entity.identifier = 'pickles'
66
+ workitem.entity(:reload => true).identifier.should == '123'
67
+ end
68
+ end
69
+
70
+ describe "#entity_fields" do
71
+ it 'returns empty hash if no entity' do
72
+ ruote_workitem.fields = {}
73
+ workitem.entity_fields.should == {}
74
+ end
75
+
76
+ it 'returns class name and identifier by default' do
77
+ workitem.entity_fields.should == { :type => 'RainbowLoom', :identifier => '123' }
78
+ end
79
+
80
+ it 'humanizes class name when requested' do
81
+ workitem.entity_fields(:humanize => true).should == { :type => 'Rainbow loom', :identifier => '123' }
82
+ end
83
+
84
+ it 'titleizes class name when requested' do
85
+ workitem.entity_fields(:titleize => true).should == { :type => 'Rainbow Loom', :identifier => '123' }
86
+ end
87
+ end
88
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bumbleworks
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.51
4
+ version: 0.0.52
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -12,7 +12,7 @@ authors:
12
12
  autorequire:
13
13
  bindir: bin
14
14
  cert_chain: []
15
- date: 2013-12-21 00:00:00.000000000 Z
15
+ date: 2013-12-23 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: ruote
@@ -192,6 +192,7 @@ files:
192
192
  - lib/bumbleworks/task/finder.rb
193
193
  - lib/bumbleworks/tree_builder.rb
194
194
  - lib/bumbleworks/version.rb
195
+ - lib/bumbleworks/workitem.rb
195
196
  - lib/bumbleworks/workitem_entity_storage.rb
196
197
  - lib/tasks/bumbleworks.rake
197
198
  - spec/fixtures/apps/with_default_directories/config_initializer.rb
@@ -209,7 +210,7 @@ files:
209
210
  - spec/fixtures/definitions/a_list_of_jams.rb
210
211
  - spec/fixtures/definitions/nested_folder/test_nested_process.rb
211
212
  - spec/fixtures/definitions/test_process.rb
212
- - spec/fixtures/entities/furby.rb
213
+ - spec/fixtures/entities/rainbow_loom.rb
213
214
  - spec/integration/entity_spec.rb
214
215
  - spec/integration/example_configurations_spec.rb
215
216
  - spec/integration/history_storage_spec.rb
@@ -236,6 +237,7 @@ files:
236
237
  - spec/lib/bumbleworks/task_spec.rb
237
238
  - spec/lib/bumbleworks/tree_builder_spec.rb
238
239
  - spec/lib/bumbleworks/workitem_entity_storage_spec.rb
240
+ - spec/lib/bumbleworks/workitem_spec.rb
239
241
  - spec/lib/bumbleworks_spec.rb
240
242
  - spec/spec_helper.rb
241
243
  - spec/support/path_helpers.rb
@@ -256,7 +258,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
256
258
  version: '0'
257
259
  segments:
258
260
  - 0
259
- hash: 3654012293200384530
261
+ hash: -2295219259305146267
260
262
  required_rubygems_version: !ruby/object:Gem::Requirement
261
263
  none: false
262
264
  requirements:
@@ -265,7 +267,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
265
267
  version: '0'
266
268
  segments:
267
269
  - 0
268
- hash: 3654012293200384530
270
+ hash: -2295219259305146267
269
271
  requirements: []
270
272
  rubyforge_project:
271
273
  rubygems_version: 1.8.23
@@ -288,7 +290,7 @@ test_files:
288
290
  - spec/fixtures/definitions/a_list_of_jams.rb
289
291
  - spec/fixtures/definitions/nested_folder/test_nested_process.rb
290
292
  - spec/fixtures/definitions/test_process.rb
291
- - spec/fixtures/entities/furby.rb
293
+ - spec/fixtures/entities/rainbow_loom.rb
292
294
  - spec/integration/entity_spec.rb
293
295
  - spec/integration/example_configurations_spec.rb
294
296
  - spec/integration/history_storage_spec.rb
@@ -315,6 +317,7 @@ test_files:
315
317
  - spec/lib/bumbleworks/task_spec.rb
316
318
  - spec/lib/bumbleworks/tree_builder_spec.rb
317
319
  - spec/lib/bumbleworks/workitem_entity_storage_spec.rb
320
+ - spec/lib/bumbleworks/workitem_spec.rb
318
321
  - spec/lib/bumbleworks_spec.rb
319
322
  - spec/spec_helper.rb
320
323
  - spec/support/path_helpers.rb