laminar 0.4.0 → 0.5.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
  SHA256:
3
- metadata.gz: cfa336597901d8c1c25976ced526555e981b731613daa6f916394b2ac71cb7a9
4
- data.tar.gz: 9f5db130a19de3dda3ef4579bff68e103bf667e79759f5be0726c413efbacff1
3
+ metadata.gz: 697f7db8e9717c6795e2e95abfcc3601c50994e90fba9cc5275b27b0a8995b92
4
+ data.tar.gz: fc4d199a808988c03b9ce8b8b12fa6cd9b6c4886a0f605b0dc53072970813c3d
5
5
  SHA512:
6
- metadata.gz: 9d054d187381bc5c768b490df36c65a02466a0ea30f198db208006491610fa23f371e1235a0e9f37ae251ad0a3997ecfaad7490dacb909912532c5e406d5af35
7
- data.tar.gz: 6cb99ce2b271f7f6f1782de44a3fc1b2585412441a375cd4be77ea31544fd9813f7ecfe02f3cfe8968e121762933853a7da5f533185202106e860dc42d203feb
6
+ metadata.gz: 6fbe3e9b76547ef3e9cbb62ff3a05c552aba735fa280bcbc24c5d3bcffd2bb7f9c9d799e84489e7ffb035ca84697f78216f9e67924e6536d1d8bd463290537a1
7
+ data.tar.gz: 26523b261d6927e59f629abe85f11a788435b5c304d60eacbedd148882e52f9432d2fb0255d3a52dbcffc544e7e2fab4483e70228c97f5d0c8b1019a4ff96255
@@ -0,0 +1,7 @@
1
+ Metrics/LineLength:
2
+ Max: 99
3
+ AllCops:
4
+ TargetRubyVersion: 2.3.0
5
+
6
+ Metrics/MethodLength:
7
+ Max: 12
@@ -1,8 +1,30 @@
1
1
  # Change Log
2
2
 
3
- ## [Unreleased](https://github.com/rmlockerd/laminar/tree/HEAD)
3
+ ## [v0.5.0](https://github.com/rmlockerd/laminar/tree/v0.5.0) (2018-11-12)
4
+ [Full Changelog](https://github.com/rmlockerd/laminar/compare/v0.4.0...v0.5.0)
4
5
 
5
- [Full Changelog](https://github.com/rmlockerd/laminar/compare/v0.2.0...HEAD)
6
+ **Implemented enhancements:**
7
+
8
+ - Add 'soft' halt capability for flows [\#11](https://github.com/rmlockerd/laminar/issues/11)
9
+ - Check starting context for a flow [\#9](https://github.com/rmlockerd/laminar/issues/9)
10
+
11
+ **Fixed bugs:**
12
+
13
+ - Branch conditionals \(if:/unless:\) do not get context passed [\#10](https://github.com/rmlockerd/laminar/issues/10)
14
+
15
+ ## [v0.4.0](https://github.com/rmlockerd/laminar/tree/v0.4.0) (2018-10-12)
16
+ [Full Changelog](https://github.com/rmlockerd/laminar/compare/v0.3.0...v0.4.0)
17
+
18
+ **Implemented enhancements:**
19
+
20
+ - Shorthand for branching to end [\#8](https://github.com/rmlockerd/laminar/issues/8)
21
+
22
+ **Fixed bugs:**
23
+
24
+ - branch if/unless don't accept blocks/Procs [\#7](https://github.com/rmlockerd/laminar/issues/7)
25
+
26
+ ## [v0.3.0](https://github.com/rmlockerd/laminar/tree/v0.3.0) (2018-10-01)
27
+ [Full Changelog](https://github.com/rmlockerd/laminar/compare/v0.2.0...v0.3.0)
6
28
 
7
29
  **Implemented enhancements:**
8
30
 
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:
@@ -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
@@ -34,7 +35,6 @@ module Laminar
34
35
 
35
36
  # Additional instance methods
36
37
  module InstanceMethods
37
-
38
38
  private
39
39
 
40
40
  def run_before_callbacks
@@ -31,10 +31,14 @@ 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
- raise ParticleStopped.new(self)
37
+ end
38
+
39
+ def halt!(context = {})
40
+ halt(context)
41
+ raise ParticleStopped, self
38
42
  end
39
43
 
40
44
  def fail!(context = {})
@@ -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,35 @@ 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
+ pre_step_callbacks(step)
126
+ run_particle(step)
127
+ post_step_callbacks(step)
132
128
  !context.halted?
133
129
  end
134
130
 
131
+ def post_step_callbacks(step)
132
+ guarded_callback(step.after_callbacks)
133
+ guarded_callback(flowspec.after_each_callbacks)
134
+ end
135
+
136
+ def pre_step_callbacks(step)
137
+ guarded_callback(flowspec.before_each_callbacks)
138
+ guarded_callback(step.before_callbacks)
139
+ end
140
+
141
+ def run_particle(step)
142
+ return if context.halted?
143
+
144
+ step.particle.call!(context)
145
+ end
146
+
147
+ def guarded_callback(list)
148
+ return if context.halted?
149
+
150
+ run_callbacks(list)
151
+ end
152
+
135
153
  # Given a step, returns the next step that satisfies the
136
154
  # execution/branch conditions.
137
155
  def next_step(current)
@@ -143,6 +161,19 @@ module Laminar
143
161
 
144
162
  flowspec.steps[next_name]
145
163
  end
164
+
165
+ def validate_required_context
166
+ missing = []
167
+ flowspec.flow_params.each do |param|
168
+ next if context.key?(param)
169
+
170
+ missing << param
171
+ end
172
+
173
+ return if missing.empty?
174
+
175
+ raise ArgumentError, "missing context: #{missing.join(', ')}"
176
+ end
146
177
  end
147
178
  end
148
179
  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
@@ -41,7 +39,7 @@ module Laminar
41
39
 
42
40
  def run_condition(target)
43
41
  if @condition.is_a?(Proc)
44
- @condition.call()
42
+ @condition.call(target.context)
45
43
  else
46
44
  target.send(@condition)
47
45
  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
@@ -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)
@@ -43,7 +44,7 @@ module Laminar
43
44
 
44
45
  param_list = context_slice
45
46
  param_list.empty? ? call : call(context_slice)
46
- run_after_callbacks
47
+ run_after_callbacks unless context.halted?
47
48
  context
48
49
  end
49
50
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Laminar
4
- VERSION = '0.4.0'
4
+ VERSION = '0.5.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: laminar
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.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-12 00:00:00.000000000 Z
11
+ date: 2018-11-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -91,6 +91,7 @@ files:
91
91
  - ".github/ISSUE_TEMPLATE/feature_request.md"
92
92
  - ".gitignore"
93
93
  - ".rspec"
94
+ - ".rubocop.yml"
94
95
  - ".travis.yml"
95
96
  - CHANGELOG.md
96
97
  - CODE_OF_CONDUCT.md