w_flow 0.10.0 → 0.11.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: 542302c862a2fd32f275fdcc7ef118fe28d782df
4
- data.tar.gz: cdffac74ea395e3869a48dd1d92e6f0381fba377
3
+ metadata.gz: 30c37c21823d83b687e0751155b7bb9a353233ff
4
+ data.tar.gz: 4cfb08e1e2966154536b8746e5d60e5db25208f8
5
5
  SHA512:
6
- metadata.gz: b1f200b2a3de539ea4328ddc6be29cbf988b784b07c5ca5cd4c1b6bc2622d47613296a66c1d2279952be962b8002a6ae05db9fd0af9ad23b654ee57cbc1c2ece
7
- data.tar.gz: ded155f14d82e06a0c437d9b6ef8f273c5b958b3921604302dd146588605e15178ebef0b22a7dda8cfab98007c408a26cd2fe75b601bf34edc00ef07ad8d39ef
6
+ metadata.gz: 337e619cfbffce4225809913df1a0843f87312c3125e4826e35612799625583278ec9467c7952b86e3f2724027e7d54b94e7dc366baa0bee2bd89f35e6f5ebd0
7
+ data.tar.gz: bd40ac136446646d0985ee0bc4f4ed92c37a56fc70c41380803275a2318d8aa85e14f38b363c963fde4754a421c18772b105ad8f235b0dee1365cfd23b04be92
data/.travis.yml CHANGED
@@ -1,5 +1,6 @@
1
1
  language: ruby
2
2
  rvm:
3
+ - 2.2.2
3
4
  - 2.2.1
4
5
  - 2.2.0
5
6
  - 2.1.1
data/README.md CHANGED
@@ -20,7 +20,7 @@ inspiration for this gem.
20
20
 
21
21
  Tested with:
22
22
 
23
- * ruby 2.2.1, 2.2.0, 2.1.1, 2.0.0
23
+ * ruby 2.2.2, 2.2.1, 2.2.0, 2.1.1, 2.0.0
24
24
 
25
25
  ## Installation
26
26
 
@@ -40,25 +40,66 @@ Or install it yourself as:
40
40
 
41
41
  ## Usage
42
42
 
43
- On its most simplest form a task (in WFlow called a Process) is something like this:
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:
44
45
 
45
46
  ```ruby
46
- class SaveUser
47
+ class Find
47
48
  include WFlow::Process
48
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'
49
55
  def perform
50
- # arguments passed to run will be under flow.data
51
- flow.data.user.save
56
+ self.appointment = Appointment.find(appointment_id)
57
+
58
+ # the previous code is the same as:
59
+ # flow.data.appointment = Appointment.find(flow.data.appointment_id)
60
+ end
61
+ end
62
+
63
+ class UpdateAppointment
64
+ include WFlow::Process
65
+
66
+ execute Find, Update, NotifyUser
67
+
68
+ execute PublishInGoogleCalendar, if: :publish_in_google_calendar?
69
+
70
+ protected
71
+
72
+ def publish_in_google_calendar?
73
+ flow.data.appointment.synch_in_google_calendar?
52
74
  end
53
75
  end
54
76
 
55
- # run process, it will return a report object
56
- report = SaveUser.run(user: current_user)
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)
57
79
 
58
- report.data.success?
80
+ # ask if workflow was a success
81
+ report.success?
59
82
  ```
60
83
 
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:
86
+
87
+ ```ruby
88
+ class UpdateAppointment
89
+ include WFlow::Process
90
+ ```
91
+
92
+ Now we can start composing. We compose by reusing other processes like this:
93
+
94
+ ```ruby
95
+ # this indicates that it will first Find, Update next and NotifyUser last
96
+ execute Find, Update, NotifyUser
97
+
98
+ # execute this process, only if method returns true
99
+ execute PublishInGoogleCalendar, if: :publish_in_google_calendar?
100
+ ```
61
101
 
102
+ TODO: more documentation
62
103
 
63
104
  ## Contributing
64
105
 
data/lib/w_flow/flow.rb CHANGED
@@ -1,18 +1,42 @@
1
1
  module WFlow
2
2
  class Flow
3
- extend Forwardable
4
- def_delegator :@supervisor, :add_to_finalizables, :finalizable!
5
- def_delegator :@supervisor, :add_to_rollbackables, :rollbackable!
6
- def_delegators :@supervisor,
7
- :skip!,
8
- :stop!,
9
- :failure!,
10
- :data,
11
- :success?,
12
- :failure?
13
-
14
- def initialize(supervisor)
15
- @supervisor = supervisor
3
+
4
+ attr_reader :data
5
+
6
+ def initialize(params)
7
+ @data = Data.new(params)
8
+ @failure = false
9
+ @failure_log = []
16
10
  end
11
+
12
+ def skip!
13
+ Supervisor.signal_skip!
14
+ end
15
+
16
+ def stop!
17
+ Supervisor.signal_stop!
18
+ end
19
+
20
+ def failure!(message = nil)
21
+ Supervisor.signal_failure!(message)
22
+ end
23
+
24
+ def success?
25
+ !failure?
26
+ end
27
+
28
+ def failure?
29
+ @failure
30
+ end
31
+
32
+ def log_failure(message)
33
+ @failure_log << message unless message.nil?
34
+ end
35
+
36
+ def set_failure_and_log(message)
37
+ @failure = true
38
+ log_failure(message)
39
+ end
40
+
17
41
  end
18
42
  end
@@ -0,0 +1,12 @@
1
+ module WFlow
2
+ class FlowReport
3
+
4
+ extend Forwardable
5
+ def_delegators :@flow, :data, :success?, :failure?, :failure_log
6
+
7
+ def initialize(flow)
8
+ @flow = flow
9
+ end
10
+
11
+ end
12
+ end
data/lib/w_flow/node.rb CHANGED
@@ -1,14 +1,118 @@
1
1
  module WFlow
2
2
  class Node
3
- def initialize(components, options)
4
- @components = components
5
- @options = options
3
+
4
+ class << self
5
+
6
+ attr_reader :components,
7
+ :if_condition,
8
+ :unless_condition,
9
+ :stop_condition,
10
+ :failure_condition,
11
+ :around_handler
12
+
13
+ def build(components, options)
14
+ 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]
21
+ end
22
+ end
23
+
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
6
70
  end
7
71
 
8
- def execute(supervisor, process)
9
- node_process = NodeProcess.new(@components, @options)
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 = []
10
82
 
11
- node_process.execute(supervisor, process)
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
12
115
  end
116
+
13
117
  end
14
118
  end
@@ -1,27 +1,14 @@
1
1
  module WFlow
2
2
  module Process
3
+
3
4
  def self.included(klass)
4
5
  klass.extend(ClassMethods)
5
6
  end
6
7
 
7
8
  attr_reader :flow
8
9
 
9
- def wflow_execute(supervisor)
10
- supervisor.supervise(self) do |flow|
11
- @flow = flow
12
-
13
- setup
14
-
15
- flow.finalizable!
16
-
17
- self.class.wflow_nodes.each do |node|
18
- node.execute(supervisor, self)
19
- end
20
-
21
- perform
22
-
23
- flow.rollbackable!
24
- end
10
+ def initialize(flow)
11
+ @flow = flow
25
12
  end
26
13
 
27
14
  def setup; end
@@ -62,7 +49,7 @@ module WFlow
62
49
  def execute(*components, &block)
63
50
  options = components.last.is_a?(Hash) ? components.pop : {}
64
51
  components << block if block_given?
65
- wflow_nodes << Node.new(components, options)
52
+ wflow_nodes << Node.build(components, options)
66
53
  end
67
54
 
68
55
  def run(params = {})
@@ -70,12 +57,19 @@ module WFlow
70
57
  raise InvalidArgument, INVALID_RUN_PARAMS
71
58
  end
72
59
 
73
- supervisor = Supervisor.new(params)
60
+ flow = Flow.new(params)
61
+
62
+ ProcessWorker.new(self).run_as_main(flow)
74
63
 
75
- new.wflow_execute(supervisor)
64
+ FlowReport.new(flow)
65
+ rescue ::StandardError => e
66
+ raise if e.is_a?(StandardError) || !Configuration.supress_errors?
76
67
 
77
- supervisor.report
68
+ set_failure_and_log(message: e.message, backtrace: e.backtrace)
69
+
70
+ FlowReport.new(flow)
78
71
  end
72
+
79
73
  end
80
74
  end
81
75
  end
@@ -0,0 +1,86 @@
1
+ module WFlow
2
+ class ProcessWorker
3
+ def initialize(process_class)
4
+ @process_class = process_class
5
+ end
6
+
7
+ def run_as_main(flow)
8
+ setup(flow)
9
+
10
+ process_report = Supervisor.supervise { run }
11
+
12
+ report = Supervisor.supervise do
13
+ if process_report.failed?
14
+ flow.set_failure_and_log(process_report.message)
15
+ rollback
16
+ end
17
+
18
+ finalize
19
+ end
20
+
21
+ raise InvalidOperation, INVALID_OPERATION unless report.success?
22
+ end
23
+
24
+ def run_as_child(flow)
25
+ setup(flow)
26
+
27
+ run
28
+ end
29
+
30
+ def finalize
31
+ @nodes.reverse_each(&:finalize)
32
+
33
+ @process.finalize if @setup_completed
34
+ end
35
+
36
+ def rollback
37
+ @nodes.reverse_each(&:rollback)
38
+
39
+ @process.rollback if @perform_completed
40
+ end
41
+
42
+ protected
43
+
44
+ def run
45
+ @process.setup
46
+ @setup_completed = true
47
+
48
+ @process_class.wflow_nodes.each do |node_class|
49
+ next unless node_class.execute?(@process)
50
+
51
+ node = node_class.new(@process)
52
+
53
+ report = Supervisor.supervise { node.run(@flow) }
54
+
55
+ if report.failed?
56
+ node.rollback
57
+ node.finalize
58
+
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
71
+ end
72
+
73
+ @process.perform
74
+ @perform_completed = true
75
+ end
76
+
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
+ end
86
+ end
@@ -1,118 +1,37 @@
1
1
  module WFlow
2
- class Supervisor
3
- attr_reader :data, :message
2
+ module Supervisor
4
3
 
5
- def initialize(params)
6
- @data = Data.new(params)
7
- @flow = Flow.new(self)
8
- @failure = false
9
- @message = false
10
- @backlog = []
11
- @finalizables = []
12
- @rollbackables = []
13
- end
14
-
15
- def supervise(process, &block)
16
- @backlog << @current_process unless @current_process.nil?
17
- @current_process = process
18
-
19
- if parent_process?
20
- supervise_parent_process(&block)
21
- finalize_processes
22
- else
23
- supervise_child_process(&block)
24
- end
25
- ensure
26
- @current_process = @backlog.pop
27
- end
28
-
29
- def add_to_finalizables
30
- @finalizables.unshift(@current_process)
31
- end
32
-
33
- def add_to_rollbackables
34
- @rollbackables << @current_process
35
- end
36
-
37
- def skip!
38
- throw :wflow_interrupt, :wflow_skip
39
- end
40
-
41
- def stop!
42
- throw :wflow_interrupt, :wflow_stop
43
- end
44
-
45
- def failure!(message = nil)
46
- raise FlowFailure, Marshal.dump(message)
47
- end
4
+ @succeeded = SupervisorReport.new
5
+ @skipped = SupervisorReport.new(:skip)
6
+ @stopped = SupervisorReport.new(:stop)
48
7
 
49
- def success?
50
- !failure?
51
- end
52
-
53
- def failure?
54
- @failure
55
- end
56
-
57
- def report
58
- Report.new(self)
59
- end
60
-
61
- protected
8
+ class << self
62
9
 
63
- def parent_process?
64
- !@current_process.nil? && @backlog.empty?
65
- end
10
+ def supervise
11
+ catch :wflow_interrupt do
12
+ yield
66
13
 
67
- def supervise_parent_process
68
- catch :wflow_interrupt do
69
- yield @flow
14
+ @succeeded
15
+ end
70
16
  end
71
- rescue FlowFailure => e
72
- set_failure(true, Marshal.load(e.message))
73
- rescue ::StandardError => e
74
- raise unless Configuration.supress_errors?
75
17
 
76
- set_failure(true, message: e.message, backtrace: e.backtrace)
77
- end
78
-
79
- def supervise_child_process
80
- interrupt = catch :wflow_interrupt do
81
- yield @flow
18
+ def signal_skip!
19
+ throw :wflow_interrupt, @skipped
82
20
  end
83
21
 
84
- stop! if interrupt == :wflow_stop && rethrow_stop?
85
- rescue FlowFailure => e
86
- if reraise_error?
87
- set_failure(true, Marshal.load(e.message))
88
- raise
89
- else
90
- set_failure(false, Marshal.load(e.message))
22
+ def signal_stop!
23
+ throw :wflow_interrupt, @stopped
91
24
  end
92
- end
93
25
 
94
- def finalize_processes
95
- interrupt = catch :wflow_interrupt do
96
- @rollbackables.each(&:rollback) if failure?
97
- @finalizables.each(&:finalize)
26
+ def signal_failure!(message = nil)
27
+ throw :wflow_interrupt, SupervisorReport.new(:failure, message)
98
28
  end
99
29
 
100
- raise FlowFailure if [:wflow_stop, :wflow_skip].include?(interrupt)
101
- rescue FlowFailure
102
- raise InvalidOperation, INVALID_OPERATION
103
- end
104
-
105
- def set_failure(state, message)
106
- @failure = state
107
- @message << message unless message.nil?
108
- end
30
+ def resignal!(report)
31
+ throw :wflow_interrupt, report
32
+ end
109
33
 
110
- def rethrow_stop?
111
- !@current_process.is_a?(NodeProcess) || !@current_process.cancel_stop?
112
34
  end
113
35
 
114
- def reraise_error?
115
- !@current_process.is_a?(NodeProcess) || !@current_process.cancel_failure?
116
- end
117
36
  end
118
37
  end
@@ -0,0 +1,28 @@
1
+ module WFlow
2
+ class SupervisorReport
3
+
4
+ attr_reader :key, :message
5
+
6
+ def initialize(key = :success, message = nil)
7
+ @key = key
8
+ @message = message
9
+ end
10
+
11
+ def success?
12
+ @key == :success
13
+ end
14
+
15
+ def skipped?
16
+ @key == :skip
17
+ end
18
+
19
+ def stopped?
20
+ @key == :stop
21
+ end
22
+
23
+ def failed?
24
+ @key == :failure
25
+ end
26
+
27
+ end
28
+ end
@@ -1,3 +1,3 @@
1
1
  module WFlow
2
- VERSION = "0.10.0"
2
+ VERSION = "0.11.0"
3
3
  end
data/lib/w_flow.rb CHANGED
@@ -1,14 +1,16 @@
1
1
  require "w_flow/version"
2
2
  require "w_flow/configuration"
3
3
  require "w_flow/data"
4
- require "w_flow/report"
4
+ require "w_flow/supervisor_report"
5
5
  require "w_flow/supervisor"
6
6
  require "w_flow/flow"
7
- require "w_flow/node_process"
7
+ require "w_flow/flow_report"
8
8
  require "w_flow/node"
9
9
  require "w_flow/process"
10
+ require "w_flow/process_worker"
10
11
 
11
12
  module WFlow
13
+
12
14
  # WFlow errors
13
15
  class StandardError < ::StandardError; end
14
16
 
@@ -20,4 +22,5 @@ module WFlow
20
22
  INVALID_RUN_PARAMS = "run must be invoked without arguments or an Hash"
21
23
  UNKNOWN_EXPRESSION = "can't evaluate expression"
22
24
  INVALID_OPERATION = "skip!, stop! or failure! can't be invoked during finalize/rollback"
25
+
23
26
  end
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.10.0
4
+ version: 0.11.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-05-26 00:00:00.000000000 Z
11
+ date: 2015-06-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -98,11 +98,12 @@ files:
98
98
  - lib/w_flow/configuration.rb
99
99
  - lib/w_flow/data.rb
100
100
  - lib/w_flow/flow.rb
101
+ - lib/w_flow/flow_report.rb
101
102
  - lib/w_flow/node.rb
102
- - lib/w_flow/node_process.rb
103
103
  - lib/w_flow/process.rb
104
- - lib/w_flow/report.rb
104
+ - lib/w_flow/process_worker.rb
105
105
  - lib/w_flow/supervisor.rb
106
+ - lib/w_flow/supervisor_report.rb
106
107
  - lib/w_flow/version.rb
107
108
  - w_flow.gemspec
108
109
  homepage: https://github.com/junhanamaki/w_flow
@@ -125,7 +126,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
125
126
  version: '0'
126
127
  requirements: []
127
128
  rubyforge_project:
128
- rubygems_version: 2.4.6
129
+ rubygems_version: 2.4.7
129
130
  signing_key:
130
131
  specification_version: 4
131
132
  summary: A workflow composer based on Single Responsability Principle to help organize
@@ -1,58 +0,0 @@
1
- module WFlow
2
- class NodeProcess
3
- def initialize(components, options)
4
- @components = components
5
- @around_option = options[:around]
6
- @if_option = options[:if]
7
- @unless_option = options[:unless]
8
- @stop_option = options[:stop]
9
- @failure_option = options[:failure]
10
- end
11
-
12
- def execute(supervisor, process)
13
- @supervisor = supervisor
14
- @process = process
15
-
16
- if execute_node?
17
- supervisor.supervise(self) do
18
- if @around_option.nil?
19
- execute_components
20
- else
21
- process_eval(@around_option, method(:execute_components))
22
- end
23
- end
24
- end
25
- end
26
-
27
- def cancel_stop?
28
- !@stop_option.nil? && !process_eval(@stop_option)
29
- end
30
-
31
- def cancel_failure?
32
- !@failure_option.nil? && !process_eval(@failure_option)
33
- end
34
-
35
- protected
36
-
37
- def execute_node?
38
- (@if_option.nil? || process_eval(@if_option)) &&
39
- (@unless_option.nil? || !process_eval(@unless_option))
40
- end
41
-
42
- def execute_components
43
- @components.each { |component| process_eval(component) }
44
- end
45
-
46
- def process_eval(object, *args)
47
- if object.is_a?(String) || object.is_a?(Symbol)
48
- @process.send(object.to_s, *args)
49
- elsif object.is_a?(Proc)
50
- @process.instance_exec(*args, &object)
51
- elsif object <= Process
52
- object.new.wflow_execute(@supervisor, *args)
53
- else
54
- raise InvalidArguments, UNKNOWN_EXPRESSION
55
- end
56
- end
57
- end
58
- end
data/lib/w_flow/report.rb DELETED
@@ -1,10 +0,0 @@
1
- module WFlow
2
- class Report
3
- extend Forwardable
4
- def_delegators :@supervisor, :data, :message, :success?, :failure?
5
-
6
- def initialize(supervisor)
7
- @supervisor = supervisor
8
- end
9
- end
10
- end