transactable 0.0.0 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/README.adoc +61 -38
- data/lib/transactable/steps/abstract.rb +0 -2
- data/transactable.gemspec +2 -2
- data.tar.gz.sig +0 -0
- metadata +4 -4
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c58179b7d8fa50cf463e7cbbf0fbaf83382d10478df233121400f110fcfee5dd
|
4
|
+
data.tar.gz: 417af3965b8ab1fb1421b619aa117abc129e173cea03e5931204e277250086c5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '029ca350dbfd66cf6e076887456434903c76c302b4906ea6f6c93371d2be0ed5c51b5035445e3519c7c6f72114c62f6285704c5ff520eb6af822d6f27ffc484c'
|
7
|
+
data.tar.gz: 6801347ce93c433a9a8a3fa28b03ce1ac47b9ee8c68bb5b37cab1b3fd222abd3d461af7164de2e99f85000ae977401c00943c094787f10c6a77871256a2f73a6
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data/README.adoc
CHANGED
@@ -1,14 +1,16 @@
|
|
1
|
+
:toc: macro
|
2
|
+
:toclevels: 5
|
3
|
+
:figure-caption!:
|
4
|
+
|
1
5
|
:command_pattern_link: link:https://www.alchemists.io/articles/command_pattern[Command Pattern]
|
6
|
+
:function_composition_link: link:https://www.alchemists.io/articles/ruby_function_composition[Function Composition]
|
7
|
+
:debug_link: link:https://github.com/ruby/debug[Debug]
|
2
8
|
:dry_container_link: link:https://dry-rb.org/gems/dry-container[Dry Container]
|
3
9
|
:dry_events_link: link:https://dry-rb.org/gems/dry-events[Dry Events]
|
4
10
|
:dry_monads_link: link:https://dry-rb.org/gems/dry-monads[Dry Monads]
|
5
11
|
:dry_schema_link: link:https://dry-rb.org/gems/dry-schema[Dry Schema]
|
6
12
|
:dry_validation_link: link:https://dry-rb.org/gems/dry-validation[Dry Validation]
|
7
13
|
|
8
|
-
:toc: macro
|
9
|
-
:toclevels: 5
|
10
|
-
:figure-caption!:
|
11
|
-
|
12
14
|
= Transactable
|
13
15
|
|
14
16
|
A DSL for transactional workflows built atop function composition. This allows you to write in a syntax that builds upon -- and abstracts away -- native function composition support while allowing you to cleanly read the code from left-to-right or top-to-bottom sequentially.
|
@@ -25,6 +27,7 @@ toc::[]
|
|
25
27
|
== Requirements
|
26
28
|
|
27
29
|
. link:https://www.ruby-lang.org[Ruby].
|
30
|
+
. A strong understanding of {function_composition_link}.
|
28
31
|
|
29
32
|
== Setup
|
30
33
|
|
@@ -138,9 +141,13 @@ The only problem with native function composition is that it reads backwards by
|
|
138
141
|
|
139
142
|
=== Steps
|
140
143
|
|
141
|
-
There are
|
144
|
+
There are several ways to compose steps for your transactional pipe. As long as all steps succeed, you'll get a successful response. Otherwise, the first step to fail will pass the failure down by skipping all subsequent steps (unless you dynamically attempt to turn the failure into a success). The following sections detail how to mix and match steps for building a robust implementation.
|
145
|
+
|
146
|
+
==== Basic
|
142
147
|
|
143
|
-
|
148
|
+
The following are the basic (default) steps for building for more advanced functionality.
|
149
|
+
|
150
|
+
===== As
|
144
151
|
|
145
152
|
Allows you to message the input as different output. Example:
|
146
153
|
|
@@ -151,7 +158,7 @@ pipe %i[a b c], as(:dig, 1) # Success :b
|
|
151
158
|
pipe Failure("Danger!"), as(:inspect) # Failure "Danger!"
|
152
159
|
----
|
153
160
|
|
154
|
-
|
161
|
+
===== Bind
|
155
162
|
|
156
163
|
Allows you to perform operations on a successful result only. You are then responsible for answering a success or failure accordingly. This is a convenience wrapper to native {dry_monads_link} `#bind` functionality. Example:
|
157
164
|
|
@@ -162,7 +169,7 @@ pipe %i[a b c], bind { |input| Failure input } # Failure [:a
|
|
162
169
|
pipe Failure("Danger!"), bind { |input| Success input.join("-") } # Failure "Danger!"
|
163
170
|
----
|
164
171
|
|
165
|
-
|
172
|
+
===== Check
|
166
173
|
|
167
174
|
Allows you to check if the input and messaged object evaluate to `true` or `Success`. When successful, input is passed through as a `Success`. When false, input is passed through as a `Failure`. Example:
|
168
175
|
|
@@ -173,7 +180,7 @@ pipe :a, check(%i[b c], :include?) # Failure :a
|
|
173
180
|
pipe Failure("Danger!"), check(%i[a b], :include?) # Failure "Danger!"
|
174
181
|
----
|
175
182
|
|
176
|
-
|
183
|
+
===== Fmap
|
177
184
|
|
178
185
|
Allows you to unwrap a successful operation, make a modification, and rewrap the modification as a new success. This is a convenience wrapper to native {dry_monads_link} `#fmap` functionality. Example:
|
179
186
|
|
@@ -183,7 +190,7 @@ pipe %i[a b c], fmap { |input| input.join "-" } # Success "a-b-c"
|
|
183
190
|
pipe Failure("Danger!"), fmap { |input| input.join "-" } # Failure "Danger!"
|
184
191
|
----
|
185
192
|
|
186
|
-
|
193
|
+
===== Insert
|
187
194
|
|
188
195
|
Allows you to insert an element after the input (default behavior) and wraps native link:https://rubyapi.org/o/array#method-i-insert[Array#insert] functionality. If the input is not an array, it will be cast as one. You can use the `:at` key to specify where you want insertion to happen. This step is most useful when needing to assemble arguments for passing to a subsequent step. Example:
|
189
196
|
|
@@ -195,7 +202,7 @@ pipe %i[a c], insert(:b, at: 1) # Success [:a, :b, :c]
|
|
195
202
|
pipe Failure("Danger!"), insert(:b) # Failure "Danger!"
|
196
203
|
----
|
197
204
|
|
198
|
-
|
205
|
+
===== Map
|
199
206
|
|
200
207
|
Allows you to map over an enumerable and wraps native link:https://rubyapi.org/o/enumerable#method-i-map[Enumerable#map] functionality.
|
201
208
|
|
@@ -205,7 +212,7 @@ pipe %i[a b c], map(&:inspect) # Success [":a", ":b", ":c"]
|
|
205
212
|
pipe Failure("Danger!"), map(&:inspect) # Failure "Danger!"
|
206
213
|
----
|
207
214
|
|
208
|
-
|
215
|
+
===== Merge
|
209
216
|
|
210
217
|
Allows you to merge the input with additional attributes as a single hash. If the input is not a hash, then the input will be merged with the attributes using `step` as the key. The default `step` key can be renamed to a different key by using the `:as` key. Like the _Insert_ step, this is most useful when needing to assemble arguments and/or data for consumption by subsequent steps. Example:
|
211
218
|
|
@@ -217,7 +224,7 @@ pipe "test", merge(as: :a, b: 2) # Success {a: "test", b: 2}
|
|
217
224
|
pipe Failure("Danger!"), merge(b: 2) # Failure "Danger!"
|
218
225
|
----
|
219
226
|
|
220
|
-
|
227
|
+
===== Orr
|
221
228
|
|
222
229
|
Allows you to operate on a failure and produce either a success or another failure. This is a convenience wrapper to native {dry_monads_link} `#or` functionality.
|
223
230
|
|
@@ -232,7 +239,7 @@ pipe Failure("Danger!"), orr { Success "Resolved" } # Success "Reso
|
|
232
239
|
pipe Failure("Danger!"), orr { |input| Failure "Big #{input}" } # Failure "Big Danger!"
|
233
240
|
----
|
234
241
|
|
235
|
-
|
242
|
+
===== Tee
|
236
243
|
|
237
244
|
Allows you to run an operation and ignore the response while input is passed through as output. This behavior is similar in nature to the link:https://www.gnu.org/savannah-checkouts/gnu/gawk/manual/html_node/Tee-Program.html[tee] program in Bash. Example:
|
238
245
|
|
@@ -249,7 +256,7 @@ pipe Failure("Danger!"), tee(Kernel, :puts, "Example.")
|
|
249
256
|
# Failure "Danger!"
|
250
257
|
----
|
251
258
|
|
252
|
-
|
259
|
+
===== To
|
253
260
|
|
254
261
|
Allows you to delegate to an object -- which doesn't have a callable interface and may or may not answer a result -- for processing of input. If the response is not a monad, it'll be automatically wrapped as a `Success`. Example:
|
255
262
|
|
@@ -265,7 +272,7 @@ pipe({label: "Test"}, to(Model, :for)) # Success #<struct Model label="Test">
|
|
265
272
|
pipe Failure("Danger!"), to(Model, :for) # Failure "Danger!"
|
266
273
|
----
|
267
274
|
|
268
|
-
|
275
|
+
===== Try
|
269
276
|
|
270
277
|
Allows you to try an operation which may fail while catching the exception as a failure for further processing. Example:
|
271
278
|
|
@@ -276,7 +283,7 @@ pipe "test", try(:invalid, catch: NoMethodError) # Failure "undefined me
|
|
276
283
|
pipe Failure("Danger!"), try(:to_json, catch: JSON::ParserError) # Failure "Danger!"
|
277
284
|
----
|
278
285
|
|
279
|
-
|
286
|
+
===== Use
|
280
287
|
|
281
288
|
Allows you to use another transaction which might have multiple steps of it's own, use an object that adheres to the {command_pattern_link}, or any function which answers a {dry_monads_link} `Result` object. In other words, you can use _use_ any object which responds to `#call` and answers a {dry_monads_link} `Result` object. This is great for chaining multiple transactions together.
|
282
289
|
|
@@ -288,7 +295,7 @@ pipe 3, use(function) # Success 9
|
|
288
295
|
pipe Failure("Danger!"), use(function) # Failure "Danger!"
|
289
296
|
----
|
290
297
|
|
291
|
-
|
298
|
+
===== Validate
|
292
299
|
|
293
300
|
Allows you to use an operation that will validate the input. This is especially useful when using {dry_schema_link}, {dry_validation_link}, or any operation that can respond to `#call` while answering a result that can be converted into a hash.
|
294
301
|
|
@@ -303,11 +310,11 @@ pipe({label: "Test"}, validate(schema, as: nil)) # Success #<Dry::Schema::Resul
|
|
303
310
|
pipe Failure("Danger!"), validate(schema) # Failure "Danger!"
|
304
311
|
----
|
305
312
|
|
306
|
-
|
313
|
+
==== Advanced
|
307
314
|
|
308
|
-
|
315
|
+
Several options are available should you need to advance beyond the basic steps. Each is described in detail below.
|
309
316
|
|
310
|
-
|
317
|
+
===== Procs
|
311
318
|
|
312
319
|
You can always use a `Proc` as a custom step. Example:
|
313
320
|
|
@@ -326,7 +333,7 @@ pipe :a,
|
|
326
333
|
|
327
334
|
ℹ️ While procs are effective, you are limited in what you can do with them in terms of additional behavior and instrumentation support.
|
328
335
|
|
329
|
-
|
336
|
+
===== Lambdas
|
330
337
|
|
331
338
|
In addition to procs, lambdas can be used too. Example:
|
332
339
|
|
@@ -344,7 +351,7 @@ pipe :a,
|
|
344
351
|
|
345
352
|
ℹ️ Lambdas are a step up from procs but, like procs, you are limited in what you can do with them in terms of additional behavior and instrumentation support.
|
346
353
|
|
347
|
-
|
354
|
+
===== Methods
|
348
355
|
|
349
356
|
Methods -- in addition to procs and lambdas -- are the _preferred_ way to add custom steps due to the concise syntax. Example:
|
350
357
|
|
@@ -370,9 +377,9 @@ Demo.new.call :a # Yields: Success :a_b
|
|
370
377
|
|
371
378
|
ℹ️ You won't be able to instrument these method calls (unless you inject instrumentation) but are great when needing additional behavior between the default steps.
|
372
379
|
|
373
|
-
|
380
|
+
===== Custom
|
374
381
|
|
375
|
-
If you'd like to define
|
382
|
+
If you'd like to define permanent and reusable step, you can register a custom step which requires you to:
|
376
383
|
|
377
384
|
. Define a custom step as a new class.
|
378
385
|
. Register your custom step along side the existing default steps.
|
@@ -386,7 +393,7 @@ module MySteps
|
|
386
393
|
prepend Transactable::Instrumentable
|
387
394
|
|
388
395
|
def initialize delimiter = "_", **dependencies
|
389
|
-
super
|
396
|
+
super(**dependencies)
|
390
397
|
@delimiter = delimiter
|
391
398
|
end
|
392
399
|
|
@@ -417,9 +424,9 @@ pipe :a,
|
|
417
424
|
# Yields: Success :ab
|
418
425
|
----
|
419
426
|
|
420
|
-
|
427
|
+
=== Containers
|
421
428
|
|
422
|
-
Should you not want the
|
429
|
+
Should you not want the basic steps, need custom steps, or a hybrid of basic and custom steps, you can define your own container and provide it as an argument to `.with` when including transactable behavior. Example:
|
423
430
|
|
424
431
|
[source,ruby]
|
425
432
|
----
|
@@ -488,10 +495,12 @@ There is a lot you can do with instrumentation so check out the {dry_events_link
|
|
488
495
|
|
489
496
|
== Development
|
490
497
|
|
491
|
-
To
|
498
|
+
To contribute, run:
|
492
499
|
|
493
500
|
[source,bash]
|
494
501
|
----
|
502
|
+
git clone https://github.com/bkuhlmann/transactable
|
503
|
+
cd transactable
|
495
504
|
bin/setup
|
496
505
|
----
|
497
506
|
|
@@ -506,7 +515,7 @@ bin/console
|
|
506
515
|
|
507
516
|
The architecture of this gem is built on top of the following concepts and gems:
|
508
517
|
|
509
|
-
*
|
518
|
+
* {function_composition_link}: Made possible through the use of the `\#>>` and `#<<` methods on the link:https://rubyapi.org/3.1/o/method[Method] and link:https://rubyapi.org/3.1/o/proc[Proc] objects.
|
510
519
|
* {dry_container_link} - Allows related dependencies to be grouped together for injection.
|
511
520
|
* {dry_events_link} - Allows all steps to be observable so you can subscribe to any/all events for metric, logging, and other capabilities.
|
512
521
|
* {dry_monads_link} - Critical to ensuring the entire pipeline of steps adhere to the _Railway Pattern_ and leans heavily on the `Result` object.
|
@@ -519,34 +528,39 @@ The architecture of this gem is built on top of the following concepts and gems:
|
|
519
528
|
* *Transactions*
|
520
529
|
** Use a single method (i.e. `#call`) which is public and adheres to the {command_pattern_link} so transactions can be piped together if desired.
|
521
530
|
* *Steps*
|
522
|
-
** Inherit from the `Abstract` class in order to gain monad, composition, and dependency behavior. Any dependencies injected are automatically filtered out so all subclasses have direct and clean access to the
|
531
|
+
** Inherit from the `Abstract` class in order to gain monad, composition, and dependency behavior. Any dependencies injected are automatically filtered out so all subclasses have direct and clean access to the base positional, keyword, and block arguments. These variables are prefixed with `base_*` in order to not conflict with subclasses which might only want to use non-prefixed variables for convenience.
|
523
532
|
** All filtered arguments -- in other words, the unused arguments -- need to be passed up to the superclass from the subclass (i.e. `super(*positionals, **keywords, &block)`). Doing so allows the superclass (i.e. `Abstract`) to provide access to `base_positionals`, `base_keywords`, and `base_block` for use if desired by the subclass.
|
524
533
|
** Prepend `Instrumentable` to gain instrumentation behavior and remain consistent with existing steps. This includes adding the `with instrumentation` RSpec shared context when testing too.
|
525
534
|
** The `#call` method must define a single positional `result` parameter since a monad will be passed as an argument. Example: `def call(result) = # Implementation`.
|
526
535
|
** Each block within the `#call` method should use the `input` parameter to be consistent. More specific parameters like `argument` or `operation` should be used to improve readability when possible. Example: `def call(result) = result.bind { |input| # Implementation }`.
|
527
536
|
** Use implicit blocks sparingly. Most of the default steps shy away from using blocks because it can make the code more complex. Use private methods, custom steps, and/or separate transactions if the code becomes too complex because you might have a smaller object which needs extraction.
|
528
537
|
|
529
|
-
|
538
|
+
=== Debugging
|
530
539
|
|
531
|
-
|
540
|
+
If you need to debug (i.e. {debug_link}) your pipe, use a lambda. Example:
|
532
541
|
|
533
|
-
[source,
|
542
|
+
[source,ruby]
|
534
543
|
----
|
535
|
-
|
544
|
+
pipe data,
|
545
|
+
check(/Book.+Price/, :match?),
|
546
|
+
-> result { binding.break }, # Breakpoint
|
547
|
+
method(:parse),
|
536
548
|
----
|
537
549
|
|
538
|
-
|
550
|
+
The above breakpoint will allow you inspect the result of the `#check` step and/or build a modified result for passing to the subsequent `#method` step.
|
551
|
+
|
552
|
+
=== Troubleshooting
|
539
553
|
|
540
554
|
The following might be of aid to as you implement your own transactions.
|
541
555
|
|
542
|
-
|
556
|
+
==== Type Errors
|
543
557
|
|
544
558
|
If you get a `TypeError: Step must be functionally composable and answer a monad`, it means:
|
545
559
|
|
546
560
|
. The step must be a `Proc` or some object which responds to `\#>>`, `#<<`, and `#call`.
|
547
561
|
. The step doesn't answer a result monad (i.e. `Success some_value` or `Failure some_value`).
|
548
562
|
|
549
|
-
|
563
|
+
==== No Method Errors
|
550
564
|
|
551
565
|
If you get a `NoMethodError: undefined method `success?` exception, it might mean that you forgot to add a comma after one of your steps. Example:
|
552
566
|
|
@@ -563,6 +577,15 @@ pipe "https://www.wikipedia.org",
|
|
563
577
|
try(:parse, catch: HTTP::Error)
|
564
578
|
----
|
565
579
|
|
580
|
+
== Tests
|
581
|
+
|
582
|
+
To test, run:
|
583
|
+
|
584
|
+
[source,bash]
|
585
|
+
----
|
586
|
+
bundle exec rake
|
587
|
+
----
|
588
|
+
|
566
589
|
== link:https://www.alchemists.io/policies/license[License]
|
567
590
|
|
568
591
|
== link:https://www.alchemists.io/policies/security[Security]
|
@@ -8,9 +8,7 @@ module Transactable
|
|
8
8
|
class Abstract
|
9
9
|
DEPENDENCIES = %i[instrument marameters].freeze
|
10
10
|
|
11
|
-
# rubocop:todo Layout/ClassStructure
|
12
11
|
include Import[*DEPENDENCIES]
|
13
|
-
# rubocop:enable Layout/ClassStructure
|
14
12
|
include Dry::Monads[:result]
|
15
13
|
include Composable
|
16
14
|
|
data/transactable.gemspec
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |spec|
|
4
4
|
spec.name = "transactable"
|
5
|
-
spec.version = "0.
|
5
|
+
spec.version = "0.1.0"
|
6
6
|
spec.authors = ["Brooke Kuhlmann"]
|
7
7
|
spec.email = ["brooke@alchemists.io"]
|
8
8
|
spec.homepage = "https://www.alchemists.io/projects/transactable"
|
@@ -24,7 +24,7 @@ Gem::Specification.new do |spec|
|
|
24
24
|
|
25
25
|
spec.required_ruby_version = "~> 3.1"
|
26
26
|
|
27
|
-
spec.add_dependency "dry-container", "~> 0.
|
27
|
+
spec.add_dependency "dry-container", "~> 0.11"
|
28
28
|
spec.add_dependency "dry-events", "~> 0.3"
|
29
29
|
spec.add_dependency "dry-monads", "~> 1.4"
|
30
30
|
spec.add_dependency "infusible", "~> 0.0"
|
data.tar.gz.sig
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: transactable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brooke Kuhlmann
|
@@ -28,7 +28,7 @@ cert_chain:
|
|
28
28
|
CxDe2+VuChj4I1nvIHdu+E6XoEVlanUPKmSg6nddhkKn2gC45Kyzh6FZqnzH/CRp
|
29
29
|
RFE=
|
30
30
|
-----END CERTIFICATE-----
|
31
|
-
date: 2022-09-
|
31
|
+
date: 2022-09-17 00:00:00.000000000 Z
|
32
32
|
dependencies:
|
33
33
|
- !ruby/object:Gem::Dependency
|
34
34
|
name: dry-container
|
@@ -36,14 +36,14 @@ dependencies:
|
|
36
36
|
requirements:
|
37
37
|
- - "~>"
|
38
38
|
- !ruby/object:Gem::Version
|
39
|
-
version: '0.
|
39
|
+
version: '0.11'
|
40
40
|
type: :runtime
|
41
41
|
prerelease: false
|
42
42
|
version_requirements: !ruby/object:Gem::Requirement
|
43
43
|
requirements:
|
44
44
|
- - "~>"
|
45
45
|
- !ruby/object:Gem::Version
|
46
|
-
version: '0.
|
46
|
+
version: '0.11'
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: dry-events
|
49
49
|
requirement: !ruby/object:Gem::Requirement
|
metadata.gz.sig
CHANGED
Binary file
|