nxt_pipeline 0.2.0 → 0.2.1

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
  SHA256:
3
- metadata.gz: 990043f1b6a4cb543090543a45aa103f915db67ad30a682fcf459d4fc582764d
4
- data.tar.gz: e3d2af9e6492d930fe196a886d4c53314055fbd4534110d2257ddd5687157bf1
3
+ metadata.gz: 3b2eac461b1a77771494ee996ece40aece6ec068070fdb282f68192119976d93
4
+ data.tar.gz: 339d7b6b27ce3b85a95d5a80558d2d4e217e0ce8b1f8965eb573f534caf6bb18
5
5
  SHA512:
6
- metadata.gz: de30962f57a74e8e3f8f1ef541395bbfac2ed424a58c1638e2b6e83f62fe40529e2d2df9bb6f1b3641894e64f57bc6b02748bef2af018d9cc9262028f7e1b105
7
- data.tar.gz: b0170c1be44573b6f9416e01582e7673cc20fb48fccab4aa2492e516f4fde59da92b8c248e157922bdd293c602ce94de209c9b81c81e549b1c0a7ceaf09b276a
6
+ metadata.gz: fe288cb402d9f354dea2f5d2cf47640f29757b74ed90e187beb74c7fe8fa721177718e6eb052a398c54e97561a2b17dbbe7d93132231f2b285b0b93e3e3fe160
7
+ data.tar.gz: c0837560b35952dc0744df9f75ae61993910f57c97c777ff3fd28921910b080fcf8cf68f2dc6308a790c9e2bfb9f63a530229a609b2711950e6b81e5dc6eda09
data/.gitignore CHANGED
@@ -6,6 +6,7 @@
6
6
  /pkg/
7
7
  /spec/reports/
8
8
  /tmp/
9
+ .idea/
9
10
 
10
11
  # rspec failure tracking
11
12
  .rspec_status
data/Gemfile.lock CHANGED
@@ -1,19 +1,19 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- nxt_pipeline (0.2.0)
4
+ nxt_pipeline (0.2.1)
5
5
  activesupport
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
- activesupport (5.2.2)
10
+ activesupport (5.2.2.1)
11
11
  concurrent-ruby (~> 1.0, >= 1.0.2)
12
12
  i18n (>= 0.7, < 2)
13
13
  minitest (~> 5.1)
14
14
  tzinfo (~> 1.1)
15
15
  coderay (1.1.2)
16
- concurrent-ruby (1.1.4)
16
+ concurrent-ruby (1.1.5)
17
17
  diff-lcs (1.3)
18
18
  ffi (1.10.0)
19
19
  formatador (0.2.5)
@@ -31,7 +31,7 @@ GEM
31
31
  guard (~> 2.1)
32
32
  guard-compat (~> 1.1)
33
33
  rspec (>= 2.99.0, < 4.0)
34
- i18n (1.5.3)
34
+ i18n (1.6.0)
35
35
  concurrent-ruby (~> 1.0)
36
36
  listen (3.1.5)
37
37
  rb-fsevent (~> 0.9, >= 0.9.4)
@@ -80,6 +80,7 @@ DEPENDENCIES
80
80
  bundler (~> 1.17)
81
81
  guard-rspec
82
82
  nxt_pipeline!
83
+ pry
83
84
  rake (~> 10.0)
84
85
  rspec (~> 3.0)
85
86
  rspec_junit_formatter
data/README.md CHANGED
@@ -22,93 +22,114 @@ Or install it yourself as:
22
22
 
23
23
  ## Usage
24
24
 
25
- Define a pipeline by defining class inheriting from `NxtPipeline::Pipeline` as shown above. The following examples shows a pipeline which takes an array of strings and passes it through multiple steps.
25
+ ### Constructors
26
+
27
+ First you probably want to configure a pipeline so that it can execute your steps.
28
+ Therefore you want to define constructors for your steps. Constructors take a name
29
+ as the first argument and step options as the second. All step options are being exposed
30
+ by the step yielded to the constructor.
26
31
 
27
32
  ```ruby
28
- class MyPipeline < NxtPipeline::Pipeline
29
- pipe_attr :words
33
+ pipeline = NxtPipeline::Pipeline.new do |p|
34
+ # Add a named constructor that will be used to execute your steps later
35
+ # All options that you pass in your step will be available through accessors in your constructor
36
+ p.constructor(:service, default: true) do |step, arg|
37
+ step.service_class.new(options: arg).call
38
+ end
30
39
 
31
- step UppercaseSegment
32
- step SortSegment
40
+ p.constructor(:job) do |step, arg|
41
+ step.job_class.perform_later(*arg) && arg
42
+ end
43
+ end
44
+
45
+ # Once a pipeline was created you can still configure it
46
+ pipeline.constructor(:call) do |step, arg|
47
+ step.caller.new(arg).call
48
+ end
49
+
50
+ # same with block syntax
51
+ # You can use this to split up execution from configuration
52
+ pipeline.configure do |p|
53
+ p.constructor(:call) do |step, arg|
54
+ step.caller.new(arg).call
55
+ end
33
56
  end
34
57
  ```
35
58
 
36
- The steps are classes themselves which inherit from `NxtPipeline::Step` and have to implement a `#pipe_through` method.
59
+ ### Defining steps
60
+
61
+ Once your pipeline knows how to execute your steps you can add those.
37
62
 
38
63
  ```ruby
39
- class UppercaseSegment < NxtPipeline::Step
40
- def pipe_through
41
- words.map(&:uppercase)
42
- end
64
+ pipeline.step :service, service_class: MyServiceClass, to_s: 'First step'
65
+ pipeline.step service_class: MyOtherServiceClass, to_s: 'Second step'
66
+ # ^ Since service is the default step you don't have to specify it the step type each time
67
+ pipeline.step :job, job_class: MyJobClass # to_s is optional
68
+ pipeline.step :job, job_class: MyOtherJobClass
69
+
70
+ pipeline.step :step_name_for_better_log do |_, arg|
71
+ # ...
43
72
  end
44
73
 
45
- class SortSegment < NxtPipeline::Step
46
- def pipe_through
47
- words.sort
48
- end
74
+ pipeline.step to_s: 'This is the same as above' do |step, arg|
75
+ # ... step.to_s => 'This is the same as above'
49
76
  end
50
77
  ```
51
78
 
52
- You can access the pipeline attribute defined by `pipe_attr` in the pipeline class by a reader method which is automatically defined by nxt_pipeline. Don't forget to return the pipeline attribute so that subsequent steps in the pipeline can take it up!
79
+ You can also define inline steps, meaning the block will be executed
53
80
 
54
- Here's how our little example pipeline behaves like in action:
81
+ ### Execution
55
82
 
56
- ```
57
- MyPipeline.new(words: %w[Ruby is awesome]).run
58
- # => ["AWESOME", "IS", "RUBY"]
59
- ```
83
+ You can then execute the steps with:
60
84
 
61
- ### Callbacks
85
+ ```ruby
86
+ pipeline.execute('initial argument')
62
87
 
63
- You can define callbacks that are automatically invoked for each step in the pipeline.
88
+ # Or run the steps directly using block syntax
64
89
 
65
- ```ruby
66
- class MyPipeline < NxtPipeline::Pipeline
67
- before_each_step do
68
- # Code run before each step.
69
- end
70
-
71
- after_each_step do
72
- # Code run after each step
73
- end
74
-
75
- around_each_step do |pipeline, segment|
76
- # Code run before each step
77
- segment.call
78
- # Code run after each step
79
- end
90
+ pipeline.execute do |p|
91
+ p.step :service, service_class: MyServiceClass, to_s: 'First step'
92
+ p.step :service, service_class: MyOtherServiceClass, to_s: 'Second step'
93
+ p.step :job, job_class: MyJobClass # to_s is optional
94
+ p.step :job, job_class: MyOtherJobClass
80
95
  end
81
- ```
82
-
83
- The callback methods are syntactic sugar for [ActiveSupport Callbacks](https://api.rubyonrails.org/classes/ActiveSupport/Callbacks.html), so the same rules apply regarding order or execution.
84
96
 
85
- ### Error handling
97
+ ```
86
98
 
87
- When everything works smoothly the pipeline calls one step after another, passing through the pipeline attribute and returning it as it gets it from the last step. However, you might want to define behavior the pipeline should perform in case one of the steps raises an error.
99
+ You can also directly execute a pipeline with:
88
100
 
89
101
  ```ruby
90
- class MyPipeline < NxtPipeline::Pipeline
91
- rescue_errors StandardError do |error, failed_step|
92
- puts "Step #{failed_step} failed with #{error.class}: #{error.message}"
102
+ NxtPipeline::Pipeline.execute('initial argument') do |p|
103
+ p.step do |_, arg|
104
+ arg.upcase
93
105
  end
94
106
  end
95
- ```
107
+ ```
96
108
 
97
- Keep in mind though that `rescue_errors` will reraise the error it caught. When you rescue this error in your application, the pipeline remembers if and how it failed.
109
+ ### Error callbacks
98
110
 
99
- ```ruby
100
- pipeline = MyPipeline.new(...)
111
+ Apart from defining constructors and steps you can also define error callbacks.
101
112
 
102
- begin
103
- pipeline.run
104
- rescue => e
105
- pipeline.failed?
106
- #=> true
113
+ ```ruby
114
+ NxtPipeline::Pipeline.new do |p|
115
+ p.step do |_, arg|
116
+ arg.upcase
117
+ end
118
+
119
+ p.on_error MyCustomError do |step, arg, error|
120
+ # First matching error callback will be executed!
121
+ end
122
+
123
+ p.on_errors ArgumentError, KeyError do |step, arg, error|
124
+ # First matching error callback will be executed!
125
+ end
107
126
 
108
- pipeline.failed_step
109
- #=> :underscored_class_name_of_failed_step
127
+ p.on_errors do |step, arg, error|
128
+ # This will match all errors inheriting from StandardError
129
+ end
110
130
  end
111
- ```
131
+ ```
132
+
112
133
 
113
134
  ## Development
114
135
 
data/lib/nxt_pipeline.rb CHANGED
@@ -1,10 +1,7 @@
1
- require 'active_support'
2
- require 'active_support/core_ext'
3
-
4
1
  require 'nxt_pipeline/version'
5
- require 'nxt_pipeline/step'
6
2
  require 'nxt_pipeline/pipeline'
3
+ require 'nxt_pipeline/step'
4
+ require 'nxt_pipeline/error_callback'
7
5
 
8
6
  module NxtPipeline
9
- class Error < StandardError; end
10
7
  end
@@ -0,0 +1,18 @@
1
+ module NxtPipeline
2
+ class ErrorCallback
3
+ def initialize(errors, callback)
4
+ @errors = errors
5
+ @callback = callback
6
+ end
7
+
8
+ attr_accessor :errors, :callback
9
+
10
+ def applies_to_error?(error)
11
+ (error.class.ancestors & errors).any?
12
+ end
13
+
14
+ def call(step, arg, error)
15
+ callback.call(step, arg, error)
16
+ end
17
+ end
18
+ end
@@ -1,81 +1,97 @@
1
1
  module NxtPipeline
2
2
  class Pipeline
3
- include ActiveSupport::Callbacks
4
- define_callbacks :each_step_pipe_through
5
-
6
- attr_reader :failed_step
3
+ def self.execute(opts, &block)
4
+ new(&block).execute(opts)
5
+ end
6
+
7
+ def initialize(&block)
8
+ @steps = []
9
+ @error_callbacks = []
10
+ @log = {}
11
+ @current_step = nil
12
+ @default_constructor = nil
13
+ @registry = {}
14
+ configure(&block) if block_given?
15
+ end
16
+
17
+ attr_reader :log
18
+
19
+ # register steps with name and block
20
+ def constructor(name, **opts, &constructor)
21
+ name = name.to_sym
22
+ raise StandardError, "Already registered step :#{name}" if registry[name]
23
+
24
+ registry[name] = constructor
7
25
 
8
- def initialize(*attrs)
9
- extract_pipe_attr_from_init_params(*attrs)
26
+ return unless opts.fetch(:default, false)
27
+ default_constructor ? (raise ArgumentError, 'Default step already defined') : self.default_constructor = constructor
10
28
  end
11
29
 
12
- def run
13
- self.class.steps.reduce(pipe_attr) do |transformed_pipe_attr, step|
14
- run_callbacks :each_step_pipe_through do
15
- step[self.class.pipe_attr_name].new(self.class.pipe_attr_name => transformed_pipe_attr).pipe_through
16
- rescue => error
17
- handle_segment_burst(error, step)
30
+ def step(type = nil, **opts, &block)
31
+ constructor = if block_given?
32
+ # make first argument the to_s of step if given
33
+ opts.merge!(to_s: type) if type && !opts.key?(:to_s)
34
+ block
35
+ else
36
+ if type
37
+ registry.fetch(type) { raise KeyError, "No step :#{type} registered" }
38
+ else
39
+ default_constructor || (raise StandardError, 'No default step registered')
18
40
  end
19
41
  end
20
- end
21
42
 
22
- def failed?
23
- failed_step.present?
43
+ steps << Step.new(constructor, **opts)
24
44
  end
25
45
 
26
- class << self
27
- def pipe_attr(name)
28
- @pipe_attr_name = name
46
+ def execute(arg, &block)
47
+ reset_log
48
+ configure(&block) if block_given?
49
+ steps.inject(arg) do |argument, step|
50
+ execute_step(step, argument)
29
51
  end
52
+ end
30
53
 
31
- def step(name)
32
- self.steps << name
33
- end
54
+ def on_errors(*errors, &callback)
55
+ error_callbacks << ErrorCallback.new(errors, callback)
56
+ end
34
57
 
35
- def rescue_errors(*errors, &block)
36
- @rescueable_errors = errors
37
- self.rescueable_block = block
38
- end
39
-
40
- def before_each_step(*filters, &block)
41
- set_callback :each_step_pipe_through, :before, *filters, &block
42
- end
43
-
44
- def after_each_step(*filters, &block)
45
- set_callback :each_step_pipe_through, :after, *filters, &block
46
- end
47
-
48
- def around_each_step(*filters, &block)
49
- set_callback :each_step_pipe_through, :around, *filters, &block
50
- end
51
-
52
- attr_reader :pipe_attr_name
53
- attr_accessor :rescueable_block
54
-
55
- def steps
56
- @steps ||= []
57
- end
58
-
59
- def rescueable_errors
60
- @rescueable_errors ||= []
61
- end
58
+ alias :on_error :on_errors
59
+
60
+ def configure(&block)
61
+ block.call(self)
62
62
  end
63
63
 
64
64
  private
65
65
 
66
- attr_reader :pipe_attr
66
+ attr_reader :error_callbacks, :registry
67
+ attr_accessor :steps, :current_step, :default_constructor
68
+ attr_writer :log
67
69
 
68
- def extract_pipe_attr_from_init_params(*attrs)
69
- raise ArgumentError, 'You need to pass a keyword param as argument to #new' unless attrs.first.is_a?(Hash)
70
- @pipe_attr = attrs.first.fetch(self.class.pipe_attr_name)
71
- end
70
+ def execute_step(step, arg)
71
+ self.current_step = step.to_s
72
+ result = step.execute(arg)
72
73
 
73
- def handle_segment_burst(error, step)
74
- @failed_step = step.name.split('::').last.underscore
74
+ if result # step was successful
75
+ log[current_step] = { status: :success }
76
+ return result
77
+ else # step was not successful if nil or false
78
+ log[current_step] = { status: :skipped }
79
+ return arg
80
+ end
81
+ rescue StandardError => error
82
+ log[current_step] = { status: :failed, reason: "#{error.class}: #{error.message}" }
83
+ callback = find_error_callback(error)
75
84
 
76
- self.class.rescueable_block.call(error, failed_step) if error.class.in?(self.class.rescueable_errors)
85
+ raise unless callback
86
+ callback.call(step, arg, error)
87
+ end
88
+
89
+ def find_error_callback(error)
90
+ error_callbacks.find { |callback| callback.applies_to_error?(error) }
91
+ end
77
92
 
78
- raise
93
+ def reset_log
94
+ self.log = {}
79
95
  end
80
96
  end
81
- end
97
+ end
@@ -1,47 +1,32 @@
1
1
  module NxtPipeline
2
2
  class Step
3
- def initialize(*args)
4
- validate_initialize_args(*args).each do |key, value|
5
- send("#{key}=", value)
6
- end
7
- end
8
-
9
- def pipe_through
10
- # Public interface of Step, to be implemented by subclasses.
11
- raise NotImplementedError
3
+ def initialize(constructor, **opts)
4
+ define_attr_readers(opts)
5
+ @opts = opts
6
+ @constructor = constructor
12
7
  end
13
8
 
14
- def self.[](*args)
15
- raise ArgumentError, 'Arguments missing' if args.empty?
9
+ attr_accessor :constructor
16
10
 
17
- Class.new(self) do
18
- self.step_args = args.map(&:to_sym)
11
+ def execute(arg)
12
+ constructor.call(self, arg)
13
+ # instance_exec(arg, &constructor)
14
+ end
19
15
 
20
- self.step_args.each do |step_arg|
21
- attr_accessor step_arg
22
- end
23
- end
16
+ def to_s
17
+ "#{self.class} opts => #{opts}"
24
18
  end
25
19
 
26
20
  private
27
21
 
28
- cattr_accessor :step_args, instance_writer: false, default: []
22
+ attr_reader :opts
29
23
 
30
- def validate_initialize_args(*args)
31
- raise ArgumentError, arguments_missing_msg(self.step_args) if args.empty?
32
-
33
- keyword_args = args.first
34
- missing_keyword_args = self.step_args.reject do |arg|
35
- keyword_args.include?(arg)
24
+ def define_attr_readers(opts)
25
+ opts.each do |key, value|
26
+ define_singleton_method key.to_s do
27
+ value
28
+ end
36
29
  end
37
-
38
- raise ArgumentError, arguments_missing_msg(missing_keyword_args) if missing_keyword_args.any?
39
-
40
- keyword_args.slice(*self.step_args)
41
- end
42
-
43
- def arguments_missing_msg(missing_arg_keys)
44
- "Arguments missing: #{missing_arg_keys.map { |a| "#{a}:" }.join(', ')}"
45
30
  end
46
31
  end
47
32
  end
@@ -1,3 +1,3 @@
1
1
  module NxtPipeline
2
- VERSION = "0.2.0".freeze
2
+ VERSION = "0.2.1".freeze
3
3
  end
data/nxt_pipeline.gemspec CHANGED
@@ -39,4 +39,5 @@ Gem::Specification.new do |spec|
39
39
  spec.add_development_dependency "guard-rspec"
40
40
  spec.add_development_dependency "rake", "~> 10.0"
41
41
  spec.add_development_dependency "rspec", "~> 3.0"
42
+ spec.add_development_dependency "pry"
42
43
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nxt_pipeline
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nils Sommer
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: exe
12
12
  cert_chain: []
13
- date: 2019-03-10 00:00:00.000000000 Z
13
+ date: 2019-04-19 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activesupport
@@ -82,6 +82,20 @@ dependencies:
82
82
  - - "~>"
83
83
  - !ruby/object:Gem::Version
84
84
  version: '3.0'
85
+ - !ruby/object:Gem::Dependency
86
+ name: pry
87
+ requirement: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ type: :development
93
+ prerelease: false
94
+ version_requirements: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
85
99
  description:
86
100
  email:
87
101
  - mail@nilssommer.de
@@ -106,6 +120,7 @@ files:
106
120
  - bin/rspec
107
121
  - bin/setup
108
122
  - lib/nxt_pipeline.rb
123
+ - lib/nxt_pipeline/error_callback.rb
109
124
  - lib/nxt_pipeline/pipeline.rb
110
125
  - lib/nxt_pipeline/step.rb
111
126
  - lib/nxt_pipeline/version.rb