trailblazer-macro 2.1.11 → 2.1.13

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.
@@ -0,0 +1,585 @@
1
+ require "test_helper"
2
+ module Autogenerated
3
+
4
+ # step Macro::Each(:report_templates, key: :report_template) {
5
+ # step Subprocess(ReportTemplate::Update), input: :input_report_template
6
+ # fail :set_report_template_errors
7
+ # }
8
+
9
+ # def report_templates(ctx, **) ctx["result.contract.default"].report_templates
10
+ # end
11
+
12
+ class EachTest < Minitest::Spec
13
+ class Composer < Struct.new(:full_name, :email)
14
+ end
15
+
16
+ class Mailer
17
+ def self.send(**options)
18
+ @send_options << options
19
+ end
20
+
21
+ class << self
22
+ attr_accessor :send_options
23
+ end
24
+ end
25
+
26
+ #@ operation has {#composers_for_each}
27
+ module B
28
+ class Song < Struct.new(:id, :title, :band, :composers)
29
+ def self.find_by(id:)
30
+ if id == 2
31
+ return Song.new(id, nil, nil, [Composer.new("Fat Mike", "mike@fat.wreck"), Composer.new("El Hefe")])
32
+ end
33
+
34
+ if id == 3
35
+ return Song.new(id, nil, nil, [Composer.new("Fat Mike", "mike@fat.wreck"), Composer.new("El Hefe", "scammer@spam")])
36
+ end
37
+
38
+ Song.new(id, nil, nil, [Composer.new("Fat Mike"), Composer.new("El Hefe")])
39
+ end
40
+ end
41
+
42
+ #:each
43
+ module Song::Operation
44
+ class Cover < Trailblazer::Operation
45
+ step :model
46
+ #:each-dataset
47
+ step Each(dataset_from: :composers_for_each, collect: true) {
48
+ step :notify_composers
49
+ }
50
+ #:each-dataset end
51
+ step :rearrange
52
+
53
+ # "decider interface"
54
+ def composers_for_each(ctx, model:, **)
55
+ model.composers
56
+ end
57
+
58
+ def notify_composers(ctx, index:, item:, **)
59
+ ctx[:value] = [index, item.full_name]
60
+ end
61
+
62
+ #~meths
63
+ def model(ctx, params:, **)
64
+ ctx[:model] = Song.find_by(id: params[:id])
65
+ end
66
+
67
+ include T.def_steps(:rearrange)
68
+ #~meths end
69
+ end
70
+ end
71
+ #:each end
72
+ end # B
73
+
74
+ it "allows a dataset compute in the hosting activity" do
75
+ #@ {:dataset} is not part of the {ctx}.
76
+ assert_invoke B::Song::Operation::Cover, params: {id: 1},
77
+ expected_ctx_variables: {
78
+ model: B::Song.find_by(id: 1),
79
+ collected_from_each: [[0, "Fat Mike"], [1, "El Hefe"],]
80
+ },
81
+ seq: "[:rearrange]"
82
+
83
+ =begin
84
+ #:collected_from_each
85
+ #~ctx_to_result
86
+ ctx = {params: {id: 1}} # Song 1 has two composers.
87
+
88
+ result = Song::Operation::Cover.(ctx)
89
+
90
+ puts result[:collected_from_each] #=> [[0, "Fat Mike"], [1, "El Hefe"]]
91
+ #~ctx_to_result end
92
+ #:collected_from_each end
93
+ =end
94
+ end
95
+
96
+ module CoverMethods
97
+ def notify_composers(ctx, index:, item:, **)
98
+ ctx[:value] = [index, item.full_name]
99
+ end
100
+
101
+ def model(ctx, params:, **)
102
+ ctx[:model] = EachTest::B::Song.find_by(id: params[:id])
103
+ end
104
+
105
+ include T.def_steps(:rearrange)
106
+ end
107
+
108
+ module ComposersForEach
109
+ def composers_for_each(ctx, model:, **)
110
+ model.composers
111
+ end
112
+ end
113
+
114
+ #@ operation has dedicated step {#find_composers}
115
+ module C
116
+ class Song < B::Song; end
117
+
118
+ module Song::Operation
119
+ class Cover < Trailblazer::Operation
120
+ step :model
121
+ step :find_composers
122
+ step Each(collect: true) {
123
+ step :notify_composers
124
+ }, In() => {:composers => :dataset}
125
+ step :rearrange
126
+
127
+ def find_composers(ctx, model:, **)
128
+ # You could also say {ctx[:dataset] = model.composers},
129
+ # and wouldn't need the In() mapping.
130
+ ctx[:composers] = model.composers
131
+ end
132
+ #~meths
133
+ include CoverMethods
134
+ #~meths end
135
+ end
136
+ end
137
+ end # C
138
+
139
+ it "dataset can come from the hosting activity" do
140
+ #@ {:dataset} is not part of the outgoing {ctx}.
141
+ assert_invoke B::Song::Operation::Cover, params: {id: 1},
142
+ expected_ctx_variables: {
143
+ model: B::Song.find_by(id: 1),
144
+ collected_from_each: [[0, "Fat Mike"], [1, "El Hefe"],]
145
+ }, seq: "[:rearrange]"
146
+ end
147
+
148
+ it "dataset coming via In() from the operation" do
149
+ #@ {:dataset} is not part of the {ctx}.
150
+ assert_invoke C::Song::Operation::Cover, params: {id: 1},
151
+ expected_ctx_variables: {
152
+ model: C::Song.find_by(id: 1),
153
+ composers: [Composer.new("Fat Mike"), Composer.new("El Hefe")],
154
+ collected_from_each: [[0, "Fat Mike"], [1, "El Hefe"],]
155
+ }, seq: "[:rearrange]"
156
+ end
157
+
158
+ #@ {:item_key}
159
+ module E
160
+ class Song < B::Song; end
161
+
162
+ Mailer = Class.new(EachTest::Mailer)
163
+
164
+ #:composer
165
+ module Song::Operation
166
+ class Cover < Trailblazer::Operation
167
+ #~meths
168
+ step :model
169
+ #:item_key
170
+ step Each(dataset_from: :composers_for_each, item_key: :composer) {
171
+ step :notify_composers
172
+ }
173
+ #:item_key end
174
+ step :rearrange
175
+
176
+
177
+ # circuit-step interface! "decider interface"
178
+ def composers_for_each(ctx, model:, **)
179
+ model.composers
180
+ end
181
+ include CoverMethods
182
+ #~meths end
183
+ def notify_composers(ctx, index:, composer:, **)
184
+ Mailer.send(to: composer.email, message: "#{index}) You, #{composer.full_name}, have been warned about your song being copied.")
185
+ end
186
+ end
187
+ end
188
+ #:composer end
189
+ end # E
190
+
191
+ it "{item_key: :composer}" do
192
+ E::Mailer.send_options = []
193
+ assert_invoke E::Song::Operation::Cover, params: {id: 1},
194
+ expected_ctx_variables: {
195
+ model: B::Song.find_by(id: 1),
196
+ # collected_from_each: ["Fat Mike", "El Hefe"]
197
+ },
198
+ seq: "[:rearrange]"
199
+ assert_equal E::Mailer.send_options, [{:to=>nil, :message=>"0) You, Fat Mike, have been warned about your song being copied."}, {:to=>nil, :message=>"1) You, El Hefe, have been warned about your song being copied."}]
200
+ end
201
+
202
+ #@ failure in Each
203
+ module F
204
+ class Song < B::Song; end
205
+
206
+ class Notify
207
+ def self.send_email(email)
208
+ return if email.nil?
209
+ true
210
+ end
211
+ end
212
+
213
+ module Song::Operation
214
+ class Cover < Trailblazer::Operation
215
+ step :model
216
+ step Each(dataset_from: :composers_for_each, collect: true) {
217
+ step :notify_composers
218
+ }
219
+ step :rearrange
220
+
221
+ def notify_composers(ctx, item:, **)
222
+ if Notify.send_email(item.email)
223
+ ctx[:value] = item.email # let's collect all emails that could be sent.
224
+ return true
225
+ else
226
+ return false
227
+ end
228
+ end
229
+ #~meths
230
+
231
+ # circuit-step interface! "decider interface"
232
+ def composers_for_each(ctx, model:, **)
233
+ model.composers
234
+ end
235
+ include CoverMethods
236
+ #~meths end
237
+ end
238
+ end
239
+ end # F
240
+
241
+ it "failure in Each" do
242
+ assert_invoke F::Song::Operation::Cover, params: {id: 2},
243
+ expected_ctx_variables: {
244
+ model: B::Song.find_by(id: 2),
245
+ collected_from_each: ["mike@fat.wreck", nil],
246
+ },
247
+ seq: "[]",
248
+ terminus: :failure
249
+
250
+ Trailblazer::Developer.wtf?(F::Song::Operation::Cover, [{params: {id: 2}, seq: []}])
251
+ end
252
+
253
+
254
+ #@ Each with operation
255
+ module D
256
+ class Song < B::Song; end
257
+ Mailer = Class.new(EachTest::Mailer)
258
+
259
+ #:operation-class
260
+ module Song::Operation
261
+ class Notify < Trailblazer::Operation
262
+ step :send_email
263
+
264
+ def send_email(ctx, index:, item:, **)
265
+ Mailer.send(to: item.email, message: "#{index}) You, #{item.full_name}, have been warned about your song being copied.")
266
+ end
267
+ end
268
+ end
269
+ #:operation-class end
270
+
271
+ #:operation
272
+ module Song::Operation
273
+ class Cover < Trailblazer::Operation
274
+ step :model
275
+ step Each(Notify, dataset_from: :composers_for_each)
276
+ step :rearrange
277
+ #~meths
278
+ def composers_for_each(ctx, model:, **)
279
+ model.composers
280
+ end
281
+ include CoverMethods
282
+ #~meths end
283
+ end
284
+ end
285
+ #:operation end
286
+ end
287
+
288
+ it "Each(Activity::Railway)" do
289
+ D::Mailer.send_options = []
290
+ assert_invoke D::Song::Operation::Cover, params: {id: 1},
291
+ seq: "[:rearrange]",
292
+ expected_ctx_variables: {
293
+ model: D::Song.find_by(id: 1),
294
+ # collected_from_each: [[0, "Fat Mike"], [1, "El Hefe"],]
295
+ }
296
+ assert_equal D::Mailer.send_options, [{:to=>nil, :message=>"0) You, Fat Mike, have been warned about your song being copied."}, {:to=>nil, :message=>"1) You, El Hefe, have been warned about your song being copied."}]
297
+ end
298
+
299
+ #@ Each with operation with three outcomes. Notify terminates on {End.spam_email},
300
+ # which is then routed to End.spam_alert in the hosting activity.
301
+ # NOTE: this is not documented, yet.
302
+ module G
303
+ class Song < B::Song; end
304
+
305
+ module Song::Operation
306
+ class Notify < Trailblazer::Operation
307
+ terminus :spam_email
308
+ # SpamEmail = Class.new(Trailblazer::Operation::Signal)
309
+
310
+ step :send_email, Output(:failure) => Track(:spam_email)
311
+
312
+ def send_email(ctx, index:, item:, **)
313
+ return false if item.email == "scammer@spam"
314
+ ctx[:value] = [index, item.full_name]
315
+ end
316
+ end
317
+ end
318
+
319
+ module Song::Operation
320
+ class Cover < Trailblazer::Operation
321
+ terminus :spam_alert
322
+
323
+ step :model
324
+ step Each(Notify, dataset_from: :composers_for_each, collect: true),
325
+ Output(:spam_email) => Track(:spam_alert)
326
+ step :rearrange
327
+ #~meths
328
+ def composers_for_each(ctx, model:, **)
329
+ model.composers
330
+ end
331
+ include CoverMethods
332
+ #~meths end
333
+ end
334
+ end
335
+ end
336
+
337
+ it "Each(Activity::Railway) with End.spam_email" do
338
+ Trailblazer::Developer.wtf?(G::Song::Operation::Cover, [{params: {id: 3}}, {}])
339
+
340
+ assert_invoke G::Song::Operation::Cover, params: {id: 3},
341
+ terminus: :spam_alert,
342
+ seq: "[]",
343
+ expected_ctx_variables: {
344
+ model: G::Song.find_by(id: 3),
345
+ collected_from_each: [[0, "Fat Mike"], nil,]
346
+ }
347
+ end
348
+ end
349
+
350
+ #@ Iteration doesn't add anything to ctx when {collect: false}.
351
+ class EachCtxDiscardedTest < Minitest::Spec
352
+ Composer = EachTest::Composer
353
+ Song = Class.new(EachTest::B::Song)
354
+
355
+ #@ iterated steps write to ctx, gets discarded.
356
+ module Song::Operation
357
+ class Cover < Trailblazer::Operation
358
+ step :model
359
+ #:write_to_ctx
360
+ step Each(dataset_from: :composers_for_each) {
361
+ step :notify_composers
362
+ step :write_to_ctx
363
+ }
364
+ #:write_to_ctx end
365
+ step :rearrange
366
+
367
+ #:write
368
+ def write_to_ctx(ctx, index:, seq:, **)
369
+ #~meths
370
+ seq << :write_to_ctx
371
+
372
+ #~meths end
373
+ ctx[:variable] = index # this is discarded!
374
+ end
375
+ #:write end
376
+
377
+ #~meths
378
+ include EachTest::CoverMethods
379
+ include EachTest::ComposersForEach
380
+ #~meths end
381
+ end
382
+ end
383
+
384
+ it "discards {ctx[:variable]}" do
385
+ assert_invoke Song::Operation::Cover, params: {id: 1},
386
+ expected_ctx_variables: {
387
+ model: Song.find_by(id: 1),
388
+ # collected_from_each: [[0, "Fat Mike"], [1, "El Hefe"],]
389
+ },
390
+ seq: "[:write_to_ctx, :write_to_ctx, :rearrange]"
391
+ end
392
+ end
393
+
394
+ # We add {:collected_from_each} ourselves.
395
+ class EachCtxAddsCollectedFromEachTest < Minitest::Spec
396
+ Composer = EachTest::Composer
397
+ Song = Class.new(EachTest::B::Song)
398
+
399
+ module Song::Operation
400
+ class Cover < Trailblazer::Operation
401
+ step :model
402
+ step Each(dataset_from: :composers_for_each,
403
+
404
+ # all filters called before/after each iteration!
405
+ Inject(:collected_from_each) => ->(ctx, **) { [] }, # this is called only once.
406
+ Out() => ->(ctx, collected_from_each:, **) { {collected_from_each: collected_from_each += [ctx[:value]] } }
407
+
408
+
409
+
410
+ ) {
411
+ step :notify_composers
412
+ step :write_to_ctx
413
+ }
414
+ step :rearrange
415
+
416
+ def write_to_ctx(ctx, index:, seq:, item:, **)
417
+ seq << :write_to_ctx
418
+
419
+ ctx[:value] = [index, item.full_name]
420
+ end
421
+
422
+ #~meths
423
+ include EachTest::CoverMethods
424
+ include EachTest::ComposersForEach
425
+ #~meths end
426
+ end
427
+ end
428
+
429
+ it "provides {:collected_from_each}" do
430
+ assert_invoke Song::Operation::Cover, params: {id: 1},
431
+ expected_ctx_variables: {
432
+ model: Song.find_by(id: 1),
433
+ collected_from_each: [[0, "Fat Mike"], [1, "El Hefe"],]
434
+ },
435
+ seq: "[:write_to_ctx, :write_to_ctx, :rearrange]"
436
+ end
437
+ end
438
+
439
+ #@ You can use Inject() to compute new variables.
440
+ #@ and Out() to compute what goes into the iterated {ctx}.
441
+ class EachCtxInOutTest < Minitest::Spec
442
+ Composer = EachTest::Composer
443
+ Song = Class.new(EachTest::B::Song)
444
+
445
+ module Song::Operation
446
+ class Cover < Trailblazer::Operation
447
+ step :model
448
+ step Each(dataset_from: :composers_for_each,
449
+ # Inject(always: true) => {
450
+ Inject(:composer_index) => ->(ctx, index:, **) { index },
451
+ # all filters called before/after each iteration!
452
+ Out() => ->(ctx, index:, variable:, **) { {:"composer-#{index}-value" => variable} }
453
+
454
+
455
+
456
+
457
+
458
+ ) {
459
+ step :notify_composers
460
+ step :write_to_ctx
461
+ }
462
+ step :rearrange
463
+
464
+ def write_to_ctx(ctx, composer_index:, model:, **)
465
+ ctx[:variable] = "#{composer_index} + #{model.class.name.split('::').last}"
466
+ end
467
+
468
+ #~meths
469
+ include EachTest::CoverMethods
470
+ include EachTest::ComposersForEach
471
+ #~meths end
472
+ end
473
+ end
474
+
475
+ it "discards {ctx[:variable]}" do
476
+ assert_invoke Song::Operation::Cover, params: {id: 1},
477
+ expected_ctx_variables: {
478
+ model: Song.find_by(id: 1),
479
+ :"composer-0-value" => "0 + Song",
480
+ :"composer-1-value" => "1 + Song",
481
+ },
482
+ seq: "[:rearrange]"
483
+ end
484
+ end
485
+
486
+ class EachOuterCtxTest < Minitest::Spec
487
+
488
+ end
489
+
490
+
491
+ #@ {:errors} is first initialized with a default injection,
492
+ #@ then passed across iterations.
493
+ # TODO: similar test above with {:collected_from_each}.
494
+ class EachSharedIterationVariableTest < Minitest::Spec
495
+ Song = Class.new(EachTest::B::Song)
496
+
497
+ #:inject
498
+ module Song::Operation
499
+ class Cover < Trailblazer::Operation
500
+ step :model
501
+ step Each(dataset_from: :composers_for_each,
502
+ Inject(:messages) => ->(*) { {} },
503
+
504
+ # all filters called before/after each iteration!
505
+ Out() => [:messages]
506
+ ) {
507
+ step :write_to_ctx
508
+ }
509
+ step :rearrange
510
+
511
+ def write_to_ctx(ctx, item:, messages:, index:, **)
512
+ ctx[:messages] = messages.merge(index => item.full_name)
513
+ end
514
+ #~meths
515
+ include EachTest::CoverMethods
516
+ include EachTest::ComposersForEach
517
+ #~meths end
518
+ end
519
+ end
520
+ #:inject end
521
+
522
+ it "passes {ctx[:messages]} across iterations and makes it grow" do
523
+ assert_invoke Song::Operation::Cover, params: {id: 1},
524
+ expected_ctx_variables: {
525
+ model: Song.find_by(id: 1),
526
+ messages: {0=>"Fat Mike", 1=>"El Hefe"}},
527
+ seq: "[:rearrange]"
528
+ end
529
+
530
+ end
531
+
532
+ #@ Each without any option
533
+ class EachPureTest < Minitest::Spec
534
+ Song = Class.new(EachTest::B::Song)
535
+
536
+ Mailer = Class.new(EachTest::Mailer)
537
+
538
+ #:each-pure
539
+ module Song::Operation
540
+ class Cover < Trailblazer::Operation
541
+ step :model
542
+ #:each-pure-macro
543
+ step Each(dataset_from: :composers_for_each) {
544
+ step :notify_composers
545
+ }
546
+ #:each-pure-macro end
547
+ step :rearrange
548
+
549
+ # "decider interface"
550
+ #:dataset_from
551
+ def composers_for_each(ctx, model:, **)
552
+ model.composers
553
+ end
554
+ #:dataset_from end
555
+
556
+ #:iterated
557
+ def notify_composers(ctx, index:, item:, **)
558
+ Mailer.send(to: item.email, message: "#{index}) You, #{item.full_name}, have been warned about your song being copied.")
559
+ end
560
+ #:iterated end
561
+ #~meths
562
+ def model(ctx, params:, **)
563
+ ctx[:model] = Song.find_by(id: params[:id])
564
+ end
565
+
566
+ include T.def_steps(:rearrange)
567
+ #~meths end
568
+ end
569
+ end
570
+ #:each-pure end
571
+
572
+ it "allows a dataset compute in the hosting activity" do
573
+ Mailer.send_options = []
574
+ #@ {:dataset} is not part of the {ctx}.
575
+ assert_invoke Song::Operation::Cover, params: {id: 1},
576
+ expected_ctx_variables: {
577
+ model: Song.find_by(id: 1),
578
+ },
579
+ seq: "[:rearrange]"
580
+
581
+ assert_equal Mailer.send_options, [{:to=>nil, :message=>"0) You, Fat Mike, have been warned about your song being copied."}, {:to=>nil, :message=>"1) You, El Hefe, have been warned about your song being copied."}]
582
+ end
583
+ end
584
+
585
+ end