w_flow 0.1.0 → 0.10.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: 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