pipeable 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/README.adoc +215 -93
- data/lib/pipeable/steps/abstract.rb +1 -3
- data/lib/pipeable/steps/amap.rb +10 -0
- data/lib/pipeable/steps/as.rb +2 -2
- data/lib/pipeable/steps/bind.rb +1 -1
- data/lib/pipeable/steps/check.rb +13 -11
- data/lib/pipeable/steps/container.rb +2 -1
- data/lib/pipeable/steps/fmap.rb +1 -1
- data/lib/pipeable/steps/insert.rb +3 -3
- data/lib/pipeable/steps/map.rb +1 -1
- data/lib/pipeable/steps/merge.rb +7 -7
- data/lib/pipeable/steps/or.rb +1 -1
- data/lib/pipeable/steps/tee.rb +1 -1
- data/lib/pipeable/steps/to.rb +8 -6
- data/lib/pipeable/steps/try.rb +5 -5
- data/lib/pipeable/steps/use.rb +5 -5
- data/lib/pipeable/steps/validate.rb +10 -14
- data/pipeable.gemspec +1 -1
- data.tar.gz.sig +0 -0
- metadata +4 -3
- 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: 4c03f09d3e8268f1786faa5907fe4f9dbc4beaf02c874bec73b683fd1e4a51de
|
|
4
|
+
data.tar.gz: 29943bc0fed70a2661cee558e5a4d23090af2fe216ba085ec1ff00e3da435ea3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ebaeb186fb3c672501fa3b5414b2d4fe381a45fa22004ae2f128b80abc15d012f0ff70befb900ad75d82b760af33fa4a35c7db5d3113e7f3f261782f01e7dd24
|
|
7
|
+
data.tar.gz: 41fe4361d2a0226ceeb8796d1edc9e6fd0e19bc00539c3d3c4e87c57d09a871818d051d7a8d881b294c0f3ea8e73f2575a0ead5dcaaf3d44dbbb0dd53065d749
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/README.adoc
CHANGED
|
@@ -10,17 +10,18 @@
|
|
|
10
10
|
:dry_validation_link: link:https://dry-rb.org/gems/dry-validation[Dry Validation]
|
|
11
11
|
:function_composition_link: link:https://alchemists.io/articles/ruby_function_composition[Function Composition]
|
|
12
12
|
:infusible_link: link:https://alchemists.io/projects/infusible[Infusible]
|
|
13
|
+
:method_parameters_and_arguments_link: link:https://alchemists.io/articles/ruby_method_parameters_and_arguments[Method Parameters And Arguments]
|
|
13
14
|
:railway_pattern_link: link:https://fsharpforfunandprofit.com/posts/recipe-part2[Railway Pattern]
|
|
14
15
|
|
|
15
16
|
= Pipeable
|
|
16
17
|
|
|
17
|
-
A DSL for workflows built atop native {function_composition_link} which leverages the {railway_pattern_link}. This allows you to write a sequence of _steps_ that cleanly read from
|
|
18
|
+
A DSL for workflows built atop native {function_composition_link} which leverages the {railway_pattern_link}. This allows you to write a sequence of _steps_ that cleanly read from top-to-bottom or left-to-right resulting in a single success or a failure. This allows you to avoid relying on exceptions for expensive control flows and/or complex conditional logic in general.
|
|
18
19
|
|
|
19
20
|
toc::[]
|
|
20
21
|
|
|
21
22
|
== Features
|
|
22
23
|
|
|
23
|
-
* Built atop
|
|
24
|
+
* Built atop native {function_composition_link}.
|
|
24
25
|
* Adheres to the {railway_pattern_link}.
|
|
25
26
|
* Provides built-in and customizable domain-specific steps.
|
|
26
27
|
* Provides chainable _pipes_ which can be used to build more complex workflows.
|
|
@@ -29,7 +30,7 @@ toc::[]
|
|
|
29
30
|
== Requirements
|
|
30
31
|
|
|
31
32
|
. link:https://www.ruby-lang.org[Ruby].
|
|
32
|
-
. A strong understanding of {function_composition_link}.
|
|
33
|
+
. A strong understanding of {function_composition_link} and {method_parameters_and_arguments_link}.
|
|
33
34
|
|
|
34
35
|
== Setup
|
|
35
36
|
|
|
@@ -65,7 +66,7 @@ require "pipeable"
|
|
|
65
66
|
|
|
66
67
|
== Usage
|
|
67
68
|
|
|
68
|
-
You can turn any object into a
|
|
69
|
+
You can turn any object into a _pipe_ by requiring and including this gem as follows:
|
|
69
70
|
|
|
70
71
|
[source,ruby]
|
|
71
72
|
----
|
|
@@ -100,7 +101,7 @@ class Demo
|
|
|
100
101
|
end
|
|
101
102
|
----
|
|
102
103
|
|
|
103
|
-
The above allows `Demo#call` to be a sequence steps which may pass or fail due to all
|
|
104
|
+
The above allows `Demo#call` to be a sequence of steps which may pass or fail due to all steps using {dry_monads_link} for input and output. This is the essence of the {railway_pattern_link}.
|
|
104
105
|
|
|
105
106
|
To execute the above example, you'd only need to pass CSV content to it:
|
|
106
107
|
|
|
@@ -130,7 +131,7 @@ pipe(input, *steps)
|
|
|
130
131
|
|
|
131
132
|
The first argument is your input which can be a Ruby primitive or a monad. Regardless, the input will be automatically wrapped as a `Success` -- but only if not a `Result` to begin with -- before passing to the first step. From there, all steps are _required_ to answer a monad in order to adhere to the {railway_pattern_link}.
|
|
132
133
|
|
|
133
|
-
Behind the scenes, the `#pipe` method is syntactic sugar
|
|
134
|
+
Behind the scenes, the `#pipe` method is syntactic sugar built atop {function_composition_link} which means if this code were to be rewritten:
|
|
134
135
|
|
|
135
136
|
[source,ruby]
|
|
136
137
|
----
|
|
@@ -140,7 +141,7 @@ pipe csv,
|
|
|
140
141
|
map { |item| "#{item[:book]}: #{item[:price]}" }
|
|
141
142
|
----
|
|
142
143
|
|
|
143
|
-
|
|
144
|
+
...then the above would look like the following (as rewritten in native Ruby):
|
|
144
145
|
|
|
145
146
|
[source,ruby]
|
|
146
147
|
----
|
|
@@ -151,19 +152,53 @@ Then the above would look like this using native Ruby:
|
|
|
151
152
|
).call Success(csv)
|
|
152
153
|
----
|
|
153
154
|
|
|
154
|
-
|
|
155
|
+
Visually, the pipe can be diagramed as follows:
|
|
156
|
+
|
|
157
|
+
image::https://alchemists.io/images/projects/pipeable/diagrams/pipe.png[A diagram of pipe steps,width=591,height=734,role=focal_point]
|
|
158
|
+
|
|
159
|
+
The problem with native function composition is that it reads backwards by passing input at the end of all sequential steps. With the `#pipe` method, you have the benefit of allowing your eyes to read from top to bottom while not having to type multiple _forward composition_ operators.
|
|
155
160
|
|
|
156
161
|
=== Steps
|
|
157
162
|
|
|
158
|
-
There are several ways to compose steps for your 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
|
|
163
|
+
There are several ways to compose steps for your 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 turn the failure into a success). Each step can be initialized and called:
|
|
164
|
+
|
|
165
|
+
* `+#initialize+`: Arguments vary per step but can be positional, keyword, and/or block arguments. This is how you _customize_ the behavior of each step.
|
|
166
|
+
* `+#call+`: Expects a {dry_monads_link} `Result` object as input. The output is either the same or new `Result` object for consumption by the next step in the pipe. Additionally, each step will either unwrap the `Result` or pass the `Result` through depending on the step's implementation (as detailed below).
|
|
159
167
|
|
|
160
168
|
==== Basic
|
|
161
169
|
|
|
162
|
-
The following are the basic (default) steps for building for
|
|
170
|
+
The following are the basic (default) steps for building custom pipes for which you can mix and match within your own implementation.
|
|
171
|
+
|
|
172
|
+
===== alt
|
|
173
|
+
|
|
174
|
+
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.
|
|
175
|
+
|
|
176
|
+
Accepts a failure while answering either a success or failure. Example:
|
|
177
|
+
|
|
178
|
+
[source,ruby]
|
|
179
|
+
----
|
|
180
|
+
pipe %i[a b c], alt { |object| Success object.join("-") } # Success [:a, :b, :c]
|
|
181
|
+
pipe Failure("Danger!"), alt { Success "Resolved" } # Success "Resolved"
|
|
182
|
+
pipe Failure("Danger!"), alt { |object| Failure "Big #{object}" } # Failure "Big Danger!"
|
|
183
|
+
----
|
|
184
|
+
|
|
185
|
+
===== amap
|
|
163
186
|
|
|
164
|
-
|
|
187
|
+
Allows you to unwrap a failure, make a modification, and wrap the modification as a new failure. This is a convenience wrapper to native {dry_monads_link} `#alt_map` functionality.
|
|
165
188
|
|
|
166
|
-
|
|
189
|
+
Accepts and answers a failure. Example:
|
|
190
|
+
|
|
191
|
+
[source,ruby]
|
|
192
|
+
----
|
|
193
|
+
pipe Failure("Danger"), amap { |object| "#{object}!" } # Failure "Danger!"
|
|
194
|
+
pipe Success("Pass"), amap { |object| "#{object}!" } # Success "Pass"
|
|
195
|
+
----
|
|
196
|
+
|
|
197
|
+
===== as
|
|
198
|
+
|
|
199
|
+
Allows you to message an object as a different result. The first argument is the method but additional positional and/or keyword arguments can be passed along if the method accepts them.
|
|
200
|
+
|
|
201
|
+
Accepts and answers a success. Example:
|
|
167
202
|
|
|
168
203
|
[source,ruby]
|
|
169
204
|
----
|
|
@@ -172,20 +207,24 @@ pipe %i[a b c], as(:dig, 1) # Success :b
|
|
|
172
207
|
pipe Failure("Danger!"), as(:inspect) # Failure "Danger!"
|
|
173
208
|
----
|
|
174
209
|
|
|
175
|
-
=====
|
|
210
|
+
===== bind
|
|
211
|
+
|
|
212
|
+
Allows you to perform operations upon success only. You are then responsible for answering a success or failure accordingly. This is a convenience wrapper to native {dry_monads_link} `#bind` functionality.
|
|
176
213
|
|
|
177
|
-
|
|
214
|
+
Accepts a success while answering either a success or failure. Example:
|
|
178
215
|
|
|
179
216
|
[source,ruby]
|
|
180
217
|
----
|
|
181
|
-
pipe %i[a b c], bind { |
|
|
182
|
-
pipe %i[a b c], bind { |
|
|
183
|
-
pipe Failure("Danger!"), bind { |
|
|
218
|
+
pipe %i[a b c], bind { |object| Success object.join("-") } # Success "a-b-c"
|
|
219
|
+
pipe %i[a b c], bind { |object| Failure object } # Failure [:a, :b, :c]
|
|
220
|
+
pipe Failure("Danger!"), bind { |object| Success object.join("-") } # Failure "Danger!"
|
|
184
221
|
----
|
|
185
222
|
|
|
186
|
-
=====
|
|
223
|
+
===== check
|
|
224
|
+
|
|
225
|
+
Allows you to check if an object matches the proof (with message). The first argument is your proof while the second argument is the message to send to your proof. A check only passes if the messaged object evaluates to `true` or `Success`. When successful, the object is passed through as a `Success`. When false, the object is passed through as a `Failure`.
|
|
187
226
|
|
|
188
|
-
|
|
227
|
+
Accepts a success while answering a success or failure depending on whether unwrapped object checks against the proof. Example:
|
|
189
228
|
|
|
190
229
|
[source,ruby]
|
|
191
230
|
----
|
|
@@ -194,19 +233,23 @@ pipe :a, check(%i[b c], :include?) # Failure :a
|
|
|
194
233
|
pipe Failure("Danger!"), check(%i[a b], :include?) # Failure "Danger!"
|
|
195
234
|
----
|
|
196
235
|
|
|
197
|
-
=====
|
|
236
|
+
===== fmap
|
|
198
237
|
|
|
199
|
-
Allows you to unwrap a
|
|
238
|
+
Allows you to unwrap a success, make a modification, and wrap the modification as a new success. This is a convenience wrapper to native {dry_monads_link} `#fmap` functionality.
|
|
239
|
+
|
|
240
|
+
Accepts and answers a success. Example:
|
|
200
241
|
|
|
201
242
|
[source,ruby]
|
|
202
243
|
----
|
|
203
|
-
pipe %i[a b c], fmap { |
|
|
204
|
-
pipe Failure("Danger!"), fmap { |
|
|
244
|
+
pipe %i[a b c], fmap { |object| object.join "-" } # Success "a-b-c"
|
|
245
|
+
pipe Failure("Danger!"), fmap { |object| object.join "-" } # Failure "Danger!"
|
|
205
246
|
----
|
|
206
247
|
|
|
207
|
-
=====
|
|
248
|
+
===== insert
|
|
249
|
+
|
|
250
|
+
Allows you to insert an element after an object (default behavior) as a single array. This step wraps native link:https://rubyapi.org/o/array#method-i-insert[Array#insert] functionality. If the object 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 _positional_ arguments for passing as an array to a subsequent step.
|
|
208
251
|
|
|
209
|
-
|
|
252
|
+
Accepts and answers a success. Example:
|
|
210
253
|
|
|
211
254
|
[source,ruby]
|
|
212
255
|
----
|
|
@@ -216,9 +259,11 @@ pipe %i[a c], insert(:b, at: 1) # Success [:a, :b, :c]
|
|
|
216
259
|
pipe Failure("Danger!"), insert(:b) # Failure "Danger!"
|
|
217
260
|
----
|
|
218
261
|
|
|
219
|
-
=====
|
|
262
|
+
===== map
|
|
263
|
+
|
|
264
|
+
Allows you to map over an object (enumerable) by wrapping native link:https://rubyapi.org/o/enumerable#method-i-map[Enumerable#map] functionality.
|
|
220
265
|
|
|
221
|
-
|
|
266
|
+
Accepts and answers a success. Example:
|
|
222
267
|
|
|
223
268
|
[source,ruby]
|
|
224
269
|
----
|
|
@@ -226,9 +271,11 @@ pipe %i[a b c], map(&:inspect) # Success [":a", ":b", ":c"]
|
|
|
226
271
|
pipe Failure("Danger!"), map(&:inspect) # Failure "Danger!"
|
|
227
272
|
----
|
|
228
273
|
|
|
229
|
-
=====
|
|
274
|
+
===== merge
|
|
230
275
|
|
|
231
|
-
Allows you to merge
|
|
276
|
+
Allows you to merge an object with additional attributes as a single hash. This step wraps native link:https://rubyapi.org/o/hash#method-i-merge[Hash#merge] functionality. If the input is not a hash, then the object will be merged with `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 step is most useful when assembling _keyword_ arguments and/or a hash for a subsequent steps.
|
|
277
|
+
|
|
278
|
+
Accepts and answers a success. Example:
|
|
232
279
|
|
|
233
280
|
[source,ruby]
|
|
234
281
|
----
|
|
@@ -238,24 +285,11 @@ pipe "test", merge(as: :a, b: 2) # Success {a: "test", b: 2}
|
|
|
238
285
|
pipe Failure("Danger!"), merge(b: 2) # Failure "Danger!"
|
|
239
286
|
----
|
|
240
287
|
|
|
241
|
-
=====
|
|
242
|
-
|
|
243
|
-
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.
|
|
244
|
-
|
|
245
|
-
ℹ️ Syntactically, `or` can't be used for this step since `or` is a native Ruby keyword so `orr` is used instead.
|
|
246
|
-
|
|
247
|
-
Example:
|
|
248
|
-
|
|
249
|
-
[source,ruby]
|
|
250
|
-
----
|
|
251
|
-
pipe %i[a b c], orr { |input| Success input.join("-") } # Success [:a, :b, :c]
|
|
252
|
-
pipe Failure("Danger!"), orr { Success "Resolved" } # Success "Resolved"
|
|
253
|
-
pipe Failure("Danger!"), orr { |input| Failure "Big #{input}" } # Failure "Big Danger!"
|
|
254
|
-
----
|
|
288
|
+
===== tee
|
|
255
289
|
|
|
256
|
-
|
|
290
|
+
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.
|
|
257
291
|
|
|
258
|
-
|
|
292
|
+
Accepts either a success or failure and passes the result through while allowing you to execute arbitrary behavior. Example:
|
|
259
293
|
|
|
260
294
|
[source,ruby]
|
|
261
295
|
----
|
|
@@ -270,60 +304,83 @@ pipe Failure("Danger!"), tee(Kernel, :puts, "Example.")
|
|
|
270
304
|
# Failure "Danger!"
|
|
271
305
|
----
|
|
272
306
|
|
|
273
|
-
=====
|
|
307
|
+
===== to
|
|
274
308
|
|
|
275
|
-
Allows you to delegate to an object
|
|
309
|
+
Allows you to delegate to an object which doesn't have a callable interface and may or may not answer a result. If the response is not a monad, it'll be automatically wrapped as a `Success`.
|
|
310
|
+
|
|
311
|
+
Accepts a success while sending the unwrapped object to the given object's corresponding method. The object is expected to answer either a plain Ruby object which will be automatically wrapped as a success or a {dry_monads_link} `Result`. Example:
|
|
276
312
|
|
|
277
313
|
[source,ruby]
|
|
278
314
|
----
|
|
279
|
-
Model = Struct.new :label
|
|
315
|
+
Model = Struct.new :label do
|
|
280
316
|
include Dry::Monads[:result]
|
|
281
317
|
|
|
282
|
-
def self.for(
|
|
318
|
+
def self.for(**) = Success new(**)
|
|
283
319
|
end
|
|
284
320
|
|
|
285
321
|
pipe({label: "Test"}, to(Model, :for)) # Success #<struct Model label="Test">
|
|
286
322
|
pipe Failure("Danger!"), to(Model, :for) # Failure "Danger!"
|
|
287
323
|
----
|
|
288
324
|
|
|
289
|
-
=====
|
|
325
|
+
===== try
|
|
326
|
+
|
|
327
|
+
Allows you to try an operation which may fail while catching any exceptions as a failure for further processing. You can catch a single exception by providing the exception as a single value or multiple exceptions as an array of values.
|
|
290
328
|
|
|
291
|
-
|
|
329
|
+
Accepts and answers a success if there are no exceptions. Otherwise, captures any error as a failure. Example:
|
|
292
330
|
|
|
293
331
|
[source,ruby]
|
|
294
332
|
----
|
|
295
|
-
pipe "test", try(:to_json, catch: JSON::ParserError)
|
|
296
|
-
|
|
297
|
-
|
|
333
|
+
pipe "test", try(:to_json, catch: JSON::ParserError)
|
|
334
|
+
# Success "\"test\""
|
|
335
|
+
|
|
336
|
+
pipe "test", try(:to_json, catch: [JSON::ParserError, StandardError])
|
|
337
|
+
# Success "\"test\""
|
|
338
|
+
|
|
339
|
+
pipe "test", try(:invalid, catch: NoMethodError)
|
|
340
|
+
# Failure(#<NoMethodError: undefined method `invalid' for an instance of String>)
|
|
341
|
+
|
|
342
|
+
pipe Failure("Danger!"), try(:to_json, catch: JSON::ParserError)
|
|
343
|
+
# Failure "Danger!"
|
|
298
344
|
----
|
|
299
345
|
|
|
300
|
-
=====
|
|
346
|
+
===== use
|
|
301
347
|
|
|
302
|
-
Allows you to use another
|
|
348
|
+
Allows you to use another pipe to build a superpipe, 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` that answers a {dry_monads_link} `Result` object. This is great for chaining multiple pipes together (i.e. superpipes).
|
|
349
|
+
|
|
350
|
+
Accepts a success while sending the unwrapped object to the command (or pipe) for further processing. A {dry_monads_link} `Result` is expected to be answered by the command. Example:
|
|
303
351
|
|
|
304
352
|
[source,ruby]
|
|
305
353
|
----
|
|
306
|
-
function = ->
|
|
354
|
+
function = -> number { Success number * 3 }
|
|
307
355
|
|
|
308
356
|
pipe 3, use(function) # Success 9
|
|
309
357
|
pipe Failure("Danger!"), use(function) # Failure "Danger!"
|
|
310
358
|
----
|
|
311
359
|
|
|
312
|
-
=====
|
|
360
|
+
===== validate
|
|
361
|
+
|
|
362
|
+
Allows you to use an contract for validating an object. This is especially useful when using {dry_schema_link}, {dry_validation_link}, or any contract that responds to `#call` and answers a `Result`.
|
|
313
363
|
|
|
314
|
-
|
|
364
|
+
By default, the `:as` key's value is `nil`. Use `:to_h`, for example, as the value for automatic casting to a `Hash`. You can also pass in any value to the `:as` key which is a valid method that the contract's result will respond to.
|
|
315
365
|
|
|
316
|
-
|
|
366
|
+
Accepts a success and rewraps as a success if the `:as` keyword is supplied. Otherwise, any failure is immediately passed through. Example:
|
|
317
367
|
|
|
318
368
|
[source,ruby]
|
|
319
369
|
----
|
|
320
370
|
schema = Dry::Schema.Params { required(:label).filled :string }
|
|
321
371
|
|
|
322
|
-
pipe({label: "Test"}, validate(schema))
|
|
323
|
-
|
|
324
|
-
|
|
372
|
+
pipe({label: "Test"}, validate(schema))
|
|
373
|
+
# Success label: "Test"
|
|
374
|
+
|
|
375
|
+
pipe({label: "Test"}, validate(schema, as: nil))
|
|
376
|
+
# Success #<Dry::Schema::Result{:label=>"Test"} errors={} path=[]>
|
|
377
|
+
|
|
378
|
+
pipe Failure("Danger!"), validate(schema)
|
|
379
|
+
# Failure "Danger!"
|
|
325
380
|
----
|
|
326
381
|
|
|
382
|
+
💡 Ensure you enable the {dry_monads_link} extension for {dry_schema_link} and/or {dry_validation_link} when using this step since this step expects the contract to respond to the `#to_monad` message.
|
|
383
|
+
|
|
327
384
|
==== Advanced
|
|
328
385
|
|
|
329
386
|
Several options are available should you need to advance beyond the basic steps. Each is described in detail below.
|
|
@@ -334,8 +391,8 @@ You can always use a `Proc` as a custom step. Example:
|
|
|
334
391
|
|
|
335
392
|
[source,ruby]
|
|
336
393
|
----
|
|
337
|
-
include Pipeable
|
|
338
394
|
include Dry::Monads[:result]
|
|
395
|
+
include Pipeable
|
|
339
396
|
|
|
340
397
|
pipe :a,
|
|
341
398
|
insert(:b),
|
|
@@ -355,7 +412,7 @@ include Pipeable
|
|
|
355
412
|
|
|
356
413
|
pipe :a,
|
|
357
414
|
insert(:b),
|
|
358
|
-
-> result { result.fmap { |
|
|
415
|
+
-> result { result.fmap { |items| items.join "_" } },
|
|
359
416
|
as(:to_sym)
|
|
360
417
|
|
|
361
418
|
# Yields: Success :a_b
|
|
@@ -363,35 +420,30 @@ pipe :a,
|
|
|
363
420
|
|
|
364
421
|
===== Methods
|
|
365
422
|
|
|
366
|
-
Methods
|
|
423
|
+
Methods, in addition to procs and lambdas, are the _preferred_ way to add custom steps due to the concise syntax. Example:
|
|
367
424
|
|
|
368
425
|
[source,ruby]
|
|
369
426
|
----
|
|
370
427
|
class Demo
|
|
371
428
|
include Pipeable
|
|
372
429
|
|
|
373
|
-
def call input
|
|
374
|
-
pipe :a,
|
|
375
|
-
insert(:b),
|
|
376
|
-
:join,
|
|
377
|
-
as(:to_sym)
|
|
378
|
-
end
|
|
430
|
+
def call(input) = pipe input, insert(:b), :join, as(:to_sym)
|
|
379
431
|
|
|
380
432
|
private
|
|
381
433
|
|
|
382
|
-
def join(result) = result.fmap { |
|
|
434
|
+
def join(result) = result.fmap { |items| items.join "_" }
|
|
383
435
|
end
|
|
384
436
|
|
|
385
|
-
Demo.new.call :a #
|
|
437
|
+
Demo.new.call :a # Success :a_b
|
|
386
438
|
----
|
|
387
439
|
|
|
388
|
-
All methods can be referenced by symbol as shown via `:join` above. Using a symbol is syntactic sugar for link:https://rubyapi.org/o/object#method-i-method[Object#method] so
|
|
440
|
+
All methods can be referenced by symbol as shown via `:join` above. Using a symbol is syntactic sugar for link:https://rubyapi.org/o/object#method-i-method[Object#method] so `:join` (symbol) is the same as using `method(:join)`. Both work but the former requires less typing.
|
|
389
441
|
|
|
390
442
|
===== Custom
|
|
391
443
|
|
|
392
444
|
If you'd like to define permanent and reusable steps, you can register a custom step which requires you to:
|
|
393
445
|
|
|
394
|
-
. Define a custom step as a
|
|
446
|
+
. Define a custom step as a class, lambda, or proc.
|
|
395
447
|
. Register your custom step along side the existing default steps.
|
|
396
448
|
|
|
397
449
|
Here's what this would look like:
|
|
@@ -405,7 +457,7 @@ module CustomSteps
|
|
|
405
457
|
@delimiter = delimiter
|
|
406
458
|
end
|
|
407
459
|
|
|
408
|
-
def call(result) = result.fmap { |
|
|
460
|
+
def call(result) = result.fmap { |items| items.join delimiter }
|
|
409
461
|
|
|
410
462
|
private
|
|
411
463
|
|
|
@@ -418,15 +470,85 @@ Pipeable::Steps::Container.register :join, CustomSteps::Join
|
|
|
418
470
|
include Pipeable
|
|
419
471
|
|
|
420
472
|
pipe :a, insert(:b), join, as(:to_sym)
|
|
421
|
-
#
|
|
473
|
+
# Success :a_b
|
|
422
474
|
|
|
423
475
|
pipe :a, insert(:b), join(""), as(:to_sym)
|
|
424
|
-
#
|
|
476
|
+
# Success :ab
|
|
477
|
+
----
|
|
478
|
+
|
|
479
|
+
A lambda or proc can be used too (albeit in limited capacity). Here's a version of the above using a lambda:
|
|
480
|
+
|
|
481
|
+
[source,ruby]
|
|
482
|
+
----
|
|
483
|
+
module CustomSteps
|
|
484
|
+
Join = -> result { result.fmap { |items| items.join "_" } }
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
Pipeable::Steps::Container.register :join, CustomSteps::Join
|
|
488
|
+
|
|
489
|
+
include Pipeable
|
|
490
|
+
|
|
491
|
+
puts pipe(:a, insert(:b), join, as(:to_sym))
|
|
492
|
+
# Success :a_b
|
|
493
|
+
----
|
|
494
|
+
|
|
495
|
+
=== Superpipes
|
|
496
|
+
|
|
497
|
+
Superpipes, as first hinted at in the `use` step above, are a combination of _pipeable_ objects chained together as individual steps. This allows you to reuse existing pipeable objects in new and interesting ways. Here's an contrived, but simple, example of what a superpipe looks like when built from pipeable objects:
|
|
498
|
+
|
|
499
|
+
[source,ruby]
|
|
425
500
|
----
|
|
501
|
+
class One
|
|
502
|
+
include Pipeable
|
|
503
|
+
|
|
504
|
+
def initialize label = "one"
|
|
505
|
+
@label = label
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
def call(item) = pipe item, insert(label, at: 0)
|
|
509
|
+
|
|
510
|
+
private
|
|
511
|
+
|
|
512
|
+
attr_reader :label
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
class Two
|
|
516
|
+
include Pipeable
|
|
517
|
+
|
|
518
|
+
def initialize label = "two"
|
|
519
|
+
@label = label
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
def call(item) = pipe item, insert(label)
|
|
523
|
+
|
|
524
|
+
private
|
|
525
|
+
|
|
526
|
+
attr_reader :label
|
|
527
|
+
end
|
|
528
|
+
|
|
529
|
+
class Three
|
|
530
|
+
include Pipeable
|
|
531
|
+
|
|
532
|
+
def initialize one: One.new, two: Two.new
|
|
533
|
+
@one = one
|
|
534
|
+
@two = two
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
def call(item) = pipe item, use(one), use(two)
|
|
538
|
+
|
|
539
|
+
private
|
|
540
|
+
|
|
541
|
+
attr_reader :one, :two
|
|
542
|
+
end
|
|
543
|
+
----
|
|
544
|
+
|
|
545
|
+
Notice, `One` and `Two` are normal pipeable objects with individual steps while `Three` injects both `One` and `Two` as dependencies and then subsequently pipes them together in the `#call` method via the `use` step. This is the power of a superpipe. ...and, yes, a superpipe can be an individual step in some other object. Turtles all the way down (or up). 😉
|
|
546
|
+
|
|
547
|
+
Again, the above is contrived but hopefully illustrates how you can build more complex architectures from smaller pipes.
|
|
426
548
|
|
|
427
549
|
=== Containers
|
|
428
550
|
|
|
429
|
-
Should you not want the basic steps, need custom steps, or a hybrid of default and custom steps, you can define your own container and provide
|
|
551
|
+
Should you not want the basic steps, need custom steps, or a hybrid of default and custom steps, you can define your own container -- using the {containable_link} gem -- and provide the container as an argument to `.[]` when including pipeable behavior. Example:
|
|
430
552
|
|
|
431
553
|
[source,ruby]
|
|
432
554
|
----
|
|
@@ -448,7 +570,7 @@ pipe :a, echo, insert(:b)
|
|
|
448
570
|
|
|
449
571
|
The above is a hybrid example where the `CustomContainer` registers a custom `echo` step along with the default `insert` step to make a new container. This is included when passed in as an argument via `.[]` (i.e. `include Pipeable[CustomContainer]`).
|
|
450
572
|
|
|
451
|
-
Whether you use default, custom, or hybrid steps, you have maximum flexibility using
|
|
573
|
+
Whether you use default, custom, or hybrid steps, you have maximum flexibility when using containers.
|
|
452
574
|
|
|
453
575
|
=== Composition
|
|
454
576
|
|
|
@@ -479,18 +601,18 @@ The architecture of this gem is built on top of the following concepts and gems:
|
|
|
479
601
|
* {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.
|
|
480
602
|
* {containable_link}: Allows related dependencies to be grouped together for injection as desired.
|
|
481
603
|
* {dry_monads_link}: Critical to ensuring the entire pipeline of steps adhere to the {railway_pattern_link} and leans heavily on the `Result` object.
|
|
482
|
-
* link:https://alchemists.io/projects/marameters[Marameters]: Through the use of the `.categorize` method, dynamic message passing is possible by inspecting the
|
|
604
|
+
* link:https://alchemists.io/projects/marameters[Marameters]: Through the use of the `.categorize` method, dynamic message passing is possible by inspecting the object's method parameters.
|
|
483
605
|
|
|
484
606
|
=== Style Guide
|
|
485
607
|
|
|
486
|
-
* *
|
|
487
|
-
** Use a single method (i.e. `#call`) which is public and adheres to the {command_pattern_link} so
|
|
608
|
+
* *Pipes*
|
|
609
|
+
** Use a single method (i.e. `#call`) which is public and adheres to the {command_pattern_link} so multiple pipes can be piped together (i.e. superpipes) if desired.
|
|
488
610
|
* *Steps*
|
|
489
611
|
** Inherit from the `Abstract` class to gain monad, composition, and dependency behavior. This allows subclasses to have direct 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.
|
|
490
|
-
** All filtered arguments -- in other words,
|
|
612
|
+
** All filtered arguments -- in other words, 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.
|
|
491
613
|
** The `#call` method must define a single positional `result` parameter since a monad will be passed as an argument. Example: `def call(result) = # Implementation`.
|
|
492
|
-
** Each block within the `#call` method should use the `
|
|
493
|
-
** Use implicit blocks sparingly. Most of the default steps shy away from using blocks because
|
|
614
|
+
** Each block within the `#call` method should use the `object` parameter to be consistent. More specific parameters like `operation` or `contract` should be used to improve readability when context allows. Example: `def call(result) = result.bind { |object| # Implementation }`.
|
|
615
|
+
** Use implicit blocks sparingly. Most of the default steps shy away from using blocks because the code becomes more complex. Use private methods, custom steps, and/or separate pipes if the code becomes too complex because you might have a smaller object which needs extraction.
|
|
494
616
|
|
|
495
617
|
=== Debugging
|
|
496
618
|
|
|
@@ -500,7 +622,7 @@ If you need to debug (i.e. {debug_link}) your pipe, use a lambda. Example:
|
|
|
500
622
|
----
|
|
501
623
|
pipe data,
|
|
502
624
|
check(/Book.+Price/, :match?),
|
|
503
|
-
-> result { binding.break },
|
|
625
|
+
-> result { binding.break; result }, # Breakpoint
|
|
504
626
|
:parse
|
|
505
627
|
----
|
|
506
628
|
|
|
@@ -508,18 +630,18 @@ The above breakpoint will allow you inspect the result of the `#check` step and/
|
|
|
508
630
|
|
|
509
631
|
=== Troubleshooting
|
|
510
632
|
|
|
511
|
-
The following might be of aid to as you implement your own
|
|
633
|
+
The following might be of aid to as you implement your own pipes.
|
|
512
634
|
|
|
513
635
|
==== Type Errors
|
|
514
636
|
|
|
515
637
|
If you get a `TypeError: Step must be functionally composable and answer a monad`, it means:
|
|
516
638
|
|
|
517
|
-
. The step must be a `Proc`, `Method`, or
|
|
518
|
-
. The step doesn't answer a result monad (i.e. `Success
|
|
639
|
+
. The step must be a `Proc`, `Method`, or any object which responds to `\#>>`, `#<<`, and `#call`.
|
|
640
|
+
. The step doesn't answer a result monad (i.e. `Success object` or `Failure object`).
|
|
519
641
|
|
|
520
642
|
==== No Method Errors
|
|
521
643
|
|
|
522
|
-
If you get a `NoMethodError: undefined method
|
|
644
|
+
If you get a `NoMethodError: undefined method success?` exception, this might mean that you forgot to add a comma after one of your steps. Example:
|
|
523
645
|
|
|
524
646
|
[source,ruby]
|
|
525
647
|
----
|
|
@@ -530,7 +652,7 @@ pipe "https://www.wikipedia.org",
|
|
|
530
652
|
|
|
531
653
|
# Invalid
|
|
532
654
|
pipe "https://www.wikipedia.org",
|
|
533
|
-
to(client, :get)
|
|
655
|
+
to(client, :get) # Missing comma.
|
|
534
656
|
try(:parse, catch: HTTP::Error)
|
|
535
657
|
----
|
|
536
658
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "dry/monads"
|
|
4
|
-
require "marameters"
|
|
5
4
|
|
|
6
5
|
module Pipeable
|
|
7
6
|
module Steps
|
|
@@ -14,12 +13,11 @@ module Pipeable
|
|
|
14
13
|
@base_positionals = positionals
|
|
15
14
|
@base_keywords = keywords
|
|
16
15
|
@base_block = block
|
|
17
|
-
@marameters = Marameters
|
|
18
16
|
end
|
|
19
17
|
|
|
20
18
|
protected
|
|
21
19
|
|
|
22
|
-
attr_reader :base_positionals, :base_keywords, :base_block
|
|
20
|
+
attr_reader :base_positionals, :base_keywords, :base_block
|
|
23
21
|
end
|
|
24
22
|
end
|
|
25
23
|
end
|
data/lib/pipeable/steps/as.rb
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
module Pipeable
|
|
4
4
|
module Steps
|
|
5
|
-
#
|
|
5
|
+
# Messages object, with optional arguments, as different result.
|
|
6
6
|
class As < Abstract
|
|
7
7
|
def call result
|
|
8
|
-
result.fmap { |
|
|
8
|
+
result.fmap { |object| object.public_send(*base_positionals, **base_keywords) }
|
|
9
9
|
end
|
|
10
10
|
end
|
|
11
11
|
end
|
data/lib/pipeable/steps/bind.rb
CHANGED
data/lib/pipeable/steps/check.rb
CHANGED
|
@@ -1,29 +1,31 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "marameters"
|
|
4
|
+
|
|
3
5
|
module Pipeable
|
|
4
6
|
module Steps
|
|
5
|
-
# Checks if
|
|
7
|
+
# Checks if proof is true and answers success (passthrough) or failure (with optional argument).
|
|
6
8
|
class Check < Abstract
|
|
7
|
-
def initialize
|
|
8
|
-
super(
|
|
9
|
-
@
|
|
9
|
+
def initialize proof, message
|
|
10
|
+
super()
|
|
11
|
+
@proof = proof
|
|
10
12
|
@message = message
|
|
11
13
|
end
|
|
12
14
|
|
|
13
15
|
def call result
|
|
14
|
-
result.bind do |
|
|
15
|
-
answer = question
|
|
16
|
-
answer == true || answer.is_a?(Success) ? result : Failure(
|
|
16
|
+
result.bind do |object|
|
|
17
|
+
answer = question object
|
|
18
|
+
answer == true || answer.is_a?(Success) ? result : Failure(object)
|
|
17
19
|
end
|
|
18
20
|
end
|
|
19
21
|
|
|
20
22
|
private
|
|
21
23
|
|
|
22
|
-
attr_reader :
|
|
24
|
+
attr_reader :proof, :message
|
|
23
25
|
|
|
24
|
-
def question
|
|
25
|
-
splat =
|
|
26
|
-
|
|
26
|
+
def question object
|
|
27
|
+
splat = Marameters.categorize proof.method(message).parameters, object
|
|
28
|
+
proof.public_send(message, *splat.positionals, **splat.keywords, &splat.block)
|
|
27
29
|
end
|
|
28
30
|
end
|
|
29
31
|
end
|
|
@@ -8,6 +8,8 @@ module Pipeable
|
|
|
8
8
|
module Container
|
|
9
9
|
extend Containable
|
|
10
10
|
|
|
11
|
+
register :alt, Or
|
|
12
|
+
register :amap, Amap
|
|
11
13
|
register :as, As
|
|
12
14
|
register :bind, Bind
|
|
13
15
|
register :check, Check
|
|
@@ -15,7 +17,6 @@ module Pipeable
|
|
|
15
17
|
register :insert, Insert
|
|
16
18
|
register :map, Map
|
|
17
19
|
register :merge, Merge
|
|
18
|
-
register :orr, Or
|
|
19
20
|
register :tee, Tee
|
|
20
21
|
register :to, To
|
|
21
22
|
register :try, Try
|
data/lib/pipeable/steps/fmap.rb
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module Pipeable
|
|
4
4
|
module Steps
|
|
5
|
-
# Inserts elements before
|
|
5
|
+
# Inserts elements before or after an object.
|
|
6
6
|
class Insert < Abstract
|
|
7
7
|
LAST = -1
|
|
8
8
|
|
|
@@ -13,8 +13,8 @@ module Pipeable
|
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def call result
|
|
16
|
-
result.fmap do |
|
|
17
|
-
cast =
|
|
16
|
+
result.fmap do |object|
|
|
17
|
+
cast = object.is_a?(Array) ? object : [object]
|
|
18
18
|
value.is_a?(Array) ? cast.insert(at, *value) : cast.insert(at, value)
|
|
19
19
|
end
|
|
20
20
|
end
|
data/lib/pipeable/steps/map.rb
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module Pipeable
|
|
4
4
|
module Steps
|
|
5
|
-
# Maps over
|
|
5
|
+
# Maps over an enumerable, processes each element, and answers a new enumerable.
|
|
6
6
|
class Map < Abstract
|
|
7
7
|
def call(result) = result.fmap { |collection| collection.map(&base_block) }
|
|
8
8
|
end
|
data/lib/pipeable/steps/merge.rb
CHANGED
|
@@ -2,19 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
module Pipeable
|
|
4
4
|
module Steps
|
|
5
|
-
# Merges initialized attributes with step
|
|
5
|
+
# Merges initialized attributes with step object for use by subsequent step.
|
|
6
6
|
class Merge < Abstract
|
|
7
|
-
def initialize
|
|
8
|
-
super(**
|
|
7
|
+
def initialize(as: :step, **)
|
|
8
|
+
super(**)
|
|
9
9
|
@as = as
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def call result
|
|
13
|
-
result.fmap do |
|
|
14
|
-
if
|
|
15
|
-
|
|
13
|
+
result.fmap do |object|
|
|
14
|
+
if object.is_a? Hash
|
|
15
|
+
object.merge! base_keywords
|
|
16
16
|
else
|
|
17
|
-
{as =>
|
|
17
|
+
{as => object}.merge!(base_keywords)
|
|
18
18
|
end
|
|
19
19
|
end
|
|
20
20
|
end
|
data/lib/pipeable/steps/or.rb
CHANGED
data/lib/pipeable/steps/tee.rb
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module Pipeable
|
|
4
4
|
module Steps
|
|
5
|
-
# Messages operation, without any
|
|
5
|
+
# Messages operation, without any checks, while passing input through as output.
|
|
6
6
|
class Tee < Abstract
|
|
7
7
|
def initialize(operation, *, **)
|
|
8
8
|
super(*, **)
|
data/lib/pipeable/steps/to.rb
CHANGED
|
@@ -1,25 +1,27 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "marameters"
|
|
4
|
+
|
|
3
5
|
module Pipeable
|
|
4
6
|
module Steps
|
|
5
|
-
# Delegates to a non-callable
|
|
7
|
+
# Delegates to a non-callable object which automatically wraps the result if necessary.
|
|
6
8
|
class To < Abstract
|
|
7
|
-
def initialize(
|
|
9
|
+
def initialize(object, message, **)
|
|
8
10
|
super(**)
|
|
9
|
-
@
|
|
11
|
+
@object = object
|
|
10
12
|
@message = message
|
|
11
13
|
end
|
|
12
14
|
|
|
13
15
|
def call result
|
|
14
16
|
result.bind do |arguments|
|
|
15
|
-
splat =
|
|
16
|
-
wrap
|
|
17
|
+
splat = Marameters.categorize object.method(message).parameters, arguments
|
|
18
|
+
wrap object.public_send(message, *splat.positionals, **splat.keywords, &splat.block)
|
|
17
19
|
end
|
|
18
20
|
end
|
|
19
21
|
|
|
20
22
|
private
|
|
21
23
|
|
|
22
|
-
attr_reader :
|
|
24
|
+
attr_reader :object, :message
|
|
23
25
|
|
|
24
26
|
def wrap(result) = result.is_a?(Dry::Monads::Result) ? result : Success(result)
|
|
25
27
|
end
|
data/lib/pipeable/steps/try.rb
CHANGED
|
@@ -2,17 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
module Pipeable
|
|
4
4
|
module Steps
|
|
5
|
-
#
|
|
5
|
+
# Sends a risky message to an object which may pass or fail.
|
|
6
6
|
class Try < Abstract
|
|
7
|
-
def initialize
|
|
8
|
-
super(
|
|
7
|
+
def initialize(*, catch:, **)
|
|
8
|
+
super(*, **)
|
|
9
9
|
@catch = catch
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def call result
|
|
13
|
-
result.fmap { |
|
|
13
|
+
result.fmap { |object| object.public_send(*base_positionals, **base_keywords) }
|
|
14
14
|
rescue *Array(catch) => error
|
|
15
|
-
Failure error
|
|
15
|
+
Failure error
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
private
|
data/lib/pipeable/steps/use.rb
CHANGED
|
@@ -2,18 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
module Pipeable
|
|
4
4
|
module Steps
|
|
5
|
-
#
|
|
5
|
+
# Messages a command (or pipe) which answers a result.
|
|
6
6
|
class Use < Abstract
|
|
7
|
-
def initialize(
|
|
7
|
+
def initialize(command, **)
|
|
8
8
|
super(**)
|
|
9
|
-
@
|
|
9
|
+
@command = command
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
-
def call(result) = result.bind { |input|
|
|
12
|
+
def call(result) = result.bind { |input| command.call input }
|
|
13
13
|
|
|
14
14
|
private
|
|
15
15
|
|
|
16
|
-
attr_reader :
|
|
16
|
+
attr_reader :command
|
|
17
17
|
end
|
|
18
18
|
end
|
|
19
19
|
end
|
|
@@ -2,27 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
module Pipeable
|
|
4
4
|
module Steps
|
|
5
|
-
# Validates
|
|
5
|
+
# Validates result via a callable contract.
|
|
6
6
|
class Validate < Abstract
|
|
7
|
-
def initialize
|
|
8
|
-
super(
|
|
9
|
-
@
|
|
7
|
+
def initialize contract, as: nil
|
|
8
|
+
super()
|
|
9
|
+
@contract = contract
|
|
10
10
|
@as = as
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
def call result
|
|
14
|
-
result.bind do |payload|
|
|
15
|
-
value = operation.call payload
|
|
16
|
-
|
|
17
|
-
return Failure value if value.failure?
|
|
18
|
-
|
|
19
|
-
Success(as ? value.public_send(as) : value)
|
|
20
|
-
end
|
|
21
|
-
end
|
|
13
|
+
def call(result) = result.bind { |payload| cast payload }
|
|
22
14
|
|
|
23
15
|
private
|
|
24
16
|
|
|
25
|
-
attr_reader :
|
|
17
|
+
attr_reader :contract, :as
|
|
18
|
+
|
|
19
|
+
def cast payload
|
|
20
|
+
contract.call(payload).to_monad.fmap { |data| as ? data.public_send(as) : data }
|
|
21
|
+
end
|
|
26
22
|
end
|
|
27
23
|
end
|
|
28
24
|
end
|
data/pipeable.gemspec
CHANGED
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pipeable
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Brooke Kuhlmann
|
|
@@ -35,7 +35,7 @@ cert_chain:
|
|
|
35
35
|
3n5C8/6Zh9DYTkpcwPSuIfAga6wf4nXc9m6JAw8AuMLaiWN/r/2s4zJsUHYERJEu
|
|
36
36
|
gZGm4JqtuSg8pYjPeIJxS960owq+SfuC+jxqmRA54BisFCv/0VOJi7tiJVY=
|
|
37
37
|
-----END CERTIFICATE-----
|
|
38
|
-
date: 2024-
|
|
38
|
+
date: 2024-05-12 00:00:00.000000000 Z
|
|
39
39
|
dependencies:
|
|
40
40
|
- !ruby/object:Gem::Dependency
|
|
41
41
|
name: containable
|
|
@@ -123,6 +123,7 @@ files:
|
|
|
123
123
|
- lib/pipeable/composable.rb
|
|
124
124
|
- lib/pipeable/pipe.rb
|
|
125
125
|
- lib/pipeable/steps/abstract.rb
|
|
126
|
+
- lib/pipeable/steps/amap.rb
|
|
126
127
|
- lib/pipeable/steps/as.rb
|
|
127
128
|
- lib/pipeable/steps/bind.rb
|
|
128
129
|
- lib/pipeable/steps/check.rb
|
|
@@ -164,7 +165,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
164
165
|
- !ruby/object:Gem::Version
|
|
165
166
|
version: '0'
|
|
166
167
|
requirements: []
|
|
167
|
-
rubygems_version: 3.5.
|
|
168
|
+
rubygems_version: 3.5.10
|
|
168
169
|
signing_key:
|
|
169
170
|
specification_version: 4
|
|
170
171
|
summary: A domain specific language for building functionally composable steps.
|
metadata.gz.sig
CHANGED
|
Binary file
|