laminar 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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.
@@ -0,0 +1,13 @@
1
+ *.gem
2
+ Gemfile.lock
3
+ /.bundle/
4
+ /.yardoc
5
+ /_yardoc/
6
+ /coverage/
7
+ /doc/
8
+ /pkg/
9
+ /spec/reports/
10
+ /tmp/
11
+
12
+ # rspec failure tracking
13
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,13 @@
1
+ ---
2
+ sudo: false
3
+ language: ruby
4
+ cache: bundler
5
+ rvm:
6
+ - 2.3
7
+ - 2.4
8
+ - 2.5
9
+ before_install: gem install bundler -v 1.16.5
10
+ script:
11
+ - bundle exec rspec
12
+ after_script:
13
+ - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT
@@ -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)*
@@ -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
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in laminar.gemspec
6
+ gemspec
@@ -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.
@@ -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).