bumbleworks 0.0.9 → 0.0.10
Sign up to get free protection for your applications and to get access to all the features.
- data/doc/GUIDE.md +3 -3
- data/doc/QUICKSTART.md +3 -3
- data/lib/bumbleworks.rb +27 -1
- data/lib/bumbleworks/configuration.rb +26 -5
- data/lib/bumbleworks/support.rb +16 -0
- data/lib/bumbleworks/task.rb +59 -5
- data/lib/bumbleworks/tasks/base.rb +11 -0
- data/lib/bumbleworks/version.rb +1 -1
- data/spec/fixtures/apps/with_default_directories/full_initializer.rb +1 -0
- data/spec/fixtures/apps/with_default_directories/{app → lib/bumbleworks}/participants/honey_participant.rb +0 -0
- data/spec/fixtures/apps/with_default_directories/{app → lib/bumbleworks}/participants/molasses_participant.rb +0 -0
- data/spec/fixtures/apps/with_default_directories/lib/{process_definitions → bumbleworks/process_definitions}/garbage_collector.rb +0 -0
- data/spec/fixtures/apps/with_default_directories/lib/{process_definitions → bumbleworks/process_definitions}/make_honey.rb +0 -0
- data/spec/fixtures/apps/with_default_directories/lib/{process_definitions → bumbleworks/process_definitions}/make_molasses.rb +0 -0
- data/spec/fixtures/apps/with_default_directories/lib/bumbleworks/tasks/make_some_honey_task.rb +5 -0
- data/spec/fixtures/apps/with_default_directories/lib/bumbleworks/tasks/taste_that_molasses_task.rb +2 -0
- data/spec/integration/configuration_spec.rb +2 -8
- data/spec/integration/sample_application_spec.rb +22 -10
- data/spec/lib/bumbleworks/configuration_spec.rb +35 -5
- data/spec/lib/bumbleworks/participant_registration_spec.rb +2 -2
- data/spec/lib/bumbleworks/support_spec.rb +28 -0
- data/spec/lib/bumbleworks/task_spec.rb +119 -2
- data/spec/lib/bumbleworks_spec.rb +45 -0
- metadata +18 -18
data/doc/GUIDE.md
CHANGED
@@ -98,9 +98,9 @@ When we're developing locally or running tests, though, running a separate worke
|
|
98
98
|
|
99
99
|
## Writing our First Process Definition
|
100
100
|
|
101
|
-
Bumbleworks, by default, will load all files in `lib/process_definitions
|
101
|
+
Bumbleworks, by default, will load all files in `lib/bumbleworks/process_definitions`, or `lib/bumbleworks/processes` if you prefer. Go ahead and create that directory, and we'll put our first process definition in there.
|
102
102
|
|
103
|
-
In Bumbleworks, our plan above (for now, we'll ignore steps 5 and 6, mostly because we're in denial) might look something like the following (save this to `lib/process_definitions/build_zen_clock.rb`):
|
103
|
+
In Bumbleworks, our plan above (for now, we'll ignore steps 5 and 6, mostly because we're in denial) might look something like the following (save this to `lib/bumbleworks/process_definitions/build_zen_clock.rb`):
|
104
104
|
|
105
105
|
```ruby
|
106
106
|
Bumbleworks.define_process 'build_zen_clock' do
|
@@ -205,7 +205,7 @@ We're doing great! We received an order, and we placed a box of parts on the co
|
|
205
205
|
|
206
206
|
## Hiring the Staff
|
207
207
|
|
208
|
-
The Zen Clock, being at once a highly technical affair and a pseudo-spiritual scam, will require both robots and real humans to build it. Let's flesh out the `build_zen_clock` process by expanding our previous `make` step (go ahead and change your `lib/process_definitions/build_zen_clock.rb` file to look like this):
|
208
|
+
The Zen Clock, being at once a highly technical affair and a pseudo-spiritual scam, will require both robots and real humans to build it. Let's flesh out the `build_zen_clock` process by expanding our previous `make` step (go ahead and change your `lib/bumbleworks/process_definitions/build_zen_clock.rb` file to look like this):
|
209
209
|
|
210
210
|
```ruby
|
211
211
|
Bumbleworks.define_process 'build_zen_clock' do
|
data/doc/QUICKSTART.md
CHANGED
@@ -31,7 +31,7 @@
|
|
31
31
|
Bumbleworks.load_definitions!
|
32
32
|
```
|
33
33
|
|
34
|
-
1. Add your first process definition at `lib/process_definitions
|
34
|
+
1. Add your first process definition at `lib/bumbleworks/process_definitions/` (or `lib/bumbleworks/processes`):
|
35
35
|
|
36
36
|
```ruby
|
37
37
|
Bumbleworks.define_process do
|
@@ -41,7 +41,7 @@
|
|
41
41
|
|
42
42
|
Process definitions follow the same syntax as [ruote](http://ruote.rubyforge.org/definitions.html), but are defined using `Bumbleworks.define_process` instead of `Ruote.define`.
|
43
43
|
|
44
|
-
1. (*optional*) Put any [custom participants](http://ruote.rubyforge.org/implementing_participants.html) in `
|
44
|
+
1. (*optional*) Put any [custom participants](http://ruote.rubyforge.org/implementing_participants.html) in `lib/bumbleworks/participants`.
|
45
45
|
|
46
46
|
1. Create an initializer file (e.g. `config/initializers/bumbleworks.rb` for Rails) with the following:
|
47
47
|
|
@@ -62,6 +62,6 @@
|
|
62
62
|
Bumbleworks.start_worker!
|
63
63
|
```
|
64
64
|
|
65
|
-
1. You can now launch processes using `Bumbleworks.launch!('process_definition_name')`.
|
65
|
+
1. You can now launch processes using `Bumbleworks.launch!('process_definition_name')`. `#launch!` takes a hash as an optional second argument - anything set here will become workitem fields. A special key, `:entity`, can be used to specify a persistent business entity for the process, which will be retrievable from process tasks (using `Task#entity`).
|
66
66
|
|
67
67
|
1. Any expressions of the form `[role] :task => [task_name]` will be turned into tasks retrievable at `Bumbleworks::Task.all`; you can get tasks specific to a role or roles using `Bumbleworks::Task.for_roles([role1, role2, ...])`.
|
data/lib/bumbleworks.rb
CHANGED
@@ -12,6 +12,7 @@ module Bumbleworks
|
|
12
12
|
class UnsupportedMode < StandardError; end
|
13
13
|
class UndefinedSetting < StandardError; end
|
14
14
|
class InvalidSetting < StandardError; end
|
15
|
+
class InvalidEntity < StandardError; end
|
15
16
|
|
16
17
|
class << self
|
17
18
|
extend Forwardable
|
@@ -85,6 +86,13 @@ module Bumbleworks
|
|
85
86
|
Bumbleworks::Ruote.register_participants(&block)
|
86
87
|
end
|
87
88
|
|
89
|
+
# @public
|
90
|
+
# Autoloads all files in the configured tasks_directory.
|
91
|
+
#
|
92
|
+
def register_tasks(&block)
|
93
|
+
Bumbleworks::Task.autoload_all
|
94
|
+
end
|
95
|
+
|
88
96
|
# @public
|
89
97
|
# Registers all process_definitions in the configured definitions_directory
|
90
98
|
# with the Ruote engine.
|
@@ -105,10 +113,28 @@ module Bumbleworks
|
|
105
113
|
|
106
114
|
# @public
|
107
115
|
# Launches the process definition with the given process name, as long as
|
108
|
-
# that definition name is already registered with Bumbleworks.
|
116
|
+
# that definition name is already registered with Bumbleworks. If options
|
117
|
+
# has an :entity key, attempts to extract the id and class name before
|
118
|
+
# sending it, so it can be properly stored in workitem fields (and
|
119
|
+
# re-instantiated later).
|
109
120
|
#
|
110
121
|
def launch!(process_definition_name, options = {})
|
122
|
+
extract_entity_from_options!(options)
|
111
123
|
Bumbleworks::Ruote.launch(process_definition_name, options)
|
112
124
|
end
|
125
|
+
|
126
|
+
private
|
127
|
+
|
128
|
+
def extract_entity_from_options!(options)
|
129
|
+
begin
|
130
|
+
if entity = options.delete(:entity)
|
131
|
+
options[:entity_id] = entity.respond_to?(:identifier) ? entity.identifier : entity.id
|
132
|
+
options[:entity_type] = entity.class.name
|
133
|
+
raise InvalidEntity, "Entity#id must be non-null" unless options[:entity_id]
|
134
|
+
end
|
135
|
+
rescue NoMethodError => e
|
136
|
+
raise InvalidEntity, "Entity must respond to #id and #class.name"
|
137
|
+
end
|
138
|
+
end
|
113
139
|
end
|
114
140
|
end
|
@@ -42,16 +42,24 @@ module Bumbleworks
|
|
42
42
|
# will load all definition files by recursively traversing the directory
|
43
43
|
# tree under this folder. No specific loading order is guaranteed
|
44
44
|
#
|
45
|
-
# default: ${Bumbleworks.root}/lib/process_definitions
|
45
|
+
# default: ${Bumbleworks.root}/lib/bumbleworks/process_definitions then ${Bumbleworks.root}/lib/bumbleworks/processes
|
46
46
|
define_setting :definitions_directory
|
47
47
|
|
48
48
|
# Path to the folder which holds the ruote participant files. Bumbleworks
|
49
49
|
# will recursively traverse the directory tree under this folder and ensure
|
50
50
|
# that all found files are autoloaded before registration of participants.
|
51
51
|
#
|
52
|
-
# default: ${Bumbleworks.root}/
|
52
|
+
# default: ${Bumbleworks.root}/lib/bumbleworks/participants
|
53
53
|
define_setting :participants_directory
|
54
54
|
|
55
|
+
# Path to the folder which holds the optional task module files, which are
|
56
|
+
# used to dynamically extend tasks (to override methods, or implement
|
57
|
+
# callbacks). Bumbleworks will recursively traverse the directory tree under
|
58
|
+
# this folder and ensure that all found files are autoloaded.
|
59
|
+
#
|
60
|
+
# default: ${Bumbleworks.root}/lib/bumbleworks/tasks
|
61
|
+
define_setting :tasks_directory
|
62
|
+
|
55
63
|
# Bumbleworks requires a dedicated key-value storage for process information. Three
|
56
64
|
# storage solutions are currently supported: Hash, Redis and Sequel. The latter
|
57
65
|
# two require the bumbleworks-redis and bumbleworks-sequel gems, respectively.
|
@@ -85,6 +93,14 @@ module Bumbleworks
|
|
85
93
|
@participants_folder ||= default_participant_directory
|
86
94
|
end
|
87
95
|
|
96
|
+
# Path where Bumbleworks will look for task modules to load.
|
97
|
+
# The path can be relative or absolute. Relative paths are
|
98
|
+
# relative to Bumbleworks.root.
|
99
|
+
#
|
100
|
+
def tasks_directory
|
101
|
+
@tasks_folder ||= default_tasks_directory
|
102
|
+
end
|
103
|
+
|
88
104
|
# Root folder where Bumbleworks looks for ruote assets (participants,
|
89
105
|
# process_definitions, etc.) The root path must be absolute.
|
90
106
|
# It can be defined through a configuration block:
|
@@ -123,7 +139,7 @@ module Bumbleworks
|
|
123
139
|
def clear!
|
124
140
|
defined_settings.each {|setting| instance_variable_set("@#{setting}", nil)}
|
125
141
|
@storage_adapters = []
|
126
|
-
@definitions_folder = @participants_folder = nil
|
142
|
+
@definitions_folder = @participants_folder = @tasks_folder = nil
|
127
143
|
end
|
128
144
|
|
129
145
|
private
|
@@ -132,15 +148,20 @@ module Bumbleworks
|
|
132
148
|
end
|
133
149
|
|
134
150
|
def default_definition_directory
|
135
|
-
default_folders = ['lib/process_definitions']
|
151
|
+
default_folders = ['lib/bumbleworks/process_definitions', 'lib/bumbleworks/processes']
|
136
152
|
find_folder(default_folders, @definitions_directory, "Definitions folder not found")
|
137
153
|
end
|
138
154
|
|
139
155
|
def default_participant_directory
|
140
|
-
default_folders = ['
|
156
|
+
default_folders = ['lib/bumbleworks/participants']
|
141
157
|
find_folder(default_folders, @participants_directory, "Participants folder not found")
|
142
158
|
end
|
143
159
|
|
160
|
+
def default_tasks_directory
|
161
|
+
default_folders = ['lib/bumbleworks/tasks']
|
162
|
+
find_folder(default_folders, @tasks_directory, "Tasks folder not found")
|
163
|
+
end
|
164
|
+
|
144
165
|
def find_folder(default_directories, defined_directory, message)
|
145
166
|
# use defined directory structure if defined
|
146
167
|
if defined_directory
|
data/lib/bumbleworks/support.rb
CHANGED
@@ -17,5 +17,21 @@ module Bumbleworks
|
|
17
17
|
memo
|
18
18
|
end
|
19
19
|
end
|
20
|
+
|
21
|
+
def constantize(name)
|
22
|
+
name_parts = name.split('::')
|
23
|
+
name_parts.shift if name_parts.first.empty?
|
24
|
+
constant = Object
|
25
|
+
|
26
|
+
name_parts.each do |name_part|
|
27
|
+
constant_defined = if Module.method(:const_defined?).arity == 1
|
28
|
+
constant.const_defined?(name_part)
|
29
|
+
else
|
30
|
+
constant.const_defined?(name_part, false)
|
31
|
+
end
|
32
|
+
constant = constant_defined ? constant.const_get(name_part) : constant.const_missing(name_part)
|
33
|
+
end
|
34
|
+
constant
|
35
|
+
end
|
20
36
|
end
|
21
37
|
end
|
data/lib/bumbleworks/task.rb
CHANGED
@@ -1,7 +1,10 @@
|
|
1
|
+
require "bumbleworks/tasks/base"
|
2
|
+
|
1
3
|
module Bumbleworks
|
2
4
|
class Task
|
3
5
|
class AlreadyClaimed < StandardError; end
|
4
6
|
class MissingWorkitem < StandardError; end
|
7
|
+
class EntityNotFound < StandardError; end
|
5
8
|
|
6
9
|
extend Forwardable
|
7
10
|
delegate [:sid, :fei, :fields, :params, :participant_name, :wfid, :wf_name] => :@workitem
|
@@ -9,6 +12,20 @@ module Bumbleworks
|
|
9
12
|
alias_method :id, :sid
|
10
13
|
|
11
14
|
class << self
|
15
|
+
# @public
|
16
|
+
# Autoload all task modules defined in files in the
|
17
|
+
# tasks_directory. The symbol for autoload comes from the
|
18
|
+
# camelized version of the filename, so this method is dependent on
|
19
|
+
# following that convention. For example, file `chew_cud_task.rb`
|
20
|
+
# should define `ChewCudTask`.
|
21
|
+
#
|
22
|
+
def autoload_all(options = {})
|
23
|
+
options[:directory] ||= Bumbleworks.tasks_directory
|
24
|
+
Bumbleworks::Support.all_files(options[:directory], :camelize => true).each do |path, name|
|
25
|
+
Object.autoload name.to_sym, path
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
12
29
|
def for_role(identifier)
|
13
30
|
for_roles([identifier])
|
14
31
|
end
|
@@ -50,6 +67,20 @@ module Bumbleworks
|
|
50
67
|
raise ArgumentError, "Not a valid workitem"
|
51
68
|
end
|
52
69
|
@nickname = params['task']
|
70
|
+
extend_module
|
71
|
+
end
|
72
|
+
|
73
|
+
def entity
|
74
|
+
if has_entity_fields?
|
75
|
+
klass = Bumbleworks::Support.constantize(fields['entity_type'])
|
76
|
+
entity = klass.first_by_identifier(fields['entity_id'])
|
77
|
+
end
|
78
|
+
raise EntityNotFound unless entity
|
79
|
+
entity
|
80
|
+
end
|
81
|
+
|
82
|
+
def has_entity_fields?
|
83
|
+
fields['entity_id'] && fields['entity_type']
|
53
84
|
end
|
54
85
|
|
55
86
|
# alias for fields[] (fields delegated to workitem)
|
@@ -66,14 +97,28 @@ module Bumbleworks
|
|
66
97
|
participant_name
|
67
98
|
end
|
68
99
|
|
100
|
+
def extend_module
|
101
|
+
extend Bumbleworks::Tasks::Base
|
102
|
+
extend task_module if nickname
|
103
|
+
rescue NameError
|
104
|
+
end
|
105
|
+
|
106
|
+
def task_module
|
107
|
+
return nil unless nickname
|
108
|
+
klass_name = Bumbleworks::Support.camelize(nickname)
|
109
|
+
klass = Bumbleworks::Support.constantize("#{klass_name}Task")
|
110
|
+
end
|
111
|
+
|
69
112
|
# update workitem with changes to fields & params
|
70
|
-
def
|
71
|
-
|
113
|
+
def update(params = {})
|
114
|
+
before_update(params)
|
115
|
+
update_workitem
|
116
|
+
after_update(params)
|
72
117
|
end
|
73
118
|
|
74
119
|
# proceed workitem (saving changes to fields)
|
75
|
-
def complete
|
76
|
-
|
120
|
+
def complete(params = {})
|
121
|
+
proceed_workitem
|
77
122
|
end
|
78
123
|
|
79
124
|
# Token used to claim task, nil if not claimed
|
@@ -96,11 +141,20 @@ module Bumbleworks
|
|
96
141
|
set_claimant(nil)
|
97
142
|
end
|
98
143
|
|
99
|
-
|
144
|
+
private
|
145
|
+
|
100
146
|
def storage_participant
|
101
147
|
self.class.storage_participant
|
102
148
|
end
|
103
149
|
|
150
|
+
def update_workitem
|
151
|
+
storage_participant.update(@workitem)
|
152
|
+
end
|
153
|
+
|
154
|
+
def proceed_workitem
|
155
|
+
storage_participant.proceed(@workitem)
|
156
|
+
end
|
157
|
+
|
104
158
|
def set_claimant(token)
|
105
159
|
if token && claimant && token != claimant
|
106
160
|
raise AlreadyClaimed, "Already claimed by #{claimant}"
|
data/lib/bumbleworks/version.rb
CHANGED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -19,7 +19,7 @@ describe Bumbleworks::Configuration do
|
|
19
19
|
|
20
20
|
it 'returns default directory when not set in configuration' do
|
21
21
|
load default_initializer
|
22
|
-
Bumbleworks.definitions_directory.should == File.join(default_app_path, 'lib', 'process_definitions')
|
22
|
+
Bumbleworks.definitions_directory.should == File.join(default_app_path, 'lib', 'bumbleworks', 'process_definitions')
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
@@ -31,13 +31,7 @@ describe Bumbleworks::Configuration do
|
|
31
31
|
|
32
32
|
it 'returns default directory when not set in configuration' do
|
33
33
|
load default_initializer
|
34
|
-
Bumbleworks.participants_directory.should == File.join(default_app_path, '
|
35
|
-
end
|
36
|
-
|
37
|
-
it 'returns second default directory if first not found' do
|
38
|
-
load default_initializer
|
39
|
-
Bumbleworks.root = File.join(default_app_path, 'app')
|
40
|
-
Bumbleworks.participants_directory.should == File.join(default_app_path, 'app', 'participants')
|
34
|
+
Bumbleworks.participants_directory.should == File.join(default_app_path, 'lib', 'bumbleworks', 'participants')
|
41
35
|
end
|
42
36
|
end
|
43
37
|
end
|
@@ -1,14 +1,14 @@
|
|
1
1
|
describe 'Bumbleworks Sample Application' do
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
}
|
2
|
+
let(:app_root) {
|
3
|
+
File.expand_path(File.join(fixtures_path, 'apps', 'with_default_directories'))
|
4
|
+
}
|
6
5
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
6
|
+
before :each do
|
7
|
+
Bumbleworks.reset!
|
8
|
+
load File.join(app_root, 'full_initializer.rb')
|
9
|
+
end
|
11
10
|
|
11
|
+
describe 'initializer' do
|
12
12
|
it 'registers participants' do
|
13
13
|
Bumbleworks.dashboard.participant_list.should have(3).items
|
14
14
|
Bumbleworks.dashboard.participant_list.map(&:classname).should =~ ['HoneyParticipant', 'MolassesParticipant', 'Ruote::StorageParticipant']
|
@@ -26,14 +26,16 @@ describe 'Bumbleworks Sample Application' do
|
|
26
26
|
{},
|
27
27
|
[["dave", {"task"=>"make_some_molasses"}, []], ["sam", {"task"=>"taste_that_molasses"}, []]]]]]
|
28
28
|
end
|
29
|
+
end
|
29
30
|
|
30
|
-
|
31
|
+
describe 'launching process' do
|
32
|
+
it 'waits for first task in catchall participant' do
|
31
33
|
Bumbleworks.launch!('make_honey')
|
32
34
|
Bumbleworks.dashboard.wait_for(:dave)
|
33
35
|
Bumbleworks::Task.all.should have(1).item
|
34
36
|
end
|
35
37
|
|
36
|
-
it '
|
38
|
+
it 'creates tasks for concurrent workflows' do
|
37
39
|
Bumbleworks.launch!('make_molasses')
|
38
40
|
Bumbleworks.dashboard.wait_for(:dave)
|
39
41
|
Bumbleworks::Task.all.should have(2).item
|
@@ -41,5 +43,15 @@ describe 'Bumbleworks Sample Application' do
|
|
41
43
|
Bumbleworks::Task.for_role('sam').should have(1).item
|
42
44
|
end
|
43
45
|
end
|
46
|
+
|
47
|
+
describe 'updating a task' do
|
48
|
+
it 'calls callbacks' do
|
49
|
+
Bumbleworks.launch!('make_honey')
|
50
|
+
Bumbleworks.dashboard.wait_for(:dave)
|
51
|
+
task = Bumbleworks::Task.for_role('dave').first
|
52
|
+
task.update('happening' => 'update')
|
53
|
+
task['what_happened'].should == 'update'
|
54
|
+
end
|
55
|
+
end
|
44
56
|
end
|
45
57
|
|
@@ -60,7 +60,14 @@ describe Bumbleworks::Configuration do
|
|
60
60
|
it 'returns the default folder if not set by client app' do
|
61
61
|
File.stub(:directory? => true)
|
62
62
|
configuration.root = '/Root'
|
63
|
-
configuration.definitions_directory.should == '/Root/lib/process_definitions'
|
63
|
+
configuration.definitions_directory.should == '/Root/lib/bumbleworks/process_definitions'
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'returns the second default folder if first does not exist' do
|
67
|
+
File.stub(:directory?).with('/Root/lib/bumbleworks/process_definitions').and_return(false)
|
68
|
+
File.stub(:directory?).with('/Root/lib/bumbleworks/processes').and_return(true)
|
69
|
+
configuration.root = '/Root'
|
70
|
+
configuration.definitions_directory.should == '/Root/lib/bumbleworks/processes'
|
64
71
|
end
|
65
72
|
|
66
73
|
it 'raises an error if default folder not found' do
|
@@ -82,10 +89,9 @@ describe Bumbleworks::Configuration do
|
|
82
89
|
end
|
83
90
|
|
84
91
|
it 'returns the default folder if not set by client app' do
|
85
|
-
File.stub(:directory?
|
86
|
-
File.stub(:directory?).with('/Root/app/participants').and_return(true)
|
92
|
+
File.stub(:directory?).with('/Root/lib/bumbleworks/participants').and_return(true)
|
87
93
|
configuration.root = '/Root'
|
88
|
-
configuration.participants_directory.should == '/Root/
|
94
|
+
configuration.participants_directory.should == '/Root/lib/bumbleworks/participants'
|
89
95
|
end
|
90
96
|
|
91
97
|
it 'raises an error if default folder not found' do
|
@@ -99,6 +105,30 @@ describe Bumbleworks::Configuration do
|
|
99
105
|
end
|
100
106
|
end
|
101
107
|
|
108
|
+
describe "#tasks_directory" do
|
109
|
+
it 'returns the folder which was set by the client app' do
|
110
|
+
File.stub(:directory?).with('/dog/ate/my/homework').and_return(true)
|
111
|
+
configuration.tasks_directory = '/dog/ate/my/homework'
|
112
|
+
configuration.tasks_directory.should == '/dog/ate/my/homework'
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'returns the default folder if not set by client app' do
|
116
|
+
File.stub(:directory?).with('/Root/lib/bumbleworks/tasks').and_return(true)
|
117
|
+
configuration.root = '/Root'
|
118
|
+
configuration.tasks_directory.should == '/Root/lib/bumbleworks/tasks'
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'raises an error if default folder not found' do
|
122
|
+
configuration.root = '/Root'
|
123
|
+
expect{configuration.tasks_directory}.to raise_error Bumbleworks::InvalidSetting
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'raises an error if specific folder not found' do
|
127
|
+
configuration.tasks_directory = '/mumbo/jumbo'
|
128
|
+
expect{configuration.tasks_directory}.to raise_error Bumbleworks::InvalidSetting
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
102
132
|
describe "#storage" do
|
103
133
|
it 'can set storage directly' do
|
104
134
|
storage = double("Storage")
|
@@ -141,7 +171,7 @@ describe Bumbleworks::Configuration do
|
|
141
171
|
configuration.clear!
|
142
172
|
|
143
173
|
configuration.root = '/Root'
|
144
|
-
configuration.definitions_directory.should == '/Root/lib/process_definitions'
|
174
|
+
configuration.definitions_directory.should == '/Root/lib/bumbleworks/process_definitions'
|
145
175
|
end
|
146
176
|
end
|
147
177
|
end
|
@@ -4,9 +4,9 @@ describe Bumbleworks::ParticipantRegistration do
|
|
4
4
|
Bumbleworks.reset!
|
5
5
|
Bumbleworks.root = File.join(fixtures_path, 'apps', 'with_default_directories')
|
6
6
|
Object.should_receive(:autoload).with(:HoneyParticipant,
|
7
|
-
File.join(Bumbleworks.root, '
|
7
|
+
File.join(Bumbleworks.root, 'lib', 'bumbleworks', 'participants', 'honey_participant.rb'))
|
8
8
|
Object.should_receive(:autoload).with(:MolassesParticipant,
|
9
|
-
File.join(Bumbleworks.root, '
|
9
|
+
File.join(Bumbleworks.root, 'lib', 'bumbleworks', 'participants', 'molasses_participant.rb'))
|
10
10
|
Bumbleworks::ParticipantRegistration.autoload_all
|
11
11
|
end
|
12
12
|
end
|
@@ -31,4 +31,32 @@ describe Bumbleworks::Support do
|
|
31
31
|
'TestNestedProcess'
|
32
32
|
end
|
33
33
|
end
|
34
|
+
|
35
|
+
describe '.constantize' do
|
36
|
+
before :each do
|
37
|
+
class Whatever
|
38
|
+
Smoothies = 'tasty'
|
39
|
+
end
|
40
|
+
class Boojus
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
after :each do
|
45
|
+
Object.send(:remove_const, :Whatever)
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'returns value of constant with given name' do
|
49
|
+
described_class.constantize('Whatever')::Smoothies.should == 'tasty'
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'works with nested constants' do
|
53
|
+
described_class.constantize('Whatever::Smoothies').should == 'tasty'
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'does not check inheritance tree' do
|
57
|
+
expect {
|
58
|
+
described_class.constantize('Whatever::Boojus')
|
59
|
+
}.to raise_error(NameError)
|
60
|
+
end
|
61
|
+
end
|
34
62
|
end
|
@@ -8,6 +8,17 @@ describe Bumbleworks::Task do
|
|
8
8
|
Bumbleworks.start_worker!
|
9
9
|
end
|
10
10
|
|
11
|
+
describe '.autoload_all' do
|
12
|
+
it 'autoloads all task modules in directory' do
|
13
|
+
Bumbleworks.root = File.join(fixtures_path, 'apps', 'with_default_directories')
|
14
|
+
Object.should_receive(:autoload).with(:MakeSomeHoneyTask,
|
15
|
+
File.join(Bumbleworks.root, 'lib', 'bumbleworks', 'tasks', 'make_some_honey_task.rb'))
|
16
|
+
Object.should_receive(:autoload).with(:TasteThatMolassesTask,
|
17
|
+
File.join(Bumbleworks.root, 'lib', 'bumbleworks', 'tasks', 'taste_that_molasses_task.rb'))
|
18
|
+
Bumbleworks::Task.autoload_all
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
11
22
|
describe '.new' do
|
12
23
|
it 'raises an error if workitem is nil' do
|
13
24
|
expect {
|
@@ -26,6 +37,53 @@ describe Bumbleworks::Task do
|
|
26
37
|
described_class.new(workflow_item)
|
27
38
|
}.not_to raise_error
|
28
39
|
end
|
40
|
+
|
41
|
+
it 'extends new object with task module' do
|
42
|
+
described_class.any_instance.should_receive(:extend_module)
|
43
|
+
described_class.new(workflow_item)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe '#extend_module' do
|
48
|
+
it 'extends with base module and task module' do
|
49
|
+
task = described_class.new(workflow_item)
|
50
|
+
task.should_receive(:task_module).and_return(:task_module_double)
|
51
|
+
task.should_receive(:extend).with(Bumbleworks::Tasks::Base).ordered
|
52
|
+
task.should_receive(:extend).with(:task_module_double).ordered
|
53
|
+
task.extend_module
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'extends only with base module if no nickname' do
|
57
|
+
task = described_class.new(workflow_item)
|
58
|
+
task.stub(:nickname).and_return(nil)
|
59
|
+
task.should_receive(:extend).with(Bumbleworks::Tasks::Base)
|
60
|
+
task.extend_module
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'extends only with base module if task module does not exist' do
|
64
|
+
task = described_class.new(workflow_item)
|
65
|
+
task.should_receive(:extend).with(Bumbleworks::Tasks::Base)
|
66
|
+
task.extend_module
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe '#task_module' do
|
71
|
+
# def task_module
|
72
|
+
# return nil unless nickname
|
73
|
+
# klass_name = Bumbleworks::Support.camelize(nickname)
|
74
|
+
# klass = Bumbleworks::Support.constantize("Bumbleworks::Tasks::#{klass_name}")
|
75
|
+
# end
|
76
|
+
it 'returns nil if no nickname' do
|
77
|
+
task = described_class.new(workflow_item)
|
78
|
+
task.stub(:nickname).and_return(nil)
|
79
|
+
task.task_module.should be_nil
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'returns constantized task nickname with "Task" appended' do
|
83
|
+
task = described_class.new(workflow_item)
|
84
|
+
Bumbleworks::Support.stub(:constantize).with("GoToWorkTask").and_return(:the_task_module)
|
85
|
+
task.task_module.should == :the_task_module
|
86
|
+
end
|
29
87
|
end
|
30
88
|
|
31
89
|
describe '#id' do
|
@@ -243,17 +301,25 @@ describe Bumbleworks::Task do
|
|
243
301
|
Bumbleworks.launch!('dog-lifecycle')
|
244
302
|
end
|
245
303
|
|
246
|
-
describe '#
|
304
|
+
describe '#update' do
|
247
305
|
it 'saves fields and params, but does not proceed process' do
|
248
306
|
event = Bumbleworks.dashboard.wait_for :dog_mouth
|
249
307
|
task = described_class.for_role('dog_mouth').first
|
250
308
|
task.params['state'] = 'is ready'
|
251
309
|
task.fields['meal'] = 'salted_rhubarb'
|
252
|
-
task.
|
310
|
+
task.update
|
253
311
|
task = described_class.for_role('dog_mouth').first
|
254
312
|
task.params['state'].should == 'is ready'
|
255
313
|
task.fields['meal'].should == 'salted_rhubarb'
|
256
314
|
end
|
315
|
+
|
316
|
+
it 'calls before_update and after_update callbacks' do
|
317
|
+
task = described_class.new(workflow_item)
|
318
|
+
task.should_receive(:before_update).with(:argue_mints).ordered
|
319
|
+
task.should_receive(:update_workitem).ordered
|
320
|
+
task.should_receive(:after_update).with(:argue_mints).ordered
|
321
|
+
task.update(:argue_mints)
|
322
|
+
end
|
257
323
|
end
|
258
324
|
|
259
325
|
describe '#complete' do
|
@@ -270,5 +336,56 @@ describe Bumbleworks::Task do
|
|
270
336
|
task.fields['meal'].should == 'root beer and a kite'
|
271
337
|
end
|
272
338
|
end
|
339
|
+
|
340
|
+
describe '#has_entity_fields?' do
|
341
|
+
it 'returns true if workitem fields include entity fields' do
|
342
|
+
task = described_class.new(workflow_item)
|
343
|
+
task['entity_id'] = '1'
|
344
|
+
task['entity_type'] = 'SomeEntity'
|
345
|
+
task.should have_entity_fields
|
346
|
+
end
|
347
|
+
|
348
|
+
it 'returns false if workitem fields do not include entity fields' do
|
349
|
+
task = described_class.new(workflow_item)
|
350
|
+
task.should_not have_entity_fields
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
describe '#entity' do
|
355
|
+
class LovelyEntity
|
356
|
+
def self.first_by_identifier(identifier)
|
357
|
+
return nil unless identifier
|
358
|
+
"Object #{identifier}"
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
let(:entitied_workflow_item) {
|
363
|
+
Ruote::Workitem.new('fields' => {
|
364
|
+
'entity_id' => '15',
|
365
|
+
'entity_type' => 'LovelyEntity',
|
366
|
+
'params' => {'task' => 'go_to_work'}
|
367
|
+
})
|
368
|
+
}
|
369
|
+
|
370
|
+
it 'attempts to instantiate business entity from _id and _type fields' do
|
371
|
+
task = described_class.new(entitied_workflow_item)
|
372
|
+
task.entity.should == 'Object 15'
|
373
|
+
end
|
374
|
+
|
375
|
+
it 'throw exception if entity fields not present' do
|
376
|
+
task = described_class.new(workflow_item)
|
377
|
+
expect {
|
378
|
+
task.entity
|
379
|
+
}.to raise_error Bumbleworks::Task::EntityNotFound
|
380
|
+
end
|
381
|
+
|
382
|
+
it 'throw exception if entity returns nil' do
|
383
|
+
task = described_class.new(entitied_workflow_item)
|
384
|
+
task['entity_id'] = nil
|
385
|
+
expect {
|
386
|
+
task.entity
|
387
|
+
}.to raise_error Bumbleworks::Task::EntityNotFound
|
388
|
+
end
|
389
|
+
end
|
273
390
|
end
|
274
391
|
end
|
@@ -51,6 +51,13 @@ describe Bumbleworks do
|
|
51
51
|
end
|
52
52
|
end
|
53
53
|
|
54
|
+
describe '.register_tasks' do
|
55
|
+
it 'autoloads task modules' do
|
56
|
+
Bumbleworks::Task.should_receive(:autoload_all)
|
57
|
+
described_class.register_tasks
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
54
61
|
describe '.register_participants' do
|
55
62
|
it 'autoloads and registers participants' do
|
56
63
|
the_block = lambda { }
|
@@ -105,4 +112,42 @@ describe Bumbleworks do
|
|
105
112
|
Bumbleworks.start_worker!.should == :lets_do_it
|
106
113
|
end
|
107
114
|
end
|
115
|
+
|
116
|
+
describe '.launch!' do
|
117
|
+
class LovelyEntity
|
118
|
+
attr_accessor :id
|
119
|
+
def initialize(id)
|
120
|
+
@id = id
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'delegates to Bumbleworks::Ruote.launch' do
|
125
|
+
Bumbleworks::Ruote.should_receive(:launch).with(:amazing_process, :hugs => :love)
|
126
|
+
Bumbleworks.launch!(:amazing_process, :hugs => :love)
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'expands entity params when entity object provided' do
|
130
|
+
Bumbleworks::Ruote.should_receive(:launch).with(:amazing_process, :entity_id => :wiley_e_coyote, :entity_type => 'LovelyEntity')
|
131
|
+
Bumbleworks.launch!(:amazing_process, :entity => LovelyEntity.new(:wiley_e_coyote))
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'uses "identifier" method instead of id, if entity has one' do
|
135
|
+
entity = LovelyEntity.new(5)
|
136
|
+
entity.stub(:identifier).and_return(:five)
|
137
|
+
Bumbleworks::Ruote.should_receive(:launch).with(:amazing_process, :entity_id => :five, :entity_type => 'LovelyEntity')
|
138
|
+
Bumbleworks.launch!(:amazing_process, :entity => entity)
|
139
|
+
end
|
140
|
+
|
141
|
+
it 'throws exception if entity has nil id' do
|
142
|
+
expect {
|
143
|
+
Bumbleworks.launch!(:amazing_process, :entity => LovelyEntity.new(nil))
|
144
|
+
}.to raise_error(Bumbleworks::InvalidEntity)
|
145
|
+
end
|
146
|
+
|
147
|
+
it 'throws exception if entity is invalid object' do
|
148
|
+
expect {
|
149
|
+
Bumbleworks.launch!(:amazing_process, :entity => :give_me_a_break)
|
150
|
+
}.to raise_error(Bumbleworks::InvalidEntity)
|
151
|
+
end
|
152
|
+
end
|
108
153
|
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.
|
4
|
+
version: 0.0.10
|
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-06-
|
15
|
+
date: 2013-06-28 00:00:00.000000000 Z
|
16
16
|
dependencies:
|
17
17
|
- !ruby/object:Gem::Dependency
|
18
18
|
name: ruote
|
@@ -175,16 +175,19 @@ files:
|
|
175
175
|
- lib/bumbleworks/storage_adapter.rb
|
176
176
|
- lib/bumbleworks/support.rb
|
177
177
|
- lib/bumbleworks/task.rb
|
178
|
+
- lib/bumbleworks/tasks/base.rb
|
178
179
|
- lib/bumbleworks/tree_builder.rb
|
179
180
|
- lib/bumbleworks/version.rb
|
180
181
|
- lib/tasks/bumbleworks.rake
|
181
|
-
- spec/fixtures/apps/with_default_directories/app/participants/honey_participant.rb
|
182
|
-
- spec/fixtures/apps/with_default_directories/app/participants/molasses_participant.rb
|
183
182
|
- spec/fixtures/apps/with_default_directories/config_initializer.rb
|
184
183
|
- spec/fixtures/apps/with_default_directories/full_initializer.rb
|
185
|
-
- spec/fixtures/apps/with_default_directories/lib/
|
186
|
-
- spec/fixtures/apps/with_default_directories/lib/
|
187
|
-
- spec/fixtures/apps/with_default_directories/lib/process_definitions/
|
184
|
+
- spec/fixtures/apps/with_default_directories/lib/bumbleworks/participants/honey_participant.rb
|
185
|
+
- spec/fixtures/apps/with_default_directories/lib/bumbleworks/participants/molasses_participant.rb
|
186
|
+
- spec/fixtures/apps/with_default_directories/lib/bumbleworks/process_definitions/garbage_collector.rb
|
187
|
+
- spec/fixtures/apps/with_default_directories/lib/bumbleworks/process_definitions/make_honey.rb
|
188
|
+
- spec/fixtures/apps/with_default_directories/lib/bumbleworks/process_definitions/make_molasses.rb
|
189
|
+
- spec/fixtures/apps/with_default_directories/lib/bumbleworks/tasks/make_some_honey_task.rb
|
190
|
+
- spec/fixtures/apps/with_default_directories/lib/bumbleworks/tasks/taste_that_molasses_task.rb
|
188
191
|
- spec/fixtures/apps/with_specified_directories/config_initializer.rb
|
189
192
|
- spec/fixtures/apps/with_specified_directories/specific_directory/definitions/.gitkeep
|
190
193
|
- spec/fixtures/apps/with_specified_directories/specific_directory/participants/.gitkeep
|
@@ -217,18 +220,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
217
220
|
- - ! '>='
|
218
221
|
- !ruby/object:Gem::Version
|
219
222
|
version: '0'
|
220
|
-
segments:
|
221
|
-
- 0
|
222
|
-
hash: -2621503672602724538
|
223
223
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
224
224
|
none: false
|
225
225
|
requirements:
|
226
226
|
- - ! '>='
|
227
227
|
- !ruby/object:Gem::Version
|
228
228
|
version: '0'
|
229
|
-
segments:
|
230
|
-
- 0
|
231
|
-
hash: -2621503672602724538
|
232
229
|
requirements: []
|
233
230
|
rubyforge_project:
|
234
231
|
rubygems_version: 1.8.23
|
@@ -236,13 +233,15 @@ signing_key:
|
|
236
233
|
specification_version: 3
|
237
234
|
summary: Framework around ruote[http://github.com/jmettraux/ruote] workflow engine
|
238
235
|
test_files:
|
239
|
-
- spec/fixtures/apps/with_default_directories/app/participants/honey_participant.rb
|
240
|
-
- spec/fixtures/apps/with_default_directories/app/participants/molasses_participant.rb
|
241
236
|
- spec/fixtures/apps/with_default_directories/config_initializer.rb
|
242
237
|
- spec/fixtures/apps/with_default_directories/full_initializer.rb
|
243
|
-
- spec/fixtures/apps/with_default_directories/lib/
|
244
|
-
- spec/fixtures/apps/with_default_directories/lib/
|
245
|
-
- spec/fixtures/apps/with_default_directories/lib/process_definitions/
|
238
|
+
- spec/fixtures/apps/with_default_directories/lib/bumbleworks/participants/honey_participant.rb
|
239
|
+
- spec/fixtures/apps/with_default_directories/lib/bumbleworks/participants/molasses_participant.rb
|
240
|
+
- spec/fixtures/apps/with_default_directories/lib/bumbleworks/process_definitions/garbage_collector.rb
|
241
|
+
- spec/fixtures/apps/with_default_directories/lib/bumbleworks/process_definitions/make_honey.rb
|
242
|
+
- spec/fixtures/apps/with_default_directories/lib/bumbleworks/process_definitions/make_molasses.rb
|
243
|
+
- spec/fixtures/apps/with_default_directories/lib/bumbleworks/tasks/make_some_honey_task.rb
|
244
|
+
- spec/fixtures/apps/with_default_directories/lib/bumbleworks/tasks/taste_that_molasses_task.rb
|
246
245
|
- spec/fixtures/apps/with_specified_directories/config_initializer.rb
|
247
246
|
- spec/fixtures/apps/with_specified_directories/specific_directory/definitions/.gitkeep
|
248
247
|
- spec/fixtures/apps/with_specified_directories/specific_directory/participants/.gitkeep
|
@@ -262,3 +261,4 @@ test_files:
|
|
262
261
|
- spec/lib/bumbleworks_spec.rb
|
263
262
|
- spec/spec_helper.rb
|
264
263
|
- spec/support/path_helpers.rb
|
264
|
+
has_rdoc:
|