bumbleworks 0.0.9 → 0.0.10

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 (24) hide show
  1. data/doc/GUIDE.md +3 -3
  2. data/doc/QUICKSTART.md +3 -3
  3. data/lib/bumbleworks.rb +27 -1
  4. data/lib/bumbleworks/configuration.rb +26 -5
  5. data/lib/bumbleworks/support.rb +16 -0
  6. data/lib/bumbleworks/task.rb +59 -5
  7. data/lib/bumbleworks/tasks/base.rb +11 -0
  8. data/lib/bumbleworks/version.rb +1 -1
  9. data/spec/fixtures/apps/with_default_directories/full_initializer.rb +1 -0
  10. data/spec/fixtures/apps/with_default_directories/{app → lib/bumbleworks}/participants/honey_participant.rb +0 -0
  11. data/spec/fixtures/apps/with_default_directories/{app → lib/bumbleworks}/participants/molasses_participant.rb +0 -0
  12. data/spec/fixtures/apps/with_default_directories/lib/{process_definitions → bumbleworks/process_definitions}/garbage_collector.rb +0 -0
  13. data/spec/fixtures/apps/with_default_directories/lib/{process_definitions → bumbleworks/process_definitions}/make_honey.rb +0 -0
  14. data/spec/fixtures/apps/with_default_directories/lib/{process_definitions → bumbleworks/process_definitions}/make_molasses.rb +0 -0
  15. data/spec/fixtures/apps/with_default_directories/lib/bumbleworks/tasks/make_some_honey_task.rb +5 -0
  16. data/spec/fixtures/apps/with_default_directories/lib/bumbleworks/tasks/taste_that_molasses_task.rb +2 -0
  17. data/spec/integration/configuration_spec.rb +2 -8
  18. data/spec/integration/sample_application_spec.rb +22 -10
  19. data/spec/lib/bumbleworks/configuration_spec.rb +35 -5
  20. data/spec/lib/bumbleworks/participant_registration_spec.rb +2 -2
  21. data/spec/lib/bumbleworks/support_spec.rb +28 -0
  22. data/spec/lib/bumbleworks/task_spec.rb +119 -2
  23. data/spec/lib/bumbleworks_spec.rb +45 -0
  24. metadata +18 -18
@@ -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`. Go ahead and create that directory, and we'll put our first process definition in there.
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
@@ -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 `app/participants` or `participants`.
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, ...])`.
@@ -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}/participants then ${Bumbleworks.root}/app/participants
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 = ['participants', 'app/participants']
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
@@ -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
@@ -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 save
71
- storage_participant.update(@workitem)
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
- storage_participant.proceed(@workitem)
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
- private
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}"
@@ -0,0 +1,11 @@
1
+ module Bumbleworks
2
+ module Tasks
3
+ module Base
4
+ def before_update(params)
5
+ end
6
+
7
+ def after_update(params)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -1,3 +1,3 @@
1
1
  module Bumbleworks
2
- VERSION = "0.0.9"
2
+ VERSION = "0.0.10"
3
3
  end
@@ -8,5 +8,6 @@ Bumbleworks.register_participants do
8
8
  molasses_maker MolassesParticipant
9
9
  end
10
10
 
11
+ Bumbleworks.register_tasks
11
12
  Bumbleworks.load_definitions!
12
13
  Bumbleworks.start_worker!
@@ -0,0 +1,5 @@
1
+ module MakeSomeHoneyTask
2
+ def before_update(params)
3
+ fields['what_happened'] = params['happening']
4
+ end
5
+ end
@@ -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, 'app', 'participants')
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
- describe 'initializer' do
3
- let(:app_root) {
4
- File.expand_path(File.join(fixtures_path, 'apps', 'with_default_directories'))
5
- }
2
+ let(:app_root) {
3
+ File.expand_path(File.join(fixtures_path, 'apps', 'with_default_directories'))
4
+ }
6
5
 
7
- before :each do
8
- Bumbleworks.reset!
9
- load File.join(app_root, 'full_initializer.rb')
10
- end
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
- it 'automatically starts engine and waits for first task in catchall participant' do
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 'automatically starts engine and waits for first task in catchall participant' do
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? => false)
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/app/participants'
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, 'app', 'participants', 'honey_participant.rb'))
7
+ File.join(Bumbleworks.root, 'lib', 'bumbleworks', 'participants', 'honey_participant.rb'))
8
8
  Object.should_receive(:autoload).with(:MolassesParticipant,
9
- File.join(Bumbleworks.root, 'app', 'participants', 'molasses_participant.rb'))
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 '#save' do
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.save
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.9
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-26 00:00:00.000000000 Z
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/process_definitions/garbage_collector.rb
186
- - spec/fixtures/apps/with_default_directories/lib/process_definitions/make_honey.rb
187
- - spec/fixtures/apps/with_default_directories/lib/process_definitions/make_molasses.rb
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/process_definitions/garbage_collector.rb
244
- - spec/fixtures/apps/with_default_directories/lib/process_definitions/make_honey.rb
245
- - spec/fixtures/apps/with_default_directories/lib/process_definitions/make_molasses.rb
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: