bumbleworks 0.0.9 → 0.0.10

Sign up to get free protection for your applications and to get access to all the features.
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: