w_flow 0.1.0 → 0.10.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: ea684020b144fcbcb694e2c1df80e7d9244cfd35
4
- data.tar.gz: abee114f93512251d671e246fd8a8fdc34dce351
3
+ metadata.gz: 542302c862a2fd32f275fdcc7ef118fe28d782df
4
+ data.tar.gz: cdffac74ea395e3869a48dd1d92e6f0381fba377
5
5
  SHA512:
6
- metadata.gz: 8545ebdf9cd7f084c2a377229807575331e591e4ca0180505657b10f1e6568c20dd9416768f4d46456c33909220b3a43d3573051f91c3f827d8c5ea399dbd9d3
7
- data.tar.gz: 0de1290ab22238663fe1ccc87778ede829b15bb6903dca93218ed01e08caac161f8b579d80fc8f18b1943be5b458e524139fbc4d160e547e633cb4c4e62339ec
6
+ metadata.gz: b1f200b2a3de539ea4328ddc6be29cbf988b784b07c5ca5cd4c1b6bc2622d47613296a66c1d2279952be962b8002a6ae05db9fd0af9ad23b654ee57cbc1c2ece
7
+ data.tar.gz: ded155f14d82e06a0c437d9b6ef8f273c5b958b3921604302dd146588605e15178ebef0b22a7dda8cfab98007c408a26cd2fe75b601bf34edc00ef07ad8d39ef
data/.gitignore CHANGED
@@ -13,3 +13,5 @@
13
13
  .ruby-version
14
14
 
15
15
  *.gem
16
+
17
+ dump.rdb
data/.travis.yml CHANGED
@@ -1,5 +1,6 @@
1
1
  language: ruby
2
2
  rvm:
3
+ - 2.2.1
3
4
  - 2.2.0
4
5
  - 2.1.1
5
6
  - 2.0.0
data/README.md CHANGED
@@ -1,16 +1,26 @@
1
1
  # WFlow
2
2
 
3
- [![Gem Version](https://badge.fury.io/rb/rekiq.svg)](http://badge.fury.io/rb/rekiq)
4
- [![Build Status](https://travis-ci.org/junhanamaki/rekiq.svg?branch=master)](https://travis-ci.org/junhanamaki/rekiq)
5
- [![Code Climate](https://codeclimate.com/github/junhanamaki/rekiq.png)](https://codeclimate.com/github/junhanamaki/rekiq)
6
- [![Test Coverage](https://codeclimate.com/github/junhanamaki/rekiq/coverage.png)](https://codeclimate.com/github/junhanamaki/rekiq)
7
- [![Dependency Status](https://gemnasium.com/junhanamaki/rekiq.svg)](https://gemnasium.com/junhanamaki/rekiq)
3
+ [![Gem Version](https://badge.fury.io/rb/w_flow.svg)](http://badge.fury.io/rb/w_flow)
4
+ [![Build Status](https://travis-ci.org/junhanamaki/w_flow.svg?branch=master)](https://travis-ci.org/junhanamaki/w_flow)
5
+ [![Code Climate](https://codeclimate.com/github/junhanamaki/w_flow.png)](https://codeclimate.com/github/junhanamaki/w_flow)
6
+ [![Test Coverage](https://codeclimate.com/github/junhanamaki/w_flow/coverage.png)](https://codeclimate.com/github/junhanamaki/w_flow)
7
+ [![Dependency Status](https://gemnasium.com/junhanamaki/w_flow.svg)](https://gemnasium.com/junhanamaki/w_flow)
8
+
9
+ WFlow aims to help on designing workflows based on [Single
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.
13
+
14
+ Word of appreciation for [usecasing](https://github.com/tdantas/usecasing),
15
+ [interactor](https://github.com/collectiveidea/interactor) and
16
+ [rest_my_case](https://github.com/goncalvesjoao/rest_my_case) that served as
17
+ inspiration for this gem.
8
18
 
9
19
  ## Dependencies
10
20
 
11
21
  Tested with:
12
22
 
13
- * ruby 2.2.0, 2.1.1, 2.0.0
23
+ * ruby 2.2.1, 2.2.0, 2.1.1, 2.0.0
14
24
 
15
25
  ## Installation
16
26
 
@@ -30,11 +40,29 @@ Or install it yourself as:
30
40
 
31
41
  ## Usage
32
42
 
33
- TODO: Write usage instructions here
43
+ On its most simplest form a task (in WFlow called a Process) is something like this:
44
+
45
+ ```ruby
46
+ class SaveUser
47
+ include WFlow::Process
48
+
49
+ def perform
50
+ # arguments passed to run will be under flow.data
51
+ flow.data.user.save
52
+ end
53
+ end
54
+
55
+ # run process, it will return a report object
56
+ report = SaveUser.run(user: current_user)
57
+
58
+ report.data.success?
59
+ ```
60
+
61
+
34
62
 
35
63
  ## Contributing
36
64
 
37
- 1. Fork it ( https://github.com/[my-github-username]/w_flow/fork )
65
+ 1. Fork it ( https://github.com/junhanamaki/w_flow/fork )
38
66
  2. Create your feature branch (`git checkout -b my-new-feature`)
39
67
  3. Commit your changes (`git commit -am 'Add some feature'`)
40
68
  4. Push to the branch (`git push origin my-new-feature`)
@@ -0,0 +1,29 @@
1
+ module WFlow
2
+ class Configuration
3
+ attr_reader :supress_errors
4
+
5
+ class << self
6
+ def config
7
+ yield configuration
8
+ end
9
+
10
+ def supress_errors?
11
+ configuration.supress_errors
12
+ end
13
+
14
+ def configuration
15
+ @configuration ||= new
16
+ end
17
+
18
+ def reset_configuration
19
+ @configuration = new
20
+ end
21
+ end
22
+
23
+ protected
24
+
25
+ def initialize
26
+ @supress_errors = false
27
+ end
28
+ end
29
+ end
data/lib/w_flow/flow.rb CHANGED
@@ -1,32 +1,18 @@
1
1
  module WFlow
2
2
  class Flow
3
- attr_reader :data, :failure_message
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?
4
13
 
5
- def initialize(data)
6
- @data = Data.new(data)
7
- @failure = false
8
- @failure_message = nil
9
- end
10
-
11
- def success?
12
- !failure?
13
- end
14
-
15
- def failure?
16
- @failure
17
- end
18
-
19
- def failure!(message = nil)
20
- @failure = true
21
- @failure_message = message
22
- end
23
-
24
- def stop!
25
- throw :stop
26
- end
27
-
28
- def skip!
29
- throw :skip
14
+ def initialize(supervisor)
15
+ @supervisor = supervisor
30
16
  end
31
17
  end
32
18
  end
@@ -0,0 +1,14 @@
1
+ module WFlow
2
+ class Node
3
+ def initialize(components, options)
4
+ @components = components
5
+ @options = options
6
+ end
7
+
8
+ def execute(supervisor, process)
9
+ node_process = NodeProcess.new(@components, @options)
10
+
11
+ node_process.execute(supervisor, process)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,58 @@
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
@@ -1,58 +1,80 @@
1
1
  module WFlow
2
2
  module Process
3
+ def self.included(klass)
4
+ klass.extend(ClassMethods)
5
+ end
6
+
3
7
  attr_reader :flow
4
8
 
5
- def initialize(flow)
6
- @flow = flow
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
7
25
  end
8
26
 
9
27
  def setup; end
10
28
  def perform; end
11
29
  def rollback; end
12
- def final; end
13
-
14
- def self.included(klass)
15
- klass.extend(ClassMethods)
30
+ def finalize; end
16
31
 
17
- klass.instance_variable_set('@process_nodes', [])
18
- end
32
+ protected
19
33
 
20
34
  module ClassMethods
35
+ attr_reader :wflow_nodes
36
+
37
+ def self.extended(klass)
38
+ klass.instance_variable_set('@wflow_nodes', [])
39
+ end
40
+
41
+ def inherited(klass)
42
+ klass.instance_variable_set('@wflow_nodes', wflow_nodes.dup)
43
+ end
44
+
21
45
  def data_accessor(*keys)
22
- data_writer(keys)
23
- data_reader(keys)
46
+ data_writer(*keys)
47
+ data_reader(*keys)
24
48
  end
25
49
 
26
50
  def data_writer(*keys)
27
51
  keys.each do |key|
28
- define_method "#{key}=" do |val|
29
- flow.data[key] = val
30
- end
52
+ define_method("#{key}=") { |val| flow.data.send("#{key}=", val) }
31
53
  end
32
54
  end
33
55
 
34
56
  def data_reader(*keys)
35
57
  keys.each do |key|
36
- define_method key do
37
- flow.data[key]
38
- end
58
+ define_method(key) { flow.data.send(key.to_s) }
39
59
  end
40
60
  end
41
61
 
42
- def execute(*args)
43
- options = args.pop if args.last.is_a?(Hash)
44
- args << Proc.new if block_given?
45
-
46
- @process_nodes << ProcessNode.new(args, options)
62
+ def execute(*components, &block)
63
+ options = components.last.is_a?(Hash) ? components.pop : {}
64
+ components << block if block_given?
65
+ wflow_nodes << Node.new(components, options)
47
66
  end
48
67
 
49
68
  def run(params = {})
50
- unless params.is_a?(Hash)
51
- raise InvalidArgument, 'run must be invoked with an Hash'
69
+ unless params.nil? || params.is_a?(Hash)
70
+ raise InvalidArgument, INVALID_RUN_PARAMS
52
71
  end
53
- end
54
72
 
55
- def run_as_dependency(flow)
73
+ supervisor = Supervisor.new(params)
74
+
75
+ new.wflow_execute(supervisor)
76
+
77
+ supervisor.report
56
78
  end
57
79
  end
58
80
  end
@@ -0,0 +1,10 @@
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
@@ -0,0 +1,118 @@
1
+ module WFlow
2
+ class Supervisor
3
+ attr_reader :data, :message
4
+
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
48
+
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
62
+
63
+ def parent_process?
64
+ !@current_process.nil? && @backlog.empty?
65
+ end
66
+
67
+ def supervise_parent_process
68
+ catch :wflow_interrupt do
69
+ yield @flow
70
+ end
71
+ rescue FlowFailure => e
72
+ set_failure(true, Marshal.load(e.message))
73
+ rescue ::StandardError => e
74
+ raise unless Configuration.supress_errors?
75
+
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
82
+ end
83
+
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))
91
+ end
92
+ end
93
+
94
+ def finalize_processes
95
+ interrupt = catch :wflow_interrupt do
96
+ @rollbackables.each(&:rollback) if failure?
97
+ @finalizables.each(&:finalize)
98
+ end
99
+
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
109
+
110
+ def rethrow_stop?
111
+ !@current_process.is_a?(NodeProcess) || !@current_process.cancel_stop?
112
+ end
113
+
114
+ def reraise_error?
115
+ !@current_process.is_a?(NodeProcess) || !@current_process.cancel_failure?
116
+ end
117
+ end
118
+ end
@@ -1,3 +1,3 @@
1
1
  module WFlow
2
- VERSION = "0.1.0"
2
+ VERSION = "0.10.0"
3
3
  end
data/lib/w_flow.rb CHANGED
@@ -1,9 +1,23 @@
1
1
  require "w_flow/version"
2
- require 'w_flow/exceptions'
3
- require 'w_flow/data'
4
- require 'w_flow/flow'
5
- require 'w_flow/process_node'
6
- require 'w_flow/process'
2
+ require "w_flow/configuration"
3
+ require "w_flow/data"
4
+ require "w_flow/report"
5
+ require "w_flow/supervisor"
6
+ require "w_flow/flow"
7
+ require "w_flow/node_process"
8
+ require "w_flow/node"
9
+ require "w_flow/process"
7
10
 
8
11
  module WFlow
12
+ # WFlow errors
13
+ class StandardError < ::StandardError; end
14
+
15
+ class InvalidArguments < StandardError; end
16
+ class FlowFailure < StandardError; end
17
+ class InvalidOperation < StandardError; end
18
+
19
+ # WFlow message constants
20
+ INVALID_RUN_PARAMS = "run must be invoked without arguments or an Hash"
21
+ UNKNOWN_EXPRESSION = "can't evaluate expression"
22
+ INVALID_OPERATION = "skip!, stop! or failure! can't be invoked during finalize/rollback"
9
23
  end
data/w_flow.gemspec CHANGED
@@ -12,13 +12,15 @@ Gem::Specification.new do |spec|
12
12
  spec.summary = %q{A workflow composer based on Single Responsability Principle to help organize projects}
13
13
  spec.description = %q{WFlow is a workflow composer that helps in organizing projects by splitting logic into reusable modules (processes)}
14
14
  spec.homepage = "https://github.com/junhanamaki/w_flow"
15
- spec.license = 'MIT'
16
15
 
17
16
  spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
17
  spec.bindir = "exe"
19
18
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
19
  spec.require_paths = ["lib"]
21
20
 
21
+ spec.license = 'MIT'
22
+ spec.required_ruby_version = '>= 2.0.0'
23
+
22
24
  spec.add_development_dependency "bundler", "~> 1.9"
23
25
  spec.add_development_dependency "rspec", '~> 3.2'
24
26
  spec.add_development_dependency "pry", '~> 0.10'
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.1.0
4
+ version: 0.10.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-10 00:00:00.000000000 Z
11
+ date: 2015-05-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -95,11 +95,14 @@ files:
95
95
  - README.md
96
96
  - Rakefile
97
97
  - lib/w_flow.rb
98
+ - lib/w_flow/configuration.rb
98
99
  - lib/w_flow/data.rb
99
- - lib/w_flow/exceptions.rb
100
100
  - lib/w_flow/flow.rb
101
+ - lib/w_flow/node.rb
102
+ - lib/w_flow/node_process.rb
101
103
  - lib/w_flow/process.rb
102
- - lib/w_flow/process_node.rb
104
+ - lib/w_flow/report.rb
105
+ - lib/w_flow/supervisor.rb
103
106
  - lib/w_flow/version.rb
104
107
  - w_flow.gemspec
105
108
  homepage: https://github.com/junhanamaki/w_flow
@@ -114,7 +117,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
114
117
  requirements:
115
118
  - - ">="
116
119
  - !ruby/object:Gem::Version
117
- version: '0'
120
+ version: 2.0.0
118
121
  required_rubygems_version: !ruby/object:Gem::Requirement
119
122
  requirements:
120
123
  - - ">="
@@ -122,7 +125,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
122
125
  version: '0'
123
126
  requirements: []
124
127
  rubyforge_project:
125
- rubygems_version: 2.4.5
128
+ rubygems_version: 2.4.6
126
129
  signing_key:
127
130
  specification_version: 4
128
131
  summary: A workflow composer based on Single Responsability Principle to help organize
@@ -1,7 +0,0 @@
1
- module WFlow
2
- class StandardError < ::StandardError; end
3
-
4
- class InvalidArgument < StandardError; end
5
- class FlowFailure < StandardError; end
6
- class UnknownTask < StandardError; end
7
- end
@@ -1,45 +0,0 @@
1
- module WFlow
2
- class ProcessNode
3
- def initialize(tasks, options)
4
- @tasks = tasks
5
- @options = options
6
- end
7
-
8
- def execute(owner_process)
9
- return unless execute_node?(owner_process)
10
-
11
- @tasks.each do |task|
12
- if task.is_a?(String) || task.is_a?(Symbol)
13
- owner_process.instance_eval(task.to_s)
14
- elsif task.is_a?(Proc)
15
- owner_process.instance_eval(&task)
16
- elsif task.is_a?(Process)
17
- task.run(owner_process.flow)
18
- else
19
- raise UnknownTask, "don't know how to execute task #{task}"
20
- end
21
- end
22
- end
23
-
24
- protected
25
-
26
- def execute_node?(owner_process)
27
- if_condition_allows_execution?(owner_process) &&
28
- unless_condition_allows_execution?(owner_process)
29
- end
30
-
31
- def if_condition_allows_execution?(owner_process)
32
- @options[:if].nil? || eval_condition(@options[:if], owner_process)
33
- end
34
-
35
- def unless_condition_allows_execution?(owner_process)
36
- @options[:unless].nil? || !eval_condition(@options[:unless], owner_process)
37
- end
38
-
39
- def eval_condition?(condition, owner_process)
40
- ((condition.is_a?(String) || condition.is_a?(Symbol)) &&
41
- owner_process.instance_eval(condition.to_s)) ||
42
- (condition.is_a?(Proc) && owner_process.instance_eval(&condition))
43
- end
44
- end
45
- end