bumbleworks 0.0.48 → 0.0.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. data/.gitignore +4 -1
  2. data/lib/bumbleworks.rb +9 -8
  3. data/lib/bumbleworks/configuration.rb +8 -5
  4. data/lib/bumbleworks/entity.rb +88 -0
  5. data/lib/bumbleworks/participant.rb +6 -7
  6. data/lib/bumbleworks/participant/base.rb +11 -0
  7. data/lib/bumbleworks/participant/entity_interactor.rb +29 -0
  8. data/lib/bumbleworks/participant/error_handler.rb +14 -0
  9. data/lib/bumbleworks/participant/local_participant.rb +11 -0
  10. data/lib/bumbleworks/participant/storage_participant.rb +11 -0
  11. data/lib/bumbleworks/process.rb +64 -0
  12. data/lib/bumbleworks/ruote.rb +12 -4
  13. data/lib/bumbleworks/support.rb +3 -5
  14. data/lib/bumbleworks/task.rb +6 -4
  15. data/lib/bumbleworks/{tasks → task}/base.rb +1 -1
  16. data/lib/bumbleworks/task/finder.rb +39 -3
  17. data/lib/bumbleworks/version.rb +1 -1
  18. data/spec/fixtures/entities/furby.rb +30 -0
  19. data/spec/integration/entity_spec.rb +49 -0
  20. data/spec/integration/history_storage_spec.rb +4 -4
  21. data/spec/integration/sample_application_spec.rb +8 -2
  22. data/spec/lib/bumbleworks/entity_spec.rb +208 -0
  23. data/spec/lib/bumbleworks/{participant_spec.rb → participant/base_spec.rb} +3 -3
  24. data/spec/lib/bumbleworks/participant/entity_interactor_spec.rb +91 -0
  25. data/spec/lib/bumbleworks/{error_handler_participant_spec.rb → participant/error_handler_spec.rb} +1 -1
  26. data/spec/lib/bumbleworks/{local_participant_spec.rb → participant/local_participant_spec.rb} +1 -1
  27. data/spec/lib/bumbleworks/process_spec.rb +147 -0
  28. data/spec/lib/bumbleworks/ruote/exp/broadcast_event_expression_spec.rb +1 -1
  29. data/spec/lib/bumbleworks/ruote/exp/wait_for_event_expression_spec.rb +3 -3
  30. data/spec/lib/bumbleworks/ruote_spec.rb +28 -21
  31. data/spec/lib/bumbleworks/task/finder_spec.rb +9 -0
  32. data/spec/lib/bumbleworks/task_spec.rb +98 -3
  33. data/spec/lib/bumbleworks_spec.rb +14 -7
  34. data/spec/spec_helper.rb +0 -1
  35. data/spec/support/process_testing_helpers.rb +9 -0
  36. metadata +34 -13
  37. data/lib/bumbleworks/error_handler_participant.rb +0 -12
  38. data/lib/bumbleworks/local_participant.rb +0 -9
  39. data/lib/bumbleworks/storage_participant.rb +0 -9
data/.gitignore CHANGED
@@ -14,4 +14,7 @@ spec/reports
14
14
  test/tmp
15
15
  test/version_tmp
16
16
  tmp
17
- .bash_color
17
+ .bash_color
18
+ *.sublime-*
19
+ default.vim
20
+ default.vim.lock
data/lib/bumbleworks.rb CHANGED
@@ -3,17 +3,18 @@ require "bumbleworks/version"
3
3
  require "bumbleworks/configuration"
4
4
  require "bumbleworks/support"
5
5
  require "bumbleworks/process_definition"
6
+ require "bumbleworks/process"
6
7
  require "bumbleworks/task"
7
8
  require "bumbleworks/participant_registration"
8
9
  require "bumbleworks/ruote"
9
10
  require "bumbleworks/hash_storage"
10
- require "bumbleworks/simple_logger"
11
- require "bumbleworks/storage_participant"
12
- require "bumbleworks/local_participant"
13
- require "bumbleworks/participant"
14
11
  require "bumbleworks/error_handler"
12
+ require "bumbleworks/entity"
13
+ require "bumbleworks/participant"
14
+
15
+ # default implementations
16
+ require "bumbleworks/simple_logger"
15
17
  require "bumbleworks/error_logger"
16
- require "bumbleworks/error_handler_participant"
17
18
 
18
19
  module Bumbleworks
19
20
  class UnsupportedMode < StandardError; end
@@ -23,7 +24,6 @@ module Bumbleworks
23
24
 
24
25
  class << self
25
26
  extend Forwardable
26
- attr_accessor :env
27
27
 
28
28
  Configuration.defined_settings.each do |setting|
29
29
  def_delegators :configuration, setting, "#{setting.to_s}="
@@ -136,7 +136,8 @@ module Bumbleworks
136
136
  #
137
137
  def launch!(process_definition_name, *args)
138
138
  extract_entity_from_fields!(args.first || {})
139
- Bumbleworks::Ruote.launch(process_definition_name, *args)
139
+ pid = Bumbleworks::Ruote.launch(process_definition_name, *args)
140
+ Bumbleworks::Process.new(pid)
140
141
  end
141
142
 
142
143
  private
@@ -149,7 +150,7 @@ module Bumbleworks
149
150
  raise InvalidEntity, "Entity#id must be non-null" unless fields[:entity_id]
150
151
  end
151
152
  rescue NoMethodError => e
152
- raise InvalidEntity, "Entity must respond to #id and #class.name"
153
+ raise InvalidEntity, "Entity must respond to #identifier (or #id) and #class.name"
153
154
  end
154
155
  end
155
156
  end
@@ -115,9 +115,11 @@ module Bumbleworks
115
115
  # default: 5 seconds
116
116
  define_setting :timeout
117
117
 
118
- # When errors occur during the exection of a process, errors are captured and dispatched to
119
- # the registerd error handlers. an error handler derives from the Bumbleworks::ErrorHandler
120
- # and will receive the error information through the #on_error method.
118
+ # When errors occur during the execution of a process, errors are captured and dispatched to
119
+ # the registered error handlers. An error handler must take a single initialization argument
120
+ # (the workitem at the point of error), and implement the #on_error method. You can subclass the
121
+ # Bumbleworks::ErrorHandler class for the initializer and workitem entity storage. The default
122
+ # handler (Bumbleworks::ErrorLogger) will simply send the configured logger an ERROR log entry.
121
123
  #
122
124
  # class MySpecialHandler < Bumbleworks::ErrorHandler
123
125
  # def on_error
@@ -126,11 +128,12 @@ module Bumbleworks
126
128
  # end
127
129
  #
128
130
  # For exclusive use:
129
- # Bumbleworks.error_handlers = [MySpeicalHandler, MySpecialHandler2]
131
+ # Bumbleworks.error_handlers = [MySpecialHandler, MySpecialHandler2]
130
132
  #
131
133
  # To append to exisiting handlers:
132
- # Bumbleworks.error_handlers << MySpeicalHandler
134
+ # Bumbleworks.error_handlers << MySpecialHandler
133
135
  #
136
+ # default: Bumbleworks::ErrorLogger
134
137
  define_setting :error_handlers
135
138
 
136
139
  # If #store_history is true, all messages will be logged in the storage under a special
@@ -0,0 +1,88 @@
1
+ module Bumbleworks
2
+ module Entity
3
+ def self.included(klass)
4
+ klass.extend ClassMethods
5
+ end
6
+
7
+ def launch_process(process_name, options = {})
8
+ identifier_column = self.class.processes[process_name.to_sym][:column]
9
+ if (options[:force] == true || (process_identifier = self.send(identifier_column)).nil?)
10
+ workitem_fields = process_fields(process_name.to_sym)
11
+ variables = process_variables(process_name.to_sym)
12
+ process_identifier = Bumbleworks.launch!(process_name.to_s, workitem_fields, variables).wfid
13
+ persist_process_identifier(identifier_column.to_sym, process_identifier)
14
+ end
15
+ Bumbleworks::Process.new(process_identifier)
16
+ end
17
+
18
+ def persist_process_identifier(identifier_column, process_identifier)
19
+ if self.respond_to?(:update)
20
+ update(identifier_column => process_identifier)
21
+ else
22
+ raise "Entity must define #persist_process_identifier method if missing #update method."
23
+ end
24
+ end
25
+
26
+ def processes_by_name
27
+ return {} unless self.class.processes
28
+ process_names = self.class.processes.keys
29
+ process_names.inject({}) do |memo, name|
30
+ pid = self.send(self.class.processes[name][:column])
31
+ memo[name] = pid ? Bumbleworks::Process.new(pid) : nil
32
+ memo
33
+ end
34
+ end
35
+
36
+ def processes
37
+ processes_by_name.values.compact
38
+ end
39
+
40
+ def cancel_all_processes!
41
+ processes.each do |process|
42
+ process.cancel!
43
+ end
44
+ end
45
+
46
+ def tasks(nickname = nil)
47
+ finder = Bumbleworks::Task.for_entity(self)
48
+ finder = finder.by_nickname(nickname) if nickname
49
+ finder
50
+ end
51
+
52
+ def process_fields(process_name = nil)
53
+ { :entity => self }
54
+ end
55
+
56
+ def process_variables(process_name = nil)
57
+ {}
58
+ end
59
+
60
+ def subscribed_events
61
+ processes.values.compact.map(&:subscribed_events).flatten.uniq
62
+ end
63
+
64
+ def is_waiting_for?(event)
65
+ subscribed_events.include? event.to_s
66
+ end
67
+
68
+ module ClassMethods
69
+ attr_reader :processes
70
+
71
+ def process(process_name, options = {})
72
+ options[:column] ||= process_identifier_column(process_name)
73
+ (@processes ||= {})[process_name.to_sym] = options
74
+ end
75
+
76
+ def entity_type
77
+ Bumbleworks::Support.tokenize(name)
78
+ end
79
+
80
+ def process_identifier_column(process_name)
81
+ identifier_column = "#{process_name}_process_identifier"
82
+ identifier_column.gsub!(/^#{entity_type}_/, '')
83
+ identifier_column.gsub!(/process_process/, 'process')
84
+ identifier_column.to_sym
85
+ end
86
+ end
87
+ end
88
+ end
@@ -1,11 +1,10 @@
1
- require "bumbleworks/local_participant"
1
+ require "bumbleworks/participant/storage_participant"
2
+ require "bumbleworks/participant/local_participant"
3
+ require "bumbleworks/participant/base"
4
+ require "bumbleworks/participant/error_handler"
5
+ require "bumbleworks/participant/entity_interactor"
2
6
 
3
7
  module Bumbleworks
4
- class Participant
5
- include LocalParticipant
6
-
7
- def on_cancel
8
- # default no-op method
9
- end
8
+ module Participant
10
9
  end
11
10
  end
@@ -0,0 +1,11 @@
1
+ module Bumbleworks
2
+ module Participant
3
+ class Base
4
+ include LocalParticipant
5
+
6
+ def on_cancel
7
+ # default no-op method
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,29 @@
1
+ module Bumbleworks
2
+ module Participant
3
+ class EntityInteractor < Bumbleworks::Participant::Base
4
+ def on_workitem
5
+ method_name = workitem.fields['params']['method'] ||
6
+ workitem.fields['params']['to'] ||
7
+ workitem.fields['params']['for']
8
+ result_field = workitem.fields['params']['and_save_as']
9
+ arguments = workitem.fields['params']['arguments'] ||
10
+ workitem.fields['params']['with']
11
+ result = call_method(method_name, :save_as => result_field, :args => arguments)
12
+ reply
13
+ end
14
+
15
+ def call_method(method_name, options = {})
16
+ result = if options[:args]
17
+ options[:args] = [options[:args]] if options[:args].is_a?(Hash)
18
+ entity.send(method_name, *options[:args])
19
+ else
20
+ entity.send(method_name)
21
+ end
22
+ if result && options[:save_as]
23
+ workitem.fields[options[:save_as]] = result
24
+ end
25
+ result
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,14 @@
1
+ module Bumbleworks
2
+ module Participant
3
+ class ErrorHandler < Bumbleworks::Participant::Base
4
+ def on_workitem
5
+ return if (error_handlers = Bumbleworks.error_handlers).empty?
6
+
7
+ error_handlers.each do |error_handler|
8
+ error_handler.new(workitem).on_error
9
+ end
10
+ reply
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,11 @@
1
+ require "ruote/part/local_participant"
2
+ require "bumbleworks/workitem_entity_storage"
3
+
4
+ module Bumbleworks
5
+ module Participant
6
+ module LocalParticipant
7
+ include ::Ruote::LocalParticipant
8
+ include WorkitemEntityStorage
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module Bumbleworks
2
+ module Participant
3
+ class StorageParticipant < ::Ruote::StorageParticipant
4
+ def on_workitem
5
+ return_value = super
6
+ Bumbleworks::Task.new(self[workitem.sid]).on_dispatch
7
+ return return_value
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,64 @@
1
+ module Bumbleworks
2
+ class Process
3
+ attr_reader :id
4
+
5
+ def initialize(wfid)
6
+ @id = wfid
7
+ end
8
+
9
+ alias_method :wfid, :id
10
+
11
+ def ==(other)
12
+ wfid == other.wfid
13
+ end
14
+
15
+ def tasks
16
+ Bumbleworks::Task.for_process(wfid)
17
+ end
18
+
19
+ def trackers
20
+ Bumbleworks.dashboard.get_trackers.values.select { |attrs|
21
+ attrs['msg']['fei'] && attrs['msg']['fei']['wfid'] == id
22
+ }
23
+ end
24
+
25
+ def all_subscribed_tags
26
+ events = trackers.inject({}) do |memo, t|
27
+ if t['wfid'].nil?
28
+ (memo[:global] ||= []).concat t['conditions']['tag']
29
+ else
30
+ (memo[t['wfid']] ||= []).concat t['conditions']['tag']
31
+ end
32
+ memo
33
+ end
34
+ end
35
+
36
+ def subscribed_events
37
+ all_subscribed_tags[:global]
38
+ end
39
+
40
+ def is_waiting_for?(event)
41
+ subscribed_events.include? event.to_s
42
+ end
43
+
44
+ def kill!
45
+ Bumbleworks.kill_process!(wfid)
46
+ end
47
+
48
+ def cancel!
49
+ Bumbleworks.cancel_process!(wfid)
50
+ end
51
+
52
+ def process_status
53
+ Bumbleworks.dashboard.process(id)
54
+ end
55
+
56
+ def method_missing(method, *args)
57
+ ps = process_status
58
+ if ps.respond_to?(method)
59
+ return ps.send(method, *args)
60
+ end
61
+ super
62
+ end
63
+ end
64
+ end
@@ -56,7 +56,7 @@ module Bumbleworks
56
56
  while dashboard.process(wfid)
57
57
  if (Time.now - start_time) > options[:timeout]
58
58
  error_type = options[:method] == :cancel ? CancelTimeout : KillTimeout
59
- raise error_type, "Process #{options[:method]} taking too long - #{dashboard.processes.count} processes remain. Errors: #{dashboard.errors}"
59
+ raise error_type, "Process #{options[:method]} for wfid '#{wfid}' taking too long. Errors: #{dashboard.errors(wfid)}"
60
60
  end
61
61
  sleep 0.1
62
62
  end
@@ -100,14 +100,22 @@ module Bumbleworks
100
100
 
101
101
  def register_participants(&block)
102
102
  dashboard.register(&block) if block
103
+ register_entity_interactor
103
104
  register_error_handler
104
105
  set_catchall_if_needed
105
106
  dashboard.participant_list
106
107
  end
107
108
 
109
+ def register_entity_interactor
110
+ unless dashboard.participant_list.any? { |part| part.regex == "^(ask|tell)_entity$" }
111
+ entity_interactor = ::Ruote::ParticipantEntry.new(["^(ask|tell)_entity$", ["Bumbleworks::Participant::EntityInteractor", {}]])
112
+ dashboard.participant_list = dashboard.participant_list.unshift(entity_interactor)
113
+ end
114
+ end
115
+
108
116
  def register_error_handler
109
117
  unless dashboard.participant_list.any? { |part| part.regex == '^error_handler_participant$' }
110
- error_handler_participant = ::Ruote::ParticipantEntry.new(['^error_handler_participant$', ["Bumbleworks::ErrorHandlerParticipant", {}]])
118
+ error_handler_participant = ::Ruote::ParticipantEntry.new(['^error_handler_participant$', ["Bumbleworks::Participant::ErrorHandler", {}]])
111
119
  dashboard.participant_list = dashboard.participant_list.unshift(error_handler_participant)
112
120
  dashboard.on_error = 'error_handler_participant'
113
121
  end
@@ -122,8 +130,8 @@ module Bumbleworks
122
130
  def set_catchall_if_needed
123
131
  last_participant = dashboard.participant_list.last
124
132
  unless last_participant && last_participant.regex == "^.+$" &&
125
- ["Ruote::StorageParticipant", "Bumbleworks::StorageParticipant"].include?(last_participant.classname)
126
- catchall = ::Ruote::ParticipantEntry.new(["^.+$", ["Bumbleworks::StorageParticipant", {}]])
133
+ ["Ruote::StorageParticipant", "Bumbleworks::Participant::StorageParticipant"].include?(last_participant.classname)
134
+ catchall = ::Ruote::ParticipantEntry.new(["^.+$", ["Bumbleworks::Participant::StorageParticipant", {}]])
127
135
  dashboard.participant_list = dashboard.participant_list.push(catchall)
128
136
  end
129
137
  end
@@ -24,11 +24,9 @@ module Bumbleworks
24
24
  constant = Object
25
25
 
26
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
27
+ const_defined_args = [name_part]
28
+ const_defined_args << false unless Module.method(:const_defined?).arity == 1
29
+ constant_defined = constant.const_defined?(*const_defined_args)
32
30
  constant = constant_defined ? constant.const_get(name_part) : constant.const_missing(name_part)
33
31
  end
34
32
  constant
@@ -1,5 +1,5 @@
1
- require "bumbleworks/tasks/base"
2
1
  require "bumbleworks/workitem_entity_storage"
2
+ require "bumbleworks/task/base"
3
3
  require "bumbleworks/task/finder"
4
4
 
5
5
  module Bumbleworks
@@ -80,9 +80,11 @@ module Bumbleworks
80
80
  end
81
81
 
82
82
  def extend_module
83
- extend Bumbleworks::Tasks::Base
84
- extend task_module if nickname
85
- rescue NameError
83
+ extend Bumbleworks::Task::Base
84
+ begin
85
+ extend task_module if nickname
86
+ rescue NameError
87
+ end
86
88
  end
87
89
 
88
90
  def task_module
@@ -1,5 +1,5 @@
1
1
  module Bumbleworks
2
- module Tasks
2
+ class Task
3
3
  module Base
4
4
  def before_update(params); end
5
5
  def after_update(params); end
@@ -5,7 +5,14 @@ module Bumbleworks
5
5
 
6
6
  def initialize(queries = [], task_class = Bumbleworks::Task)
7
7
  @queries = queries
8
+ @queries << proc { |wi| wi['fields']['params']['task'] }
8
9
  @task_class = task_class
10
+ @task_filters = []
11
+ @wfids = nil
12
+ end
13
+
14
+ def available
15
+ unclaimed.completable
9
16
  end
10
17
 
11
18
  def by_nickname(nickname)
@@ -46,13 +53,33 @@ module Bumbleworks
46
53
  self
47
54
  end
48
55
 
56
+ def for_processes(processes)
57
+ process_ids = (processes || []).map { |p|
58
+ p.is_a?(Bumbleworks::Process) ? p.wfid : p
59
+ }
60
+ @wfids = process_ids
61
+ self
62
+ end
63
+
64
+ def for_process(process)
65
+ for_processes([process])
66
+ end
67
+
49
68
  def all
50
- workitems = Bumbleworks.dashboard.storage_participant.send(:do_select, {}) { |wi|
69
+ return [] if @wfids == []
70
+ workitems = Bumbleworks.dashboard.context.storage.get_many('workitems', @wfids).select { |wi|
51
71
  @queries.all? { |q| q.call(wi) }
72
+ }.collect { |wi|
73
+ ::Ruote::Workitem.new(wi)
52
74
  }
53
75
  from_workitems(workitems)
54
76
  end
55
77
 
78
+ def completable
79
+ @task_filters << proc { |task| task.completable? }
80
+ self
81
+ end
82
+
56
83
  def each(&block)
57
84
  all.each(&block)
58
85
  end
@@ -76,10 +103,19 @@ module Bumbleworks
76
103
 
77
104
  private
78
105
 
106
+ def filter_tasks(tasks)
107
+ @task_filters.empty? ? tasks :
108
+ tasks.select { |task|
109
+ @task_filters.all? { |f| f.call(task) }
110
+ }
111
+ end
112
+
79
113
  def from_workitems(workitems)
80
- workitems.map { |wi|
81
- @task_class.new(wi) if wi.params['task']
114
+ tasks = workitems.map { |wi|
115
+ @task_class.new(wi)
82
116
  }.compact
117
+
118
+ filter_tasks(tasks)
83
119
  end
84
120
  end
85
121
  end