w_flow 0.11.0 → 0.12.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 30c37c21823d83b687e0751155b7bb9a353233ff
4
- data.tar.gz: 4cfb08e1e2966154536b8746e5d60e5db25208f8
3
+ metadata.gz: 4c6971610a0278716309118842acdfd7532ed202
4
+ data.tar.gz: bbda595555d7dcc881280bd7369d1456c8561ff6
5
5
  SHA512:
6
- metadata.gz: 337e619cfbffce4225809913df1a0843f87312c3125e4826e35612799625583278ec9467c7952b86e3f2724027e7d54b94e7dc366baa0bee2bd89f35e6f5ebd0
7
- data.tar.gz: bd40ac136446646d0985ee0bc4f4ed92c37a56fc70c41380803275a2318d8aa85e14f38b363c963fde4754a421c18772b105ad8f235b0dee1365cfd23b04be92
6
+ metadata.gz: 2012a6ea13452f2dc30418ea861fe8f54b5dcb30723cf8aa4a372835b25a9713670695ec4efee5a187b70885f40830e15fe9ddf62c0d85d9ff8c994915c4404d
7
+ data.tar.gz: fca65b36b9966e01f743fe9619b9477e5200ff96ddf2b23100749d2101b6a26a15a1550cac133067c3a843a52dc9ec81e5782a120e88b3b86480055e52a1f575
data/README.md CHANGED
@@ -8,8 +8,7 @@
8
8
 
9
9
  WFlow aims to help on designing workflows based on [Single
10
10
  Responsibility Principle](http://en.wikipedia.org/wiki/Single_responsibility_principle). WFlow
11
- proposes to achieve this by providing tools to build classes where each are responsible for a task
12
- and one task only, and by providing tools to compose these classes.
11
+ proposes to achieve this by providing tools to help compose those classes into a workflow.
13
12
 
14
13
  Word of appreciation for [usecasing](https://github.com/tdantas/usecasing),
15
14
  [interactor](https://github.com/collectiveidea/interactor) and
@@ -40,66 +39,139 @@ Or install it yourself as:
40
39
 
41
40
  ## Usage
42
41
 
43
- Imagine a situation where we want to update an appointment, notify the user of that update,
44
- and publish it in google calendar if needed:
42
+ In its most simplest form a Process would look like this:
45
43
 
46
44
  ```ruby
47
- class Find
45
+ # Process to retrive a user from the database given a user_id
46
+ class FindUser
47
+ # include module Process
48
48
  include WFlow::Process
49
49
 
50
- # helper for flow.data
51
- data_reader :appointment_id
52
- data_writer :appointment
53
-
54
- # perform is the name of the method that will be invoked when calling 'run'
50
+ # perform is where you'll execute your business logic
55
51
  def perform
56
- self.appointment = Appointment.find(appointment_id)
52
+ # flow is an object present in a Process, and it is how you retrieve data
53
+ # from the current flow, in this case the input user_id
54
+ user_id = flow.data.user_id
57
55
 
58
- # the previous code is the same as:
59
- # flow.data.appointment = Appointment.find(flow.data.appointment_id)
56
+ # when you want to output a value from the Process you set it in data
57
+ flow.data.user = User.find(user_id)
60
58
  end
61
59
  end
60
+ ```
62
61
 
63
- class UpdateAppointment
64
- include WFlow::Process
62
+ And you invoke it like this:
63
+
64
+ ```ruby
65
+ # run will returns a report object
66
+ report = FindUser.run(user_id: 10)
65
67
 
66
- execute Find, Update, NotifyUser
68
+ # this report object will contain the output from the Process
69
+ report.data.user
70
+ ```
67
71
 
68
- execute PublishInGoogleCalendar, if: :publish_in_google_calendar?
72
+ This and any other Process can be used to compose a workflow, so lets try that:
69
73
 
70
- protected
74
+ ```ruby
75
+ class SendWelcomeEmail
76
+ include WFlow::Process
77
+
78
+ # use previously created Process to find the user
79
+ execute FindUser
71
80
 
72
- def publish_in_google_calendar?
73
- flow.data.appointment.synch_in_google_calendar?
81
+ def perform
82
+ # code to send email to user
74
83
  end
75
84
  end
76
85
 
77
- # imagining that we have the id of the appointment to be updated and the new attributes for the appointment
78
- report = UpdateAppointment.run(appointment_id: appointment_id, attributes: new_attributes)
79
-
80
- # ask if workflow was a success
81
- report.success?
86
+ report = SendWelcomeEmail.run(user_id: 10)
82
87
  ```
83
88
 
84
- So what's going on here? We first declare a class and included the module WFlow::Process, so that
85
- we can compose the workflow for this process:
89
+ Processes passed to execute will be called before the perform method. You can
90
+ have as any execute as you want and as many Processes (and method nomes and Procs) in a execute.
91
+ This means that when you run SendWelcomeEmail process, it will first execute FindUser which will
92
+ set the user under flow.data, and than you can use that user to get the email address
93
+ for where to send the email.
94
+
95
+ So far so good, but lets go back to FindUser. Looking at it, we are currently not accounting for
96
+ errors cases, like what should happen when we can't find an user, or when the connection to the
97
+ database fails? This depends on what you want to do, but for this example we'll raise a flow failure,
98
+ and we'll also simplify the code a bit using data helpers:
86
99
 
87
100
  ```ruby
88
- class UpdateAppointment
101
+ class FindUser
89
102
  include WFlow::Process
103
+
104
+ # helper methods to access attributes under flow.data
105
+ data_reader :user_id
106
+ data_accessor :user
107
+
108
+ def perform
109
+ self.user = User.find(user_id)
110
+ rescue
111
+ # you can pass whatever you want to the failure! method (or even call it without arguments)
112
+ # passed value will be available in returned report under failure_log
113
+ flow.failure!('unable to find user')
114
+ end
115
+ end
116
+
117
+ report = FindUser.run(user_id: 10)
118
+
119
+ # check if success
120
+ unless report.success? # there's also a report.failure?
121
+ # failure_log is an array that contains all the objects passed to failure!
122
+ report.failure_log.each do |log|
123
+ puts log
124
+ end
125
+ end
90
126
  ```
91
127
 
92
- Now we can start composing. We compose by reusing other processes like this:
128
+ Invoking failure! will interrupt a workflow immediatly. This means that if you run
129
+ SendWelcomeEmail for a non existing user it will never run the code under perform (which
130
+ is a good thing, since there's no one to send the email to). But what if we want to do
131
+ something more even in case of failure? You can pass a handler for that:
93
132
 
94
133
  ```ruby
95
- # this indicates that it will first Find, Update next and NotifyUser last
96
- execute Find, Update, NotifyUser
134
+ class SendWelcomeEmail
135
+ include WFlow::Process
136
+
137
+ attr_writer :admin_email
138
+
139
+ # you can pass a name or a proc as a failure handler, which will be called if one
140
+ # of the Processes in execute chain raises a flow failure
141
+ execute FindUser, failure: :on_failure
142
+
143
+ # we'll use an if handler (there's also an unless handler), which allows us to control if a execute chain
144
+ # should be executed or not
145
+ execute :compose_email, SendMessageToAdmin, -> { flow.failure! }, if: -> { @no_user_found }
146
+
147
+ def perform
148
+ # ...
149
+ end
150
+
151
+ protected
97
152
 
98
- # execute this process, only if method returns true
99
- execute PublishInGoogleCalendar, if: :publish_in_google_calendar?
153
+ # failure handler, return false to cancel failure, or true to let Process fail
154
+ def on_failure
155
+ @no_user_found = true
156
+
157
+ false
158
+ end
159
+
160
+ def compose_email
161
+ self.admin_email = "we were unable to find user :("
162
+ end
163
+ end
100
164
  ```
101
165
 
102
- TODO: more documentation
166
+ Wow it suddenly become complex, but it reflects a more realistic situation. So what's
167
+ going on? First we try to find the user, which will raise a flow failure if no user
168
+ is found. In this case we want to inform the admin that something went wrong, so instead of allowing
169
+ the flow to be interrupted right away, we return false in the failure handler to cancel failure.
170
+ After that, the second execute chain will be executed, because @no_user_found is set to true. This
171
+ execution chain will invoke the method compose_email, SendMessageToAdmin, and than
172
+ call proc that reraises failure.
173
+
174
+ This is some of the features of WFlow, please check [wiki](https://github.com/junhanamaki/w_flow/wiki) for more details.
103
175
 
104
176
  ## Contributing
105
177
 
@@ -1,5 +1,6 @@
1
1
  module WFlow
2
2
  class Configuration
3
+
3
4
  attr_reader :supress_errors
4
5
 
5
6
  class << self
@@ -25,5 +26,6 @@ module WFlow
25
26
  def initialize
26
27
  @supress_errors = false
27
28
  end
29
+
28
30
  end
29
31
  end
data/lib/w_flow/data.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  module WFlow
2
2
  class Data
3
+
3
4
  def initialize(data)
4
5
  @data = data
5
6
  end
@@ -15,5 +16,6 @@ module WFlow
15
16
  @data[method_name.to_sym]
16
17
  end
17
18
  end
19
+
18
20
  end
19
21
  end
data/lib/w_flow/node.rb CHANGED
@@ -3,115 +3,15 @@ module WFlow
3
3
 
4
4
  class << self
5
5
 
6
- attr_reader :components,
7
- :if_condition,
8
- :unless_condition,
9
- :stop_condition,
10
- :failure_condition,
11
- :around_handler
6
+ attr_reader :tasks, :options
12
7
 
13
- def build(components, options)
8
+ def build(tasks, options)
14
9
  Class.new(self) do |klass|
15
- @components = components
16
- @if_condition = options[:if]
17
- @unless_condition = options[:unless]
18
- @stop_condition = options[:stop]
19
- @failure_condition = options[:failure]
20
- @around_handler = options[:around]
10
+ @tasks = tasks
11
+ @options = options
21
12
  end
22
13
  end
23
14
 
24
- def execute?(process)
25
- (if_condition.nil? || process_eval(process, if_condition)) &&
26
- (unless_condition.nil? || !process_eval(process, unless_condition))
27
- end
28
-
29
- def cancel_stop?(process)
30
- !stop_condition.nil? && !process_eval(process, stop_condition)
31
- end
32
-
33
- def cancel_failure?(process)
34
- !failure_condition.nil? && !process_eval(process, failure_condition)
35
- end
36
-
37
- def process_eval(process, object, *args)
38
- if object.is_a?(String) || object.is_a?(Symbol)
39
- process.send(object.to_s, *args)
40
- elsif object.is_a?(Proc)
41
- process.instance_exec(*args, &object)
42
- else
43
- raise InvalidArguments, UNKNOWN_EXPRESSION
44
- end
45
- end
46
-
47
- end
48
-
49
- def initialize(owner_process)
50
- @owner_process = owner_process
51
- @components = self.class.components
52
- @around_handler = self.class.around_handler
53
- end
54
-
55
- def run(flow)
56
- @flow = flow
57
- @execution_chains = []
58
-
59
- if @around_handler.nil?
60
- execute_components
61
- else
62
- @around_handler.call(method(:execute_components))
63
- end
64
- end
65
-
66
- def finalize
67
- @execution_chains.reverse_each do |execution_chain|
68
- execution_chain.reverse_each(&:finalize)
69
- end
70
- end
71
-
72
- def rollback
73
- @execution_chains.reverse_each do |execution_chain|
74
- execution_chain.reverse_each(&:rollback)
75
- end
76
- end
77
-
78
- protected
79
-
80
- def execute_components(options = {})
81
- execution_chain = []
82
-
83
- @components.each do |component|
84
- report = Supervisor.supervise do
85
- if component.is_a?(Class) && component <= Process
86
- process_worker = ProcessWorker.new(component)
87
-
88
- execution_chain << process_worker
89
-
90
- process_worker.run_as_child(@flow)
91
- else
92
- self.class.process_eval(@owner_process, component)
93
- end
94
- end
95
-
96
- if report.failed?
97
- execution_chain.reverse_each(&:rollback)
98
- execution_chain.reverse_each(&:finalize)
99
-
100
- if options[:failure].nil? || options[:failure].call
101
- @flow.log_failure(report.message)
102
- return
103
- else
104
- Supervisor.resignal!(report)
105
- end
106
- else
107
- if report.stopped? && (options[:stop].nil? || !options[:stop].call)
108
- @execution_chains << execution_chain
109
- Supervisor.resignal!(report)
110
- end
111
- end
112
- end
113
-
114
- @execution_chains << execution_chain
115
15
  end
116
16
 
117
17
  end
@@ -0,0 +1,91 @@
1
+ module WFlow
2
+ class NodeWorker
3
+
4
+ def initialize(owner_process, node_class)
5
+ @owner_process = owner_process
6
+ @tasks = node_class.tasks
7
+
8
+ options = node_class.options
9
+
10
+ @execute_if = options[:if]
11
+ @execute_unless = options[:unless]
12
+ @around_proc = options[:around]
13
+ @confirm_stop = options[:stop]
14
+ @confirm_failure = options[:failure]
15
+ end
16
+
17
+ def execute?
18
+ (@execute_if.nil? || process_eval(@execute_if)) &&
19
+ (@execute_unless.nil? || !process_eval(@execute_unless))
20
+ end
21
+
22
+ def run(flow)
23
+ @flow = flow
24
+ @executed_tasks_workers = []
25
+
26
+ report = Supervisor.supervise do
27
+ if @around_proc.nil?
28
+ execute_tasks
29
+ else
30
+ process_eval(@around_proc, method(:execute_tasks))
31
+ end
32
+ end
33
+
34
+ if report.failed?
35
+ rollback
36
+ finalize
37
+
38
+ @executed_tasks_workers.clear
39
+
40
+ if signal_failure?
41
+ Supervisor.resignal!(report)
42
+ else
43
+ @flow.log_failure(report.message)
44
+ end
45
+ elsif report.stopped? && signal_stop?
46
+ Supervisor.resignal!(report)
47
+ end
48
+ end
49
+
50
+ def finalize
51
+ executed_do(:finalize)
52
+ end
53
+
54
+ def rollback
55
+ executed_do(:rollback)
56
+ end
57
+
58
+ def process_eval(object, *args)
59
+ if object.is_a?(String) || object.is_a?(Symbol)
60
+ @owner_process.send(object.to_s, *args)
61
+ elsif object.is_a?(Proc)
62
+ @owner_process.instance_exec(*args, &object)
63
+ else
64
+ raise InvalidArguments, UNKNOWN_EXPRESSION
65
+ end
66
+ end
67
+
68
+ protected
69
+
70
+ def execute_tasks(options = {})
71
+ tasks_worker = TasksWorker.new(self, @tasks)
72
+
73
+ @executed_tasks_workers << tasks_worker
74
+
75
+ tasks_worker.run(@flow, options)
76
+ end
77
+
78
+ def signal_stop?
79
+ @confirm_stop.nil? || process_eval(@confirm_stop)
80
+ end
81
+
82
+ def signal_failure?
83
+ @confirm_failure.nil? || process_eval(@confirm_failure)
84
+ end
85
+
86
+ def executed_do(order)
87
+ @executed_tasks_workers.reverse_each(&order)
88
+ end
89
+
90
+ end
91
+ end
@@ -46,10 +46,10 @@ module WFlow
46
46
  end
47
47
  end
48
48
 
49
- def execute(*components, &block)
50
- options = components.last.is_a?(Hash) ? components.pop : {}
51
- components << block if block_given?
52
- wflow_nodes << Node.build(components, options)
49
+ def execute(*tasks, &block)
50
+ options = tasks.last.is_a?(Hash) ? tasks.pop : {}
51
+ tasks << block if block_given?
52
+ wflow_nodes << Node.build(tasks, options)
53
53
  end
54
54
 
55
55
  def run(params = {})
@@ -1,5 +1,6 @@
1
1
  module WFlow
2
2
  class ProcessWorker
3
+
3
4
  def initialize(process_class)
4
5
  @process_class = process_class
5
6
  end
@@ -28,59 +29,45 @@ module WFlow
28
29
  end
29
30
 
30
31
  def finalize
31
- @nodes.reverse_each(&:finalize)
32
+ @executed_nodes.reverse_each(&:finalize)
32
33
 
33
34
  @process.finalize if @setup_completed
34
35
  end
35
36
 
36
37
  def rollback
37
- @nodes.reverse_each(&:rollback)
38
+ @executed_nodes.reverse_each(&:rollback)
38
39
 
39
40
  @process.rollback if @perform_completed
40
41
  end
41
42
 
42
43
  protected
43
44
 
45
+ def setup(flow)
46
+ @flow = flow
47
+ @process = @process_class.new(flow)
48
+
49
+ @executed_nodes = []
50
+ @setup_completed = false
51
+ @perform_completed = false
52
+ end
53
+
44
54
  def run
45
55
  @process.setup
46
56
  @setup_completed = true
47
57
 
48
58
  @process_class.wflow_nodes.each do |node_class|
49
- next unless node_class.execute?(@process)
50
-
51
- node = node_class.new(@process)
59
+ node_worker = NodeWorker.new(@process, node_class)
52
60
 
53
- report = Supervisor.supervise { node.run(@flow) }
61
+ next unless node_worker.execute?
54
62
 
55
- if report.failed?
56
- node.rollback
57
- node.finalize
63
+ @executed_nodes << node_worker
58
64
 
59
- if node_class.cancel_failure?(@process)
60
- @flow.log_failure(report.message)
61
- else
62
- Supervisor.resignal!(report)
63
- end
64
- else
65
- @nodes << node
66
-
67
- if report.stopped? && !node_class.cancel_stop?(@process)
68
- Supervisor.resignal!(report)
69
- end
70
- end
65
+ report = node_worker.run(@flow)
71
66
  end
72
67
 
73
68
  @process.perform
74
69
  @perform_completed = true
75
70
  end
76
71
 
77
- def setup(flow)
78
- @flow = flow
79
- @process = @process_class.new(flow)
80
- @nodes = []
81
- @setup_completed = false
82
- @perform_completed = false
83
- end
84
-
85
72
  end
86
73
  end
@@ -0,0 +1,70 @@
1
+ module WFlow
2
+ class TasksWorker
3
+
4
+ def initialize(owner_node, tasks)
5
+ @owner_node = owner_node
6
+ @tasks = tasks
7
+ end
8
+
9
+ def run(flow, options = {})
10
+ @flow = flow
11
+ @options = options
12
+ @executed_tasks = []
13
+
14
+ @tasks.each do |task|
15
+ report = Supervisor.supervise { execute_task(task) }
16
+
17
+ if report.failed?
18
+ rollback
19
+ finalize
20
+
21
+ @executed_tasks.clear
22
+
23
+ if signal_failure?
24
+ Supervisor.resignal!(report)
25
+ else
26
+ flow.log_failure(report.message)
27
+ return
28
+ end
29
+ else
30
+ Supervisor.resignal!(report) if report.stopped? && signal_stop?
31
+ end
32
+ end
33
+ end
34
+
35
+ def finalize
36
+ executed_do(:finalize)
37
+ end
38
+
39
+ def rollback
40
+ executed_do(:rollback)
41
+ end
42
+
43
+ protected
44
+
45
+ def execute_task(task)
46
+ if task.is_a?(Class) && task <= Process
47
+ process_worker = ProcessWorker.new(task)
48
+
49
+ @executed_tasks << process_worker
50
+
51
+ process_worker.run_as_child(@flow)
52
+ else
53
+ @owner_node.process_eval(task)
54
+ end
55
+ end
56
+
57
+ def signal_failure?
58
+ @options[:failure].nil? || @options[:failure].call
59
+ end
60
+
61
+ def signal_stop?
62
+ @options[:stop].nil? || @options[:stop].call
63
+ end
64
+
65
+ def executed_do(order)
66
+ @executed_tasks.reverse_each(&order)
67
+ end
68
+
69
+ end
70
+ end
@@ -1,3 +1,3 @@
1
1
  module WFlow
2
- VERSION = "0.11.0"
2
+ VERSION = "0.12.0"
3
3
  end
data/lib/w_flow.rb CHANGED
@@ -6,8 +6,10 @@ require "w_flow/supervisor"
6
6
  require "w_flow/flow"
7
7
  require "w_flow/flow_report"
8
8
  require "w_flow/node"
9
+ require "w_flow/node_worker"
9
10
  require "w_flow/process"
10
11
  require "w_flow/process_worker"
12
+ require "w_flow/tasks_worker"
11
13
 
12
14
  module WFlow
13
15
 
data/w_flow.gemspec CHANGED
@@ -9,8 +9,8 @@ Gem::Specification.new do |spec|
9
9
  spec.authors = ["junhanamaki"]
10
10
  spec.email = ["jun.hanamaki@gmail.com"]
11
11
 
12
- spec.summary = %q{A workflow composer based on Single Responsability Principle to help organize projects}
13
- spec.description = %q{WFlow is a workflow composer that helps in organizing projects by splitting logic into reusable modules (processes)}
12
+ spec.summary = %q{A workflow composer based on Single Responsability Principle}
13
+ spec.description = %q{WFlow is a workflow composer that helps in code organization by splitting logic into reusable modules, more at https://github.com/junhanamaki/w_flow}
14
14
  spec.homepage = "https://github.com/junhanamaki/w_flow"
15
15
 
16
16
  spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: w_flow
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.0
4
+ version: 0.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - junhanamaki
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2015-06-03 00:00:00.000000000 Z
11
+ date: 2015-06-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -80,8 +80,8 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0.4'
83
- description: WFlow is a workflow composer that helps in organizing projects by splitting
84
- logic into reusable modules (processes)
83
+ description: WFlow is a workflow composer that helps in code organization by splitting
84
+ logic into reusable modules, more at https://github.com/junhanamaki/w_flow
85
85
  email:
86
86
  - jun.hanamaki@gmail.com
87
87
  executables: []
@@ -100,10 +100,12 @@ files:
100
100
  - lib/w_flow/flow.rb
101
101
  - lib/w_flow/flow_report.rb
102
102
  - lib/w_flow/node.rb
103
+ - lib/w_flow/node_worker.rb
103
104
  - lib/w_flow/process.rb
104
105
  - lib/w_flow/process_worker.rb
105
106
  - lib/w_flow/supervisor.rb
106
107
  - lib/w_flow/supervisor_report.rb
108
+ - lib/w_flow/tasks_worker.rb
107
109
  - lib/w_flow/version.rb
108
110
  - w_flow.gemspec
109
111
  homepage: https://github.com/junhanamaki/w_flow
@@ -129,6 +131,5 @@ rubyforge_project:
129
131
  rubygems_version: 2.4.7
130
132
  signing_key:
131
133
  specification_version: 4
132
- summary: A workflow composer based on Single Responsability Principle to help organize
133
- projects
134
+ summary: A workflow composer based on Single Responsability Principle
134
135
  test_files: []