laminar 0.3.0 → 0.7.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
- SHA1:
3
- metadata.gz: 3fb0486b2d99158c6fa192d237100a61bc9ae40e
4
- data.tar.gz: 0755f031eee6dfaefbb64d9301c3e27327c6088f
2
+ SHA256:
3
+ metadata.gz: ea3e3d103dc6aa3041a22d958a120a73c7fa526f5dcbfc74b8f19503194362a6
4
+ data.tar.gz: 98289c18df72debb00c6df9877bae3262a6f6fda7d56b44197e09c6e9a980a2d
5
5
  SHA512:
6
- metadata.gz: 5d7f0f34420a061583f4744cf16bfa6a899c8ccf8bd2cb2b8aa24c269c487290632084fbce0dd74e6f2f6540b54f4e605e6731f37e89e6c6bee5a5176f0f55fc
7
- data.tar.gz: 2fbbbef809c51fff94be71195e63fab67c8c93f9cb1ed0b96228e7dcc84e21a34572d31f4a48e18e6e23514686115a690f6d125581333626c7c1c93e1f2eb38a
6
+ metadata.gz: 9b04f54ca23f10321e112675c6ddefdfbede105a744304e9fd969580145b32293ae5e52a9c8e78ffb8a4f1b65ed9bbabf0f660604e84609fcd7c6e2403ec1462
7
+ data.tar.gz: 8289147544e03879303d68d86f4f171461ceea552fc81feba510b9caced6dcca50e1ed7c088cffd1459d31658e06cf0c86ba81708db8cfd216e80ca21e811ab5
data/.gitignore CHANGED
@@ -1,3 +1,4 @@
1
+ .ruby-version
1
2
  *.gem
2
3
  Gemfile.lock
3
4
  /.bundle/
data/.rspec CHANGED
@@ -1,3 +1,3 @@
1
- --format documentation
2
1
  --color
3
2
  --require spec_helper
3
+ --order random
@@ -0,0 +1,22 @@
1
+ Layout/LineLength:
2
+ Max: 99
3
+ AllCops:
4
+ TargetRubyVersion: 2.3.0
5
+
6
+ Metrics/MethodLength:
7
+ Max: 12
8
+
9
+ Lint/RaiseException:
10
+ Enabled: true
11
+
12
+ Lint/StructNewOverride:
13
+ Enabled: true
14
+
15
+ Style/HashEachMethods:
16
+ Enabled: true
17
+
18
+ Style/HashTransformKeys:
19
+ Enabled: true
20
+
21
+ Style/HashTransformValues:
22
+ Enabled: true
@@ -6,8 +6,13 @@ rvm:
6
6
  - 2.3
7
7
  - 2.4
8
8
  - 2.5
9
+ - 2.6
9
10
  before_install: gem install bundler -v 1.16.5
10
11
  script:
11
12
  - bundle exec rspec
13
+ before_script:
14
+ - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
15
+ - chmod +x ./cc-test-reporter
16
+ - ./cc-test-reporter before-build
12
17
  after_script:
13
18
  - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT
@@ -1,8 +1,49 @@
1
- # Change Log
1
+ # Changelog
2
2
 
3
- ## [Unreleased](https://github.com/rmlockerd/laminar/tree/HEAD)
3
+ ## [v0.7.0](https://github.com/rmlockerd/laminar/tree/v0.7.0) (2020-06-13)
4
4
 
5
- [Full Changelog](https://github.com/rmlockerd/laminar/compare/v0.2.0...HEAD)
5
+ [Full Changelog](https://github.com/rmlockerd/laminar/compare/v0.6.0...v0.7.0)
6
+
7
+ **Fixed bugs:**
8
+
9
+ - Fix 3.0-compatibility warnings with Ruby 2.7.x [\#13](https://github.com/rmlockerd/laminar/issues/13)
10
+
11
+ ## [v0.6.0](https://github.com/rmlockerd/laminar/tree/v0.6.0) (2020-04-15)
12
+
13
+ [Full Changelog](https://github.com/rmlockerd/laminar/compare/v0.5.1...v0.6.0)
14
+
15
+ ## [v0.5.1](https://github.com/rmlockerd/laminar/tree/v0.5.1) (2020-03-06)
16
+
17
+ [Full Changelog](https://github.com/rmlockerd/laminar/compare/v0.5.0...v0.5.1)
18
+
19
+ ## [v0.5.0](https://github.com/rmlockerd/laminar/tree/v0.5.0) (2018-11-12)
20
+
21
+ [Full Changelog](https://github.com/rmlockerd/laminar/compare/v0.4.0...v0.5.0)
22
+
23
+ **Implemented enhancements:**
24
+
25
+ - Add 'soft' halt capability for flows [\#11](https://github.com/rmlockerd/laminar/issues/11)
26
+ - Check starting context for a flow [\#9](https://github.com/rmlockerd/laminar/issues/9)
27
+
28
+ **Fixed bugs:**
29
+
30
+ - Branch conditionals \(if:/unless:\) do not get context passed [\#10](https://github.com/rmlockerd/laminar/issues/10)
31
+
32
+ ## [v0.4.0](https://github.com/rmlockerd/laminar/tree/v0.4.0) (2018-10-12)
33
+
34
+ [Full Changelog](https://github.com/rmlockerd/laminar/compare/v0.3.0...v0.4.0)
35
+
36
+ **Implemented enhancements:**
37
+
38
+ - Shorthand for branching to end [\#8](https://github.com/rmlockerd/laminar/issues/8)
39
+
40
+ **Fixed bugs:**
41
+
42
+ - branch if/unless don't accept blocks/Procs [\#7](https://github.com/rmlockerd/laminar/issues/7)
43
+
44
+ ## [v0.3.0](https://github.com/rmlockerd/laminar/tree/v0.3.0) (2018-10-01)
45
+
46
+ [Full Changelog](https://github.com/rmlockerd/laminar/compare/v0.2.0...v0.3.0)
6
47
 
7
48
  **Implemented enhancements:**
8
49
 
@@ -13,10 +54,13 @@
13
54
  - Add before/after particle callbacks [\#1](https://github.com/rmlockerd/laminar/issues/1)
14
55
 
15
56
  ## [v0.2.0](https://github.com/rmlockerd/laminar/tree/v0.2.0) (2018-09-27)
57
+
58
+ [Full Changelog](https://github.com/rmlockerd/laminar/compare/8bd9c0f01bae6cfd7f5df58c3c792068fa30366d...v0.2.0)
59
+
16
60
  **Fixed bugs:**
17
61
 
18
62
  - .gemspec missing active\_support dependency [\#4](https://github.com/rmlockerd/laminar/issues/4)
19
63
 
20
64
 
21
65
 
22
- \* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*
66
+ \* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*
data/README.md CHANGED
@@ -1,8 +1,9 @@
1
1
  # Laminar
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/laminar.svg)](https://badge.fury.io/rb/laminar)
3
4
  [![Build Status](https://travis-ci.org/rmlockerd/laminar.svg?branch=master)](https://travis-ci.org/rmlockerd/laminar)
4
- [![Maintainability](https://img.shields.io/codeclimate/maintainability/rmlockerd/laminar.svg)](https://codeclimate.com/github/rmlockerd/laminar)
5
- [![Test Coverage](https://img.shields.io/codeclimate/coverage-letter/rmlockerd/laminar.svg)](https://codeclimate.com/github/rmlockerd/laminar)
5
+ [![Maintainability](https://api.codeclimate.com/v1/badges/6b2d761ca042af6461e3/maintainability)](https://codeclimate.com/github/rmlockerd/laminar/maintainability)
6
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/6b2d761ca042af6461e3/test_coverage)](https://codeclimate.com/github/rmlockerd/laminar/test_coverage)
6
7
 
7
8
  A simple Chain-of-Responsibility/Interactor gem that helps MVC applications organise their business logic, keeping their models and controllers skinny and their logic easily testable. Individual chunks of business logic (called particles) can be easily composed into more complex chains called flows.
8
9
 
@@ -237,6 +238,31 @@ class CheckEquipment
237
238
  end
238
239
  ```
239
240
 
241
+ #### Flow Parameters
242
+ Flows do not get the benefit of keyword argument checking like ordinary
243
+ Particles, since their #call method is implemented by the Flow mixin.
244
+ You can, however, specify a list of required context keys in the flow
245
+ definition itself:
246
+
247
+ ```ruby
248
+ flow do
249
+ context_must_have :product_sku, :unit_price
250
+ ...
251
+ end
252
+ ```
253
+
254
+ The context is simply checked for the presence of the specified
255
+ list of keys. If you need to do more complicated validation of
256
+ context, use a ``#before`` callback that halts the flow if
257
+ validation fails.
258
+
259
+ Context validation happens just prior to execution of the
260
+ first step in the flow (and any ``#before_each``) callbacks, but after
261
+ execution of the Flow's own ``#before`` callbacks. Since you can
262
+ manipulate context in a callback, this is useful to set up
263
+ context required by a flow's particles that you don't necessarily
264
+ expect your caller to provide.
265
+
240
266
  #### Flow Branching
241
267
  Ordinarily particle execution is sequential in the order specified.
242
268
  However, you can optionally branch to a different label with `branch`.
@@ -250,7 +276,7 @@ However, you can optionally branch to a different label with `branch`.
250
276
  end
251
277
  ```
252
278
 
253
- You can use the special symbol :endflow to jump terminate the flow
279
+ You can use the endflow directive to terminate the flow gracefully
254
280
  (skipping all remaining steps).
255
281
 
256
282
  ```ruby
@@ -309,6 +335,11 @@ no condition is satisfied, execution drops to the next step.
309
335
  step :last_step
310
336
  end
311
337
  ```
338
+ #### Halting a Flow
339
+ If a particle calls ``#halt!`` or ``#fail!`` on its context, execution
340
+ of any surrounding Flow (or nested flows) stops immediately via a
341
+ ``ParticleStopped`` error. To gracefully signal that an enclosing
342
+ flow should stop without raising an error, use ``#halt`` instead.
312
343
 
313
344
  #### Flow Callbacks
314
345
 
@@ -318,11 +349,15 @@ A flow can specify callback(s) to run before/after every step:
318
349
  class MyFlow
319
350
  include Laminar::Flow
320
351
 
321
- before_each :thing, :thing2 # method
322
- before_each { ... } # block
352
+ flow do
353
+ before_each :thing, :thing2 # method
354
+ before_each { ... } # block
323
355
 
324
- after_each :thing, :thing2 # method
325
- after_each { ... } # block
356
+ after_each :thing, :thing2 # method
357
+ after_each { ... } # block
358
+
359
+ # steps ...
360
+ end
326
361
  ```
327
362
 
328
363
  The order of execution for callbacks in a flow looks like:
@@ -33,10 +33,10 @@ Gem::Specification.new do |spec|
33
33
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
34
34
  spec.require_paths = ['lib']
35
35
 
36
- spec.add_development_dependency 'activesupport', '>= 4.2'
36
+ spec.add_development_dependency 'activesupport', '~> 4.2'
37
37
 
38
38
  spec.add_development_dependency 'bundler', '~> 1.16'
39
- spec.add_development_dependency 'rake', '~> 10.0'
39
+ spec.add_development_dependency 'rake', '~> 12.3', '>= 12.3.3'
40
40
  spec.add_development_dependency 'rspec', '~> 3.0'
41
41
  spec.add_development_dependency 'simplecov', '~> 0.16'
42
42
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Laminar
4
+ # Callback hooks for particles
4
5
  module Callbacks
5
6
  def self.included(klass)
6
7
  klass.class_eval do
@@ -23,6 +24,11 @@ module Laminar
23
24
  end
24
25
  alias after_call after
25
26
 
27
+ def finalize(*args, &block)
28
+ final_list.concat(args)
29
+ final_list << block if block
30
+ end
31
+
26
32
  def before_list
27
33
  @before_list ||= []
28
34
  end
@@ -30,11 +36,14 @@ module Laminar
30
36
  def after_list
31
37
  @after_list ||= []
32
38
  end
39
+
40
+ def final_list
41
+ @final_list ||= []
42
+ end
33
43
  end
34
44
 
35
45
  # Additional instance methods
36
46
  module InstanceMethods
37
-
38
47
  private
39
48
 
40
49
  def run_before_callbacks
@@ -45,6 +54,10 @@ module Laminar
45
54
  run_callbacks(self.class.after_list)
46
55
  end
47
56
 
57
+ def run_final_callbacks
58
+ run_callbacks(self.class.final_list)
59
+ end
60
+
48
61
  def run_callbacks(list)
49
62
  list.each { |cb| cb.is_a?(Symbol) ? send(cb) : instance_exec(&cb) }
50
63
  end
@@ -31,9 +31,13 @@ module Laminar
31
31
  @halted
32
32
  end
33
33
 
34
- def halt!(context = {})
34
+ def halt(context = {})
35
35
  @halted = true
36
36
  merge!(context)
37
+ end
38
+
39
+ def halt!(context = {})
40
+ halt(context)
37
41
  raise ParticleStopped, self
38
42
  end
39
43
 
@@ -103,14 +103,11 @@ module Laminar
103
103
  end
104
104
 
105
105
  # Initiates evaluation of the flow.
106
- # @param object the context/input on which the flow will operate. This
107
- # is usually a Hash but in simple cases can be a single object. The
108
- # implementing flow class should provide a #context_valid? method
109
- # that returns true is the given context contains the minimum required
110
- # information.
106
+ # @param object the context/input on which the flow will operate.
111
107
  def call(*)
112
108
  return context if flowspec.nil?
113
109
 
110
+ validate_required_context
114
111
  step = flowspec.steps[flowspec.first_step]
115
112
  loop do
116
113
  break unless invoke_step(step)
@@ -124,14 +121,36 @@ module Laminar
124
121
 
125
122
  def invoke_step(step)
126
123
  return if step.nil?
127
- run_callbacks(flowspec.before_each_callbacks)
128
- run_callbacks(step.before_callbacks)
129
- step.particle.call!(context)
130
- run_callbacks(step.after_callbacks)
131
- run_callbacks(flowspec.after_each_callbacks)
124
+
125
+ context[:__flow_step__] = step.name
126
+ pre_step_callbacks(step)
127
+ run_particle(step)
128
+ post_step_callbacks(step)
132
129
  !context.halted?
133
130
  end
134
131
 
132
+ def post_step_callbacks(step)
133
+ guarded_callback(step.after_callbacks)
134
+ guarded_callback(flowspec.after_each_callbacks)
135
+ end
136
+
137
+ def pre_step_callbacks(step)
138
+ guarded_callback(flowspec.before_each_callbacks)
139
+ guarded_callback(step.before_callbacks)
140
+ end
141
+
142
+ def run_particle(step)
143
+ return if context.halted?
144
+
145
+ step.particle.call!(context)
146
+ end
147
+
148
+ def guarded_callback(list)
149
+ return if context.halted?
150
+
151
+ run_callbacks(list)
152
+ end
153
+
135
154
  # Given a step, returns the next step that satisfies the
136
155
  # execution/branch conditions.
137
156
  def next_step(current)
@@ -143,6 +162,19 @@ module Laminar
143
162
 
144
163
  flowspec.steps[next_name]
145
164
  end
165
+
166
+ def validate_required_context
167
+ missing = []
168
+ flowspec.flow_params.each do |param|
169
+ next if context.key?(param)
170
+
171
+ missing << param
172
+ end
173
+
174
+ return if missing.empty?
175
+
176
+ raise ArgumentError, "missing context: #{missing.join(', ')}"
177
+ end
146
178
  end
147
179
  end
148
180
  end
@@ -18,9 +18,7 @@ module Laminar
18
18
  attr_accessor :name, :condition, :condition_type
19
19
 
20
20
  def initialize(name, options = {})
21
- unless name.class.method_defined?(:to_sym)
22
- raise ArgumentError, 'invalid name'
23
- end
21
+ raise ArgumentError, 'invalid name' unless name.class.method_defined?(:to_sym)
24
22
 
25
23
  validate_options(options)
26
24
  @name = name.to_sym
@@ -40,7 +38,11 @@ module Laminar
40
38
  private
41
39
 
42
40
  def run_condition(target)
43
- target.send(@condition)
41
+ if @condition.is_a?(Proc)
42
+ @condition.call(target.context)
43
+ else
44
+ target.send(@condition)
45
+ end
44
46
  end
45
47
 
46
48
  def define_condition(options)
@@ -48,9 +50,9 @@ module Laminar
48
50
  return if @condition_type.nil?
49
51
 
50
52
  @condition = options[@condition_type]
51
- return if @condition.nil? || @condition.is_a?(Symbol)
53
+ return if @condition.nil? || @condition.is_a?(Symbol) || @condition.is_a?(Proc)
52
54
 
53
- raise TypeError, 'condition must be a method (symbol).'
55
+ raise TypeError, 'condition must be a method symbol or Proc).'
54
56
  end
55
57
  end
56
58
  end
@@ -23,6 +23,14 @@ module Laminar
23
23
  @prev_step = step
24
24
  end
25
25
 
26
+ def context_must_have(*params)
27
+ flow_params.concat(params.flatten)
28
+ end
29
+
30
+ def flow_params
31
+ @flow_params ||= []
32
+ end
33
+
26
34
  def before_each(*args, &block)
27
35
  before_each_callbacks.concat(args)
28
36
  before_each_callbacks << block if block
@@ -14,9 +14,7 @@ module Laminar
14
14
  valid_options %i[class].freeze
15
15
 
16
16
  def initialize(name, options = {}, &block)
17
- unless name.class.method_defined?(:to_sym)
18
- raise ArgumentError, 'invalid name'
19
- end
17
+ raise ArgumentError, 'invalid name' unless name.class.method_defined?(:to_sym)
20
18
 
21
19
  validate_options(options)
22
20
  @class_name = (options[:class] || name).to_s.camelize
@@ -43,6 +41,10 @@ module Laminar
43
41
  end
44
42
  alias goto branch
45
43
 
44
+ def endflow(options = {})
45
+ branches << Branch.new(:endflow, options)
46
+ end
47
+
46
48
  # Find the next rule in the flow. Examines the branches associated
47
49
  # with the current rule and returns the name of the first branch
48
50
  # that satisfies its condition.
@@ -26,6 +26,7 @@ module Laminar
26
26
  end
27
27
  end
28
28
 
29
+ # Laminar::Particle instance methods.
29
30
  module InstanceMethods
30
31
  def initialize(context = {})
31
32
  @context = Context.build(context)
@@ -38,12 +39,18 @@ module Laminar
38
39
  end
39
40
 
40
41
  def invoke!
41
- run_before_callbacks
42
- return context if context.halted?
42
+ begin
43
+ run_before_callbacks
44
+ return context if context.halted?
43
45
 
44
- param_list = context_slice
45
- param_list.empty? ? call : call(context_slice)
46
- run_after_callbacks
46
+ param_list = context_slice
47
+ param_list.empty? ? call : call(**context_slice)
48
+ run_after_callbacks unless context.halted?
49
+ rescue ParticleStopped
50
+ run_final_callbacks
51
+ raise
52
+ end
53
+ run_final_callbacks
47
54
  context
48
55
  end
49
56
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Laminar
4
- VERSION = '0.3.0'
4
+ VERSION = '0.7.0'
5
5
  end
metadata CHANGED
@@ -1,27 +1,27 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: laminar
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Lockerd
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-10-01 00:00:00.000000000 Z
11
+ date: 2020-06-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
19
  version: '4.2'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ">="
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '4.2'
27
27
  - !ruby/object:Gem::Dependency
@@ -44,14 +44,20 @@ dependencies:
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '10.0'
47
+ version: '12.3'
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: 12.3.3
48
51
  type: :development
49
52
  prerelease: false
50
53
  version_requirements: !ruby/object:Gem::Requirement
51
54
  requirements:
52
55
  - - "~>"
53
56
  - !ruby/object:Gem::Version
54
- version: '10.0'
57
+ version: '12.3'
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: 12.3.3
55
61
  - !ruby/object:Gem::Dependency
56
62
  name: rspec
57
63
  requirement: !ruby/object:Gem::Requirement
@@ -91,6 +97,7 @@ files:
91
97
  - ".github/ISSUE_TEMPLATE/feature_request.md"
92
98
  - ".gitignore"
93
99
  - ".rspec"
100
+ - ".rubocop.yml"
94
101
  - ".travis.yml"
95
102
  - CHANGELOG.md
96
103
  - CODE_OF_CONDUCT.md
@@ -132,8 +139,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
132
139
  - !ruby/object:Gem::Version
133
140
  version: '0'
134
141
  requirements: []
135
- rubyforge_project:
136
- rubygems_version: 2.6.11
142
+ rubygems_version: 3.0.3
137
143
  signing_key:
138
144
  specification_version: 4
139
145
  summary: Simple, composable business objects & workflow