w_flow 0.10.0 → 0.11.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: 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