w_flow 0.11.0 → 0.12.0

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