laminar 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +35 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +17 -0
- data/.gitignore +13 -0
- data/.rspec +3 -0
- data/.travis.yml +13 -0
- data/CHANGELOG.md +22 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +362 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/laminar.gemspec +42 -0
- data/lib/laminar.rb +13 -0
- data/lib/laminar/callbacks.rb +53 -0
- data/lib/laminar/context.rb +45 -0
- data/lib/laminar/flow.rb +148 -0
- data/lib/laminar/flow/branch.rb +57 -0
- data/lib/laminar/flow/flow_error.rb +9 -0
- data/lib/laminar/flow/options_validator.rb +39 -0
- data/lib/laminar/flow/specification.rb +55 -0
- data/lib/laminar/flow/step.rb +87 -0
- data/lib/laminar/particle.rb +69 -0
- data/lib/laminar/particle_stopped.rb +13 -0
- data/lib/laminar/version.rb +5 -0
- metadata +140 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3fb0486b2d99158c6fa192d237100a61bc9ae40e
|
4
|
+
data.tar.gz: 0755f031eee6dfaefbb64d9301c3e27327c6088f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5d7f0f34420a061583f4744cf16bfa6a899c8ccf8bd2cb2b8aa24c269c487290632084fbce0dd74e6f2f6540b54f4e605e6731f37e89e6c6bee5a5176f0f55fc
|
7
|
+
data.tar.gz: 2fbbbef809c51fff94be71195e63fab67c8c93f9cb1ed0b96228e7dcc84e21a34572d31f4a48e18e6e23514686115a690f6d125581333626c7c1c93e1f2eb38a
|
@@ -0,0 +1,35 @@
|
|
1
|
+
---
|
2
|
+
name: Bug report
|
3
|
+
about: Create a report to help us improve
|
4
|
+
|
5
|
+
---
|
6
|
+
|
7
|
+
**Describe the bug**
|
8
|
+
A clear and concise description of what the bug is.
|
9
|
+
|
10
|
+
**To Reproduce**
|
11
|
+
Steps to reproduce the behavior:
|
12
|
+
1. Go to '...'
|
13
|
+
2. Click on '....'
|
14
|
+
3. Scroll down to '....'
|
15
|
+
4. See error
|
16
|
+
|
17
|
+
**Expected behavior**
|
18
|
+
A clear and concise description of what you expected to happen.
|
19
|
+
|
20
|
+
**Screenshots**
|
21
|
+
If applicable, add screenshots to help explain your problem.
|
22
|
+
|
23
|
+
**Desktop (please complete the following information):**
|
24
|
+
- OS: [e.g. iOS]
|
25
|
+
- Browser [e.g. chrome, safari]
|
26
|
+
- Version [e.g. 22]
|
27
|
+
|
28
|
+
**Smartphone (please complete the following information):**
|
29
|
+
- Device: [e.g. iPhone6]
|
30
|
+
- OS: [e.g. iOS8.1]
|
31
|
+
- Browser [e.g. stock browser, safari]
|
32
|
+
- Version [e.g. 22]
|
33
|
+
|
34
|
+
**Additional context**
|
35
|
+
Add any other context about the problem here.
|
@@ -0,0 +1,17 @@
|
|
1
|
+
---
|
2
|
+
name: Feature request
|
3
|
+
about: Suggest an idea for this project
|
4
|
+
|
5
|
+
---
|
6
|
+
|
7
|
+
**Is your feature request related to a problem? Please describe.**
|
8
|
+
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
9
|
+
|
10
|
+
**Describe the solution you'd like**
|
11
|
+
A clear and concise description of what you want to happen.
|
12
|
+
|
13
|
+
**Describe alternatives you've considered**
|
14
|
+
A clear and concise description of any alternative solutions or features you've considered.
|
15
|
+
|
16
|
+
**Additional context**
|
17
|
+
Add any other context or screenshots about the feature request here.
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# Change Log
|
2
|
+
|
3
|
+
## [Unreleased](https://github.com/rmlockerd/laminar/tree/HEAD)
|
4
|
+
|
5
|
+
[Full Changelog](https://github.com/rmlockerd/laminar/compare/v0.2.0...HEAD)
|
6
|
+
|
7
|
+
**Implemented enhancements:**
|
8
|
+
|
9
|
+
- Before/after callbacks on individual flow steps [\#6](https://github.com/rmlockerd/laminar/issues/6)
|
10
|
+
- Halt a flow without failure [\#5](https://github.com/rmlockerd/laminar/issues/5)
|
11
|
+
- Add Travis configuration [\#3](https://github.com/rmlockerd/laminar/issues/3)
|
12
|
+
- Add automated tests [\#2](https://github.com/rmlockerd/laminar/issues/2)
|
13
|
+
- Add before/after particle callbacks [\#1](https://github.com/rmlockerd/laminar/issues/1)
|
14
|
+
|
15
|
+
## [v0.2.0](https://github.com/rmlockerd/laminar/tree/v0.2.0) (2018-09-27)
|
16
|
+
**Fixed bugs:**
|
17
|
+
|
18
|
+
- .gemspec missing active\_support dependency [\#4](https://github.com/rmlockerd/laminar/issues/4)
|
19
|
+
|
20
|
+
|
21
|
+
|
22
|
+
\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*
|
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
2
|
+
|
3
|
+
## Our Pledge
|
4
|
+
|
5
|
+
In the interest of fostering an open and welcoming environment, we as
|
6
|
+
contributors and maintainers pledge to making participation in our project and
|
7
|
+
our community a harassment-free experience for everyone, regardless of age, body
|
8
|
+
size, disability, ethnicity, gender identity and expression, level of experience,
|
9
|
+
nationality, personal appearance, race, religion, or sexual identity and
|
10
|
+
orientation.
|
11
|
+
|
12
|
+
## Our Standards
|
13
|
+
|
14
|
+
Examples of behavior that contributes to creating a positive environment
|
15
|
+
include:
|
16
|
+
|
17
|
+
* Using welcoming and inclusive language
|
18
|
+
* Being respectful of differing viewpoints and experiences
|
19
|
+
* Gracefully accepting constructive criticism
|
20
|
+
* Focusing on what is best for the community
|
21
|
+
* Showing empathy towards other community members
|
22
|
+
|
23
|
+
Examples of unacceptable behavior by participants include:
|
24
|
+
|
25
|
+
* The use of sexualized language or imagery and unwelcome sexual attention or
|
26
|
+
advances
|
27
|
+
* Trolling, insulting/derogatory comments, and personal or political attacks
|
28
|
+
* Public or private harassment
|
29
|
+
* Publishing others' private information, such as a physical or electronic
|
30
|
+
address, without explicit permission
|
31
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
32
|
+
professional setting
|
33
|
+
|
34
|
+
## Our Responsibilities
|
35
|
+
|
36
|
+
Project maintainers are responsible for clarifying the standards of acceptable
|
37
|
+
behavior and are expected to take appropriate and fair corrective action in
|
38
|
+
response to any instances of unacceptable behavior.
|
39
|
+
|
40
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
41
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
42
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
43
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
44
|
+
threatening, offensive, or harmful.
|
45
|
+
|
46
|
+
## Scope
|
47
|
+
|
48
|
+
This Code of Conduct applies both within project spaces and in public spaces
|
49
|
+
when an individual is representing the project or its community. Examples of
|
50
|
+
representing a project or community include using an official project e-mail
|
51
|
+
address, posting via an official social media account, or acting as an appointed
|
52
|
+
representative at an online or offline event. Representation of a project may be
|
53
|
+
further defined and clarified by project maintainers.
|
54
|
+
|
55
|
+
## Enforcement
|
56
|
+
|
57
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
58
|
+
reported by contacting the project team at robertl@gmail.com. All
|
59
|
+
complaints will be reviewed and investigated and will result in a response that
|
60
|
+
is deemed necessary and appropriate to the circumstances. The project team is
|
61
|
+
obligated to maintain confidentiality with regard to the reporter of an incident.
|
62
|
+
Further details of specific enforcement policies may be posted separately.
|
63
|
+
|
64
|
+
Project maintainers who do not follow or enforce the Code of Conduct in good
|
65
|
+
faith may face temporary or permanent repercussions as determined by other
|
66
|
+
members of the project's leadership.
|
67
|
+
|
68
|
+
## Attribution
|
69
|
+
|
70
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
71
|
+
available at [http://contributor-covenant.org/version/1/4][version]
|
72
|
+
|
73
|
+
[homepage]: http://contributor-covenant.org
|
74
|
+
[version]: http://contributor-covenant.org/version/1/4/
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2018 Robert Lockerd
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,362 @@
|
|
1
|
+
# Laminar
|
2
|
+
|
3
|
+
[![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)
|
6
|
+
|
7
|
+
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
|
+
## Installation
|
10
|
+
|
11
|
+
Add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
gem 'laminar'
|
15
|
+
```
|
16
|
+
|
17
|
+
And then execute:
|
18
|
+
|
19
|
+
$ bundle
|
20
|
+
|
21
|
+
Or install it yourself as:
|
22
|
+
|
23
|
+
$ gem install laminar
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
### 'Skinny' Controllers AND 'Skinny' Models
|
27
|
+
|
28
|
+
Even if you are reasonably new to Model-View-Controller (MVC) frameworks, such as Ruby on Rails, you have likely encountered the advice to have 'skinny controllers, fat models'. 'Skinny' controllers (i.e., simple, small, single-responsibility) are indeed best, but pushing a lot of code into your models has its own issues, and it isn't in reality an either/or choice.
|
29
|
+
|
30
|
+
Separating your business logic into single-purpose service objects (also sometimes called 'interactors' and several other names) helps keep your models and controllers skinny, and your code DRY and more easily testable.
|
31
|
+
|
32
|
+
### Particles
|
33
|
+
|
34
|
+
A particle is a PORO (Plain Old Ruby Object) that encapsulates a piece of your business logic. Keeping with the Single Responsibility Principle, a particle should preferably do only one thing.
|
35
|
+
|
36
|
+
#### Defining a Particle
|
37
|
+
A particle is a plain Ruby class that includes `Laminar::Particle` and defines a `call` method.
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
class ChangeAddress
|
41
|
+
include Laminar::Particle
|
42
|
+
|
43
|
+
def call
|
44
|
+
// change address logic goes here
|
45
|
+
end
|
46
|
+
end
|
47
|
+
```
|
48
|
+
|
49
|
+
#### Particle Context
|
50
|
+
|
51
|
+
To invoke a particle, invoke the `.call` method on the particle's
|
52
|
+
class object, passing a Hash of values that is the 'context' in which
|
53
|
+
the particle runs.
|
54
|
+
```ruby
|
55
|
+
ChangeAddress.call(user: user, new_address: addr)
|
56
|
+
```
|
57
|
+
|
58
|
+
The invoked particle accesses its context within its `.call` method like normal Hash:
|
59
|
+
```ruby
|
60
|
+
sku = context[:product_sku]
|
61
|
+
```
|
62
|
+
|
63
|
+
The particle can also add to or modify the context. The context is returned to the invoker, which can then access the modified context.
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
// Particle
|
67
|
+
class OpenTicket
|
68
|
+
include Laminar::Particle
|
69
|
+
|
70
|
+
def call
|
71
|
+
context[:status] = :pending
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
// Caller
|
76
|
+
result = OpenTicket.call
|
77
|
+
if result[:status] != :pending
|
78
|
+
...
|
79
|
+
end
|
80
|
+
```
|
81
|
+
|
82
|
+
#### Keyword Arguments
|
83
|
+
|
84
|
+
You can also use keyword arguments particle's `.call` method:
|
85
|
+
```ruby
|
86
|
+
class ChangeAddress
|
87
|
+
include Laminar::Particle
|
88
|
+
|
89
|
+
def call(user:, new_address:)
|
90
|
+
// change address logic goes here
|
91
|
+
end
|
92
|
+
end
|
93
|
+
```
|
94
|
+
|
95
|
+
When you declare keyword arguments, Laminar passes matching values
|
96
|
+
from the context to your particle. This can make your particle more
|
97
|
+
self-documenting and provides a simple `ArgumentError` exception if the
|
98
|
+
calling context does not contain the minimum information required for the
|
99
|
+
particle to function.
|
100
|
+
|
101
|
+
The `.call` implementation always has access to the full context via
|
102
|
+
`context` whether or not you declare keyword arguments.
|
103
|
+
|
104
|
+
#### Particle Success / Failure
|
105
|
+
Particles have a simple mechanism for flagging success and
|
106
|
+
failure. To signal failure, simply call `.fail!` on the context.
|
107
|
+
```ruby
|
108
|
+
context.fail!
|
109
|
+
```
|
110
|
+
|
111
|
+
There are also convenience methods for checking success/failure:
|
112
|
+
```ruby
|
113
|
+
context.success? # => true by default
|
114
|
+
context.failed? # => false
|
115
|
+
|
116
|
+
context.fail!
|
117
|
+
|
118
|
+
context.halted? # => true
|
119
|
+
context.failed? # => true
|
120
|
+
context.success? # => false
|
121
|
+
```
|
122
|
+
|
123
|
+
The `.fail!` method accepts a hash that is merged into the context,
|
124
|
+
making it convenient to attach error information:
|
125
|
+
```ruby
|
126
|
+
context.fail!(error: 'The user is allergic to bananas!')
|
127
|
+
```
|
128
|
+
|
129
|
+
Use the `.halt!` class method to immediately stop a particle without marking it as a failure.
|
130
|
+
|
131
|
+
```ruby
|
132
|
+
context.halt!
|
133
|
+
|
134
|
+
context.halted? # => true
|
135
|
+
context.success? # => true
|
136
|
+
context.failed? # => false
|
137
|
+
```
|
138
|
+
|
139
|
+
The `.halt!` accepts a context hash similar to `.fail!`.
|
140
|
+
|
141
|
+
#### Callbacks
|
142
|
+
|
143
|
+
Particles can specify one or more callbacks to execute immediately before or after the invocation of its `#call`.
|
144
|
+
|
145
|
+
```ruby
|
146
|
+
class Foo
|
147
|
+
include Laminar::Flow
|
148
|
+
|
149
|
+
before :setup # method symbol
|
150
|
+
before { ... } # block
|
151
|
+
|
152
|
+
after :teardown # method symbol
|
153
|
+
after { ... } # block
|
154
|
+
```
|
155
|
+
Callbacks execute in the order they are specified when there are multiple of the same kind.
|
156
|
+
|
157
|
+
### Flows
|
158
|
+
|
159
|
+
A flow is a chained sequence of particles, creating a simple workflow.
|
160
|
+
Each step (particle) contributes to an overall outcome through a shared
|
161
|
+
context. Simple branching and looping is supported via conditional
|
162
|
+
logic.
|
163
|
+
|
164
|
+
A flow includes `Laminar::Flow`, which provides a DSL for specifying
|
165
|
+
the particles to execute. The most basic flow is a simple set of steps executed sequentially.
|
166
|
+
|
167
|
+
```ruby
|
168
|
+
class FillCavity
|
169
|
+
include Laminar::Flow
|
170
|
+
|
171
|
+
flow do
|
172
|
+
step :numb_mouth
|
173
|
+
step :drill_cavity
|
174
|
+
step :apply_amalgam
|
175
|
+
end
|
176
|
+
end
|
177
|
+
```
|
178
|
+
|
179
|
+
A step label must be a symbol that identifies a Particle. By default,
|
180
|
+
the Flow assumes the step label is the implementation class name (i.e.,
|
181
|
+
`:numb_mouth` -> `NumbMouth`).
|
182
|
+
You can use the `class:` directive to specify an alternate class
|
183
|
+
name. Very useful when your particles
|
184
|
+
are organised into modules.
|
185
|
+
```ruby
|
186
|
+
class FillCavity
|
187
|
+
include Laminar::Flow
|
188
|
+
|
189
|
+
flow do
|
190
|
+
step :numb_mouth
|
191
|
+
step :drill_cavity, class: 'Dentist::Drill'
|
192
|
+
step :apply_amalgam
|
193
|
+
end
|
194
|
+
end
|
195
|
+
```
|
196
|
+
|
197
|
+
#### Invoking a Flow
|
198
|
+
Flows behave exactly like Particles in terms of execution. To start
|
199
|
+
a Flow, call `.call` on the Flow class, passing a Hash of context:
|
200
|
+
|
201
|
+
```ruby
|
202
|
+
// Flow
|
203
|
+
class FillCavity
|
204
|
+
include Laminar::Flow
|
205
|
+
|
206
|
+
flow do
|
207
|
+
step :numb_mouth
|
208
|
+
step :drill_cavity, class: 'Dentist::Drill'
|
209
|
+
step :apply_amalgam
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
// Caller
|
214
|
+
result = FillCavity.call(patient: patient, tooth_number: tooth)
|
215
|
+
```
|
216
|
+
|
217
|
+
A Flow returns the context as it stands after the final step in the
|
218
|
+
Flow ends. Because Flows behave exactly like Particles, they can be
|
219
|
+
nested as steps inside other flows without issue:
|
220
|
+
|
221
|
+
```ruby
|
222
|
+
class FillCavity
|
223
|
+
include Laminar::Flow
|
224
|
+
|
225
|
+
flow do
|
226
|
+
step :check_equipment # Flow
|
227
|
+
...
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
class CheckEquipment
|
232
|
+
include Laminar::Flow
|
233
|
+
|
234
|
+
flow do
|
235
|
+
...
|
236
|
+
end
|
237
|
+
end
|
238
|
+
```
|
239
|
+
|
240
|
+
#### Flow Branching
|
241
|
+
Ordinarily particle execution is sequential in the order specified.
|
242
|
+
However, you can optionally branch to a different label with `branch`.
|
243
|
+
```ruby
|
244
|
+
flow do
|
245
|
+
step :do_something do
|
246
|
+
branch :final_step
|
247
|
+
end
|
248
|
+
step :another_step # skipped
|
249
|
+
step :final_step
|
250
|
+
end
|
251
|
+
```
|
252
|
+
|
253
|
+
You can use the special symbol :endflow to jump terminate the flow
|
254
|
+
(skipping all remaining steps).
|
255
|
+
|
256
|
+
```ruby
|
257
|
+
flow do
|
258
|
+
step :do_something do
|
259
|
+
branch :endflow
|
260
|
+
end
|
261
|
+
step :another_step # skipped
|
262
|
+
step :final_step # skipped
|
263
|
+
end
|
264
|
+
```
|
265
|
+
|
266
|
+
#### Conditional Branching
|
267
|
+
|
268
|
+
Branches can be made conditional with the `if:` and `unless:`
|
269
|
+
directives.
|
270
|
+
|
271
|
+
```ruby
|
272
|
+
flow do
|
273
|
+
step :first do
|
274
|
+
branch :last_step, if: :done_early?
|
275
|
+
end
|
276
|
+
step :then_me
|
277
|
+
step :do_something
|
278
|
+
step :last_step
|
279
|
+
end
|
280
|
+
```
|
281
|
+
|
282
|
+
The target of `if:` or `unless:` is a symbol naming a method on the invoking Flow.
|
283
|
+
|
284
|
+
```ruby
|
285
|
+
flow do
|
286
|
+
step :first do
|
287
|
+
branch :last_step, if: :done_early?
|
288
|
+
end
|
289
|
+
...
|
290
|
+
end
|
291
|
+
|
292
|
+
def done_early?
|
293
|
+
!context[:finished].nil? && context[:finished] == true
|
294
|
+
end
|
295
|
+
```
|
296
|
+
|
297
|
+
A step can have multiple branch directives; the flow will take the first
|
298
|
+
branch that it finds that satisfies its specified condition (if any). If
|
299
|
+
no condition is satisfied, execution drops to the next step.
|
300
|
+
|
301
|
+
```ruby
|
302
|
+
flow do
|
303
|
+
step :first do
|
304
|
+
branch :last_step, if: :condition1?
|
305
|
+
branch :do_something, if: :condition2?
|
306
|
+
end
|
307
|
+
step :then_me # executed if neither condition1 nor condition2
|
308
|
+
step :do_something
|
309
|
+
step :last_step
|
310
|
+
end
|
311
|
+
```
|
312
|
+
|
313
|
+
#### Flow Callbacks
|
314
|
+
|
315
|
+
A flow can specify callback(s) to run before/after every step:
|
316
|
+
|
317
|
+
```ruby
|
318
|
+
class MyFlow
|
319
|
+
include Laminar::Flow
|
320
|
+
|
321
|
+
before_each :thing, :thing2 # method
|
322
|
+
before_each { ... } # block
|
323
|
+
|
324
|
+
after_each :thing, :thing2 # method
|
325
|
+
after_each { ... } # block
|
326
|
+
```
|
327
|
+
|
328
|
+
The order of execution for callbacks in a flow looks like:
|
329
|
+
|
330
|
+
```
|
331
|
+
flow's before
|
332
|
+
flow's before_each
|
333
|
+
step1's before
|
334
|
+
<step1 invoked>
|
335
|
+
step1's after
|
336
|
+
flow's after_each
|
337
|
+
flow's after
|
338
|
+
```
|
339
|
+
|
340
|
+
#### Testing Particles and Flows
|
341
|
+
|
342
|
+
TODO
|
343
|
+
|
344
|
+
## Contributing
|
345
|
+
|
346
|
+
Bug reports and pull requests are welcome on GitHub at
|
347
|
+
https://github.com/rmlockerd/laminar. This project is intended to be a
|
348
|
+
safe, welcoming space for collaboration, and contributors are expected to
|
349
|
+
adhere to the [Contributor Covenant](http://contributor-covenant.org) code
|
350
|
+
of conduct.
|
351
|
+
|
352
|
+
## License
|
353
|
+
|
354
|
+
The gem is available as open source under the terms of the
|
355
|
+
[MIT License](https://opensource.org/licenses/MIT).
|
356
|
+
|
357
|
+
## Code of Conduct
|
358
|
+
|
359
|
+
Everyone interacting in the Laminar project’s codebases, issue trackers,
|
360
|
+
chat rooms and mailing lists is expected to follow the
|
361
|
+
[code of conduct]
|
362
|
+
(https://github.com/rmlockerd/laminar/blob/master/CODE_OF_CONDUCT.md).
|