trailblazer 2.0.7 → 2.1.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +35 -1
  3. data/Gemfile +6 -12
  4. data/README.md +3 -1
  5. data/Rakefile +6 -17
  6. data/lib/trailblazer.rb +7 -4
  7. data/lib/trailblazer/deprecation/call.rb +46 -0
  8. data/lib/trailblazer/deprecation/context.rb +43 -0
  9. data/lib/trailblazer/operation/contract.rb +40 -9
  10. data/lib/trailblazer/operation/deprecations.rb +21 -0
  11. data/lib/trailblazer/operation/guard.rb +5 -5
  12. data/lib/trailblazer/operation/model.rb +15 -10
  13. data/lib/trailblazer/operation/nested.rb +56 -85
  14. data/lib/trailblazer/operation/persist.rb +4 -2
  15. data/lib/trailblazer/operation/policy.rb +16 -7
  16. data/lib/trailblazer/operation/pundit.rb +3 -3
  17. data/lib/trailblazer/operation/representer.rb +5 -0
  18. data/lib/trailblazer/operation/rescue.rb +12 -9
  19. data/lib/trailblazer/operation/validate.rb +36 -29
  20. data/lib/trailblazer/operation/wrap.rb +49 -11
  21. data/lib/trailblazer/task.rb +20 -0
  22. data/lib/trailblazer/version.rb +1 -1
  23. data/test/benchmark.rb +63 -0
  24. data/test/deprecation/call_test.rb +42 -0
  25. data/test/deprecation/context_test.rb +19 -0
  26. data/test/docs/contract_test.rb +73 -53
  27. data/test/docs/dry_test.rb +2 -2
  28. data/test/docs/fast_test.rb +133 -13
  29. data/test/docs/guard_test.rb +28 -35
  30. data/test/docs/macro_test.rb +1 -1
  31. data/test/docs/model_test.rb +13 -13
  32. data/test/docs/nested_test.rb +54 -122
  33. data/test/docs/operation_test.rb +42 -43
  34. data/test/docs/pundit_test.rb +16 -16
  35. data/test/docs/representer_test.rb +18 -18
  36. data/test/docs/rescue_test.rb +29 -29
  37. data/test/docs/trace_test.rb +82 -0
  38. data/test/docs/wrap_test.rb +59 -26
  39. data/test/module_test.rb +75 -75
  40. data/test/nested_test.rb +293 -0
  41. data/test/operation/contract_test.rb +23 -153
  42. data/test/operation/dsl/contract_test.rb +9 -9
  43. data/test/operation/dsl/representer_test.rb +169 -169
  44. data/test/operation/model_test.rb +15 -21
  45. data/test/operation/persist_test.rb +18 -11
  46. data/test/operation/pundit_test.rb +25 -23
  47. data/test/operation/representer_test.rb +254 -254
  48. data/test/test_helper.rb +5 -2
  49. data/test/variables_test.rb +158 -0
  50. data/trailblazer.gemspec +1 -1
  51. data/untitled +33 -0
  52. metadata +25 -27
  53. data/lib/trailblazer/operation/callback.rb +0 -35
  54. data/lib/trailblazer/operation/procedural/contract.rb +0 -15
  55. data/lib/trailblazer/operation/procedural/validate.rb +0 -22
  56. data/test/operation/callback_test.rb +0 -70
  57. data/test/operation/dsl/callback_test.rb +0 -106
  58. data/test/operation/params_test.rb +0 -36
  59. data/test/operation/pipedream_test.rb +0 -59
  60. data/test/operation/pipetree_test.rb +0 -104
  61. data/test/operation/present_test.rb +0 -24
  62. data/test/operation/resolver_test.rb +0 -47
  63. data/test/operation_test.rb +0 -143
@@ -0,0 +1,82 @@
1
+ require "test_helper"
2
+
3
+ class DocsTraceTest < Minitest::Spec
4
+ Song = Struct.new(:id, :title)
5
+
6
+ class MyContract < Reform::Form
7
+ property :title
8
+ end
9
+
10
+ class Create < Trailblazer::Operation
11
+ class Present < Trailblazer::Operation
12
+ step Model( Song, :new )
13
+ step Contract::Build( constant: MyContract )
14
+ end
15
+
16
+ step Nested( Present )
17
+ step Contract::Validate( key: :song )
18
+ step Contract::Persist()
19
+ step :notify_band
20
+
21
+ def notify_band(options, **)
22
+ true
23
+ end
24
+ end
25
+
26
+ let(:params) { {} }
27
+ let(:current_user) { Module }
28
+
29
+ it do
30
+ #:trace
31
+ result = Create::Present.trace( params: params, current_user: current_user )
32
+ puts result.wtf?
33
+
34
+ # =>
35
+ # |-- Start.default
36
+ # |-- model.build
37
+ # |-- contract.build
38
+ # `-- End.success
39
+ #:trace end
40
+
41
+ result.wtf.gsub(/0x\w+/, "").must_equal %{|-- #<Trailblazer::Activity::Start:>
42
+ |-- model.build
43
+ |-- contract.build
44
+ `-- #<Trailblazer::Operation::Railway::End::Success:>}
45
+ end
46
+
47
+ it do
48
+ #:trace-cpx
49
+ result = Create.trace( params: params, current_user: current_user )
50
+ puts result.wtf?
51
+
52
+ # =>
53
+ # |-- #<Trailblazer::Activity::Nested:0x000000031960d8>
54
+ # | |-- Start.default
55
+ # | |-- model.build
56
+ # | |-- contract.build
57
+ # | |-- End.success
58
+ # | `-- #<Trailblazer::Activity::Nested:0x000000031960d8>
59
+ # |-- #<Trailblazer::Activity::Nested:0x0000000311f3e8>
60
+ # | |-- Start.default
61
+ # | |-- contract.default.params
62
+ # | |-- End.failure
63
+ # | `-- #<Trailblazer::Activity::Nested:0x0000000311f3e8>
64
+ # `-- #<Trailblazer::Operation::Railway::End::Failure:0x00000003201fb8>
65
+ #:trace-cpx end
66
+
67
+ # result.wtf?.must_equal %{|-- Start.default
68
+ # |-- model.build
69
+ # |-- contract.build
70
+ # `-- End.success}
71
+ end
72
+
73
+ # operation = ->(*args) { Create.__call__(*args) }
74
+
75
+ # stack, _ = Trailblazer::Circuit::Trace.(
76
+ # operation,
77
+ # nil,
78
+ # options={ a_return: true },
79
+ # )
80
+
81
+ # puts output = Circuit::Trace::Present.tree(stack)
82
+ end
@@ -1,5 +1,7 @@
1
1
  require "test_helper"
2
2
 
3
+ # TODO: consume End signal from wrapped
4
+
3
5
  class WrapTest < Minitest::Spec
4
6
  Song = Struct.new(:id, :title) do
5
7
  def self.find(id)
@@ -7,6 +9,34 @@ class WrapTest < Minitest::Spec
7
9
  end
8
10
  end
9
11
 
12
+ class DirectWiringTest < Minitest::Spec
13
+ class Create < Trailblazer::Operation
14
+ class MyContract < Reform::Form
15
+ property :title
16
+ end
17
+
18
+ step( Wrap( ->(options, *args, &block) {
19
+ begin
20
+ block.call
21
+ rescue => exception
22
+ options["result.model.find"] = "argh! because #{exception.class}"
23
+ [ Railway.fail_fast!, options, *args ]
24
+ end }) {
25
+ step ->(options, **) { options["x"] = true }
26
+ step Model( Song, :find )
27
+ step Contract::Build( constant: MyContract )
28
+ }.merge(fast_track: true))
29
+ step Contract::Validate()
30
+ step Contract::Persist( method: :sync )
31
+ end
32
+
33
+ it { Create.( params: {id: 1, title: "Prodigal Son"} ).inspect("x", :model).must_equal %{<Result:true [true, #<struct WrapTest::Song id=1, title=\"Prodigal Son\">] >} }
34
+
35
+ it "goes directly from Wrap to End.fail_fast" do
36
+ Create.(params: {}).inspect("x", :model, "result.model.find").must_equal %{<Result:false [true, nil, "argh! because RuntimeError"] >}
37
+ end
38
+ end
39
+
10
40
  class Create < Trailblazer::Operation
11
41
  class MyContract < Reform::Form
12
42
  property :title
@@ -26,26 +56,26 @@ class WrapTest < Minitest::Spec
26
56
  step Contract::Persist( method: :sync )
27
57
  end
28
58
 
29
- it { Create.( id: 1, title: "Prodigal Son" )["contract.default"].model.inspect.must_equal %{#<struct WrapTest::Song id=1, title="Prodigal Son">} }
30
- it { Create.( id: nil ).inspect("result.model.find").must_equal %{<Result:false [\"argh! because RuntimeError\"] >} }
59
+ it { Create.( params: {id: 1, title: "Prodigal Son"} )["contract.default"].model.inspect.must_equal %{#<struct WrapTest::Song id=1, title="Prodigal Son">} }
60
+ it { Create.( params: {id: nil }).inspect("result.model.find").must_equal %{<Result:false [\"argh! because RuntimeError\"] >} }
31
61
 
32
62
  #-
33
63
  # Wrap return
34
64
  class WrapReturnTest < Minitest::Spec
35
65
  class Create < Trailblazer::Operation
36
66
  step Wrap ->(options, *, &block) { options["yield?"] ? block.call : false } {
37
- step ->(options) { options["x"] = true }
67
+ step ->(options, **) { options["x"] = true }
38
68
  success :noop!
39
69
  # ...
40
70
  }
41
71
 
42
- def noop!(options)
72
+ def noop!(options, **)
43
73
  end
44
74
  end
45
75
 
46
- it { Create.().inspect("x").must_equal %{<Result:false [nil] >} }
76
+ it { Create.(params: {}).inspect("x").must_equal %{<Result:false [nil] >} }
47
77
  # returns falsey means deviate to left.
48
- it { Create.({}, "yield?" => true).inspect("x").must_equal %{<Result:true [true] >} }
78
+ it { Create.("yield?" => true).inspect("x").must_equal %{<Result:true [true] >} }
49
79
  end
50
80
 
51
81
  class WrapWithCallableTest < Minitest::Spec
@@ -59,27 +89,16 @@ class WrapTest < Minitest::Spec
59
89
 
60
90
  class Create < Trailblazer::Operation
61
91
  step Wrap( MyWrapper ) {
62
- step ->(options) { options["x"] = true }
92
+ step ->(options, **) { options["x"] = true }
63
93
  # ...
64
94
  }
65
95
  end
66
96
 
67
- it { Create.().inspect("x").must_equal %{<Result:false [nil] >} }
97
+ it { Create.(params: {}).inspect("x").must_equal %{<Result:false [nil] >} }
68
98
  # returns falsey means deviate to left.
69
- it { Create.({}, "yield?" => true).inspect("x").must_equal %{<Result:true [true] >} }
99
+ it { Create.("yield?" => true).inspect("x").must_equal %{<Result:true [true] >} }
70
100
  end
71
101
 
72
- #-
73
- # arguments for Wrap
74
- class Update < Trailblazer::Operation
75
- step Wrap ->(options, operation, pipe, &block) { operation["yield?"] ? block.call : false } {
76
- step ->(options) { options["x"] = true }
77
- }
78
- end
79
-
80
- it { Update.().inspect("x").must_equal %{<Result:false [nil] >} }
81
- it { Update.({}, "yield?" => true).inspect("x").must_equal %{<Result:true [true] >} }
82
-
83
102
  class WrapExampleProcTest < Minitest::Spec
84
103
  module Sequel
85
104
  def self.transaction
@@ -115,14 +134,14 @@ class WrapTest < Minitest::Spec
115
134
  # handle errors after the wrap
116
135
  end
117
136
 
118
- def notify!(options)
137
+ def notify!(options, **)
119
138
  MyNotifier.mail
120
139
  end
121
140
  #~wrap-only end
122
141
  end
123
142
  #:sequel-transaction end
124
143
 
125
- it { Create.( title: "Pie" ).inspect("model", "x", "err").must_equal %{<Result:true [#<struct WrapTest::Song id=nil, title=\"Pie\">, nil, nil] >} }
144
+ it { Create.( params: {title: "Pie"} ).inspect(:model, "x", "err").must_equal %{<Result:true [#<struct WrapTest::Song id=nil, title=\"Pie\">, nil, nil] >} }
126
145
  end
127
146
 
128
147
  class WrapExampleCallableTest < Minitest::Spec
@@ -138,8 +157,6 @@ class WrapTest < Minitest::Spec
138
157
 
139
158
  #:callable-t
140
159
  class MyTransaction
141
- extend Uber::Callable
142
-
143
160
  def self.call(options, *)
144
161
  Sequel.transaction { yield } # yield runs the nested pipe.
145
162
  # return value decides about left or right track!
@@ -170,14 +187,30 @@ class WrapTest < Minitest::Spec
170
187
  # handle errors after the wrap
171
188
  end
172
189
 
173
- def notify!(options)
190
+ def notify!(options, **)
174
191
  MyNotifier.mail # send emails, because success...
175
192
  end
176
193
  #~wrap-onlyy end
177
194
  end
178
195
  #:sequel-transaction-callable end
179
196
 
180
- it { Create.( title: "Pie" ).inspect("model", "x", "err").must_equal %{<Result:true [#<struct WrapTest::Song id=nil, title=\"Pie\">, nil, nil] >} }
197
+ it { Create.( params: {title: "Pie"} ).inspect(:model, "x", "err").must_equal %{<Result:true [#<struct WrapTest::Song id=nil, title=\"Pie\">, nil, nil] >} }
198
+ end
199
+
200
+ class WrapWithMethodTest < Minitest::Spec
201
+ class Create < Trailblazer::Operation
202
+ step Model( Song, :new )
203
+ step Wrap ->(options, *, &block) { block.call } {
204
+ step :check_model!
205
+
206
+ }
207
+
208
+ def check_model!(options, model:, **)
209
+ options["x"] = model
210
+ end
211
+ end
212
+
213
+ it { Create.(params: {}) }
181
214
  end
182
215
  end
183
216
 
@@ -1,100 +1,100 @@
1
- require 'test_helper'
2
- require "trailblazer/operation/module"
3
- require "trailblazer/operation/callback"
4
- require "trailblazer/operation/contract"
1
+ # require 'test_helper'
2
+ # require "trailblazer/operation/module"
3
+ # require "trailblazer/operation/callback"
4
+ # require "trailblazer/operation/contract"
5
5
 
6
- class OperationModuleTest < MiniTest::Spec
7
- Song = Struct.new(:name, :artist)
8
- Artist = Struct.new(:id, :full_name)
6
+ # class OperationModuleTest < MiniTest::Spec
7
+ # Song = Struct.new(:name, :artist)
8
+ # Artist = Struct.new(:id, :full_name)
9
9
 
10
- class Create < Trailblazer::Operation
11
- include Trailblazer::Operation::Callback
12
- include Contract::Explicit
10
+ # class Create < Trailblazer::Operation
11
+ # include Trailblazer::Operation::Callback
12
+ # include Contract::Explicit
13
13
 
14
- contract do
15
- property :name
16
- property :artist, populate_if_empty: Artist do
17
- property :id
18
- end
19
- end
14
+ # contract do
15
+ # property :name
16
+ # property :artist, populate_if_empty: Artist do
17
+ # property :id
18
+ # end
19
+ # end
20
20
 
21
- callback do
22
- on_change :notify_me!
23
- end
21
+ # callback do
22
+ # on_change :notify_me!
23
+ # end
24
24
 
25
- attr_reader :model
26
- def call(params)
27
- self["model"] = Song.new
25
+ # attr_reader :model
26
+ # def call(params)
27
+ # self["model"] = Song.new
28
28
 
29
- validate(params, model: self["model"]) do
30
- contract.sync
29
+ # validate(params, model: self["model"]) do
30
+ # contract.sync
31
31
 
32
- dispatch!
33
- end
32
+ # dispatch!
33
+ # end
34
34
 
35
- self
36
- end
35
+ # self
36
+ # end
37
37
 
38
- def dispatched
39
- self["dispatched"] ||= []
40
- end
38
+ # def dispatched
39
+ # self["dispatched"] ||= []
40
+ # end
41
41
 
42
- private
43
- def notify_me!(*)
44
- dispatched << :notify_me!
45
- end
46
- end
42
+ # private
43
+ # def notify_me!(*)
44
+ # dispatched << :notify_me!
45
+ # end
46
+ # end
47
47
 
48
48
 
49
- module SignedIn
50
- include Trailblazer::Operation::Module
49
+ # module SignedIn
50
+ # include Trailblazer::Operation::Module
51
51
 
52
- contract do
53
- property :artist, inherit: true do
54
- property :full_name
52
+ # contract do
53
+ # property :artist, inherit: true do
54
+ # property :full_name
55
55
 
56
- puts definitions.inspect
57
- end
58
- end
56
+ # puts definitions.inspect
57
+ # end
58
+ # end
59
59
 
60
- callback do
61
- on_change :notify_you!
62
- end
60
+ # callback do
61
+ # on_change :notify_you!
62
+ # end
63
63
 
64
- def notify_you!(*)
65
- dispatched << :notify_you!
66
- end
67
- end
64
+ # def notify_you!(*)
65
+ # dispatched << :notify_you!
66
+ # end
67
+ # end
68
68
 
69
69
 
70
- class Update < Create
71
- callback do
72
- on_change :notify_them!
73
- end
70
+ # class Update < Create
71
+ # callback do
72
+ # on_change :notify_them!
73
+ # end
74
74
 
75
- include SignedIn
75
+ # include SignedIn
76
76
 
77
- def notify_them!(*)
78
- dispatched << :notify_them!
79
- end
80
- end
77
+ # def notify_them!(*)
78
+ # dispatched << :notify_them!
79
+ # end
80
+ # end
81
81
 
82
82
 
83
- it do
84
- op = Create.({name: "Feelings", artist: {id: 1, full_name: "The Offspring"}})
83
+ # it do
84
+ # op = Create.({name: "Feelings", artist: {id: 1, full_name: "The Offspring"}})
85
85
 
86
- op["dispatched"].must_equal [:notify_me!]
87
- op["model"].name.must_equal "Feelings"
88
- op["model"].artist.id.must_equal 1
89
- op["model"].artist.full_name.must_equal nil # property not declared.
90
- end
86
+ # op["dispatched"].must_equal [:notify_me!]
87
+ # op["model"].name.must_equal "Feelings"
88
+ # op["model"].artist.id.must_equal 1
89
+ # op["model"].artist.full_name.must_equal nil # property not declared.
90
+ # end
91
91
 
92
- it do
93
- op = Update.({name: "Feelings", artist: {id: 1, full_name: "The Offspring"}})
92
+ # it do
93
+ # op = Update.({name: "Feelings", artist: {id: 1, full_name: "The Offspring"}})
94
94
 
95
- op["dispatched"].must_equal [:notify_me!, :notify_them!, :notify_you!]
96
- op["model"].name.must_equal "Feelings"
97
- op["model"].artist.id.must_equal 1
98
- op["model"].artist.full_name.must_equal "The Offspring" # property declared via Module.
99
- end
100
- end
95
+ # op["dispatched"].must_equal [:notify_me!, :notify_them!, :notify_you!]
96
+ # op["model"].name.must_equal "Feelings"
97
+ # op["model"].artist.id.must_equal 1
98
+ # op["model"].artist.full_name.must_equal "The Offspring" # property declared via Module.
99
+ # end
100
+ # end
@@ -0,0 +1,293 @@
1
+ require "test_helper"
2
+
3
+ # exec_context nach Nested
4
+
5
+ class NestedTest < Minitest::Spec
6
+ #---
7
+ #- shared data
8
+ class B < Trailblazer::Operation
9
+ pass ->(options, **) { options["can.B.see.A.mutable.data?"] = options["mutable.data.from.A"] }
10
+ pass ->(options, **) { options["can.B.see.current_user?"] = options["current_user"] }
11
+ pass ->(options, **) { options["can.B.see.params?"] = options["params"] }
12
+ pass ->(options, **) { options["can.B.see.A.class.data?"] = options["A.class.data"] }
13
+ pass ->(options, **) { options["can.B.see.container.data?"] = options["some.container.data"] }
14
+ pass ->(options, **) { options["mutable.data.from.B"] = "from B!" }
15
+ end
16
+
17
+ class A < Trailblazer::Operation
18
+ extend ClassDependencies
19
+ self["A.class.data"] = "yes" # class data on A
20
+
21
+ pass ->(options, **) { options["mutable.data.from.A"] = "from A!" } # mutable data on A
22
+ step Nested( B )
23
+ pass ->(options, **) { options["can.A.see.B.mutable.data?"] = options["mutable.data.from.B"] }
24
+ end
25
+
26
+ #---
27
+ #- default behavior: share everything.
28
+ # no containers
29
+ # no runtime data
30
+ # no params
31
+ it do
32
+ result = A.("params" => {})
33
+ # everything from A visible
34
+ result["A.class.data"]. must_equal "yes"
35
+ result["mutable.data.from.A"].must_equal "from A!"
36
+
37
+ # B can see everything
38
+ result["can.B.see.A.mutable.data?"].must_equal "from A!"
39
+ result["can.B.see.current_user?"].must_be_nil
40
+ result["can.B.see.params?"].must_equal({})
41
+ result["can.B.see.A.class.data?"].must_equal "yes"
42
+ result["can.B.see.container.data?"].must_be_nil
43
+
44
+ result["can.A.see.B.mutable.data?"].must_equal "from B!"
45
+ end
46
+
47
+ #---
48
+ #- Nested::NonActivity
49
+ class AlmostB < Trailblazer::Operation
50
+ step ->(options, is_successful:raise, **) { is_successful } # {AlmostB} fails if {is_successful} isn't true.
51
+ step ->(options, **) { options["can.B.see.A.mutable.data?"] = options["mutable.data.from.A"] }
52
+ pass ->(options, **) { options["mutable.data.from.B"] = "from AlmostB!" }
53
+ end
54
+
55
+ #- Nested( ->{} )
56
+ class SomeNestedWithProc < Trailblazer::Operation
57
+ extend ClassDependencies
58
+ self["A.class.data"] = "yes" # class data on A
59
+
60
+ Decider = ->(options, use_class:raise, **) { use_class }
61
+
62
+ pass ->(options, **) { options["mutable.data.from.A"] = "from A!" } # mutable data on A
63
+ step Nested( Decider )
64
+ pass ->(options, **) { options["can.A.see.B.mutable.data?"] = options["mutable.data.from.B"] }
65
+ end
66
+
67
+ #- Nested( Callable )
68
+ class SomeNestedWithCallable < Trailblazer::Operation
69
+ extend ClassDependencies
70
+ self["A.class.data"] = "yes" # class data on A
71
+
72
+ class Decider
73
+ def self.call(options, use_class:raise, **)
74
+ use_class
75
+ end
76
+ end
77
+
78
+ pass ->(options, **) { options["mutable.data.from.A"] = "from A!" } # mutable data on A
79
+ step Nested( Decider )
80
+ pass ->(options, **) { options["can.A.see.B.mutable.data?"] = options["mutable.data.from.B"] }
81
+ end
82
+
83
+ #- Nested( :method )
84
+ class SomeNestedWithMethod < Trailblazer::Operation
85
+ extend ClassDependencies
86
+ self["A.class.data"] = "yes" # class data on A
87
+
88
+ def decider(options, use_class:raise, **)
89
+ use_class
90
+ end
91
+
92
+ pass ->(options, **) { options["mutable.data.from.A"] = "from A!" } # mutable data on A
93
+ step Nested( :decider )
94
+ pass ->(options, **) { options["can.A.see.B.mutable.data?"] = options["mutable.data.from.B"] }
95
+ end
96
+
97
+
98
+ #- test callable
99
+ # B with Callable, successful
100
+ it do
101
+ result = SomeNestedWithCallable.("params" => {}, use_class: B)
102
+ assert_b(result, is_successful: "whatever")
103
+ end
104
+
105
+ # AlmostB with Callable, successful
106
+ it do
107
+ result = SomeNestedWithCallable.("params" => {}, use_class: AlmostB, is_successful: true)
108
+ assert_almost_b(result, is_successful: true)
109
+ end
110
+
111
+ # AlmostB with Callable, failure
112
+ it do
113
+ result = SomeNestedWithCallable.("params" => {}, use_class: AlmostB, is_successful: false)
114
+ assert_almost_b(result, is_successful: false)
115
+ end
116
+
117
+ #- test proc
118
+ # B with proc, successful
119
+ it do
120
+ result = SomeNestedWithProc.("params" => {}, use_class: B)
121
+ assert_b(result, is_successful: "whatever")
122
+ end
123
+
124
+ # AlmostB with proc, successful
125
+ it do
126
+ result = SomeNestedWithProc.("params" => {}, use_class: AlmostB, is_successful: true)
127
+
128
+ assert_almost_b(result, is_successful: true)
129
+ end
130
+
131
+ # AlmostB with proc, failure.
132
+ it do
133
+ result = SomeNestedWithProc.("params" => {}, use_class: AlmostB, is_successful: false)
134
+
135
+ assert_almost_b(result, is_successful: false)
136
+ end
137
+
138
+ #- test :method
139
+ # B with method, successful
140
+ it do
141
+ result = SomeNestedWithMethod.("params" => {}, use_class: B)
142
+ assert_b(result, is_successful: "whatever")
143
+ end
144
+
145
+ # AlmostB with method, successful
146
+ it do
147
+ result = SomeNestedWithMethod.("params" => {}, use_class: AlmostB, is_successful: true)
148
+
149
+ assert_almost_b(result, is_successful: true)
150
+ end
151
+
152
+ # AlmostB with method, failure.
153
+ it do
154
+ result = SomeNestedWithMethod.("params" => {}, use_class: AlmostB, is_successful: false)
155
+
156
+ assert_almost_b(result, is_successful: false)
157
+ end
158
+
159
+
160
+ def assert_almost_b(result, is_successful:raise)
161
+ result.success?.must_equal is_successful # AlmostB was successful, so A is successful.
162
+
163
+ # everything from A visible
164
+ result["A.class.data"]. must_equal "yes"
165
+ result["mutable.data.from.A"].must_equal "from A!"
166
+
167
+ # AlmostB doesn't look for everything
168
+ result["can.B.see.current_user?"].must_be_nil
169
+ result["can.B.see.params?"].must_be_nil
170
+ if is_successful
171
+ result["can.B.see.A.mutable.data?"].must_equal "from A!"
172
+ result["can.B.see.A.class.data?"].must_equal nil # we don't look for it.
173
+ result["can.A.see.B.mutable.data?"].must_equal "from AlmostB!"
174
+ else
175
+ result["can.B.see.A.mutable.data?"].must_be_nil
176
+ result["can.B.see.A.class.data?"].must_be_nil
177
+ result["can.A.see.B.mutable.data?"].must_be_nil
178
+ end
179
+ result["can.B.see.container.data?"].must_be_nil
180
+
181
+
182
+ # result[:is_successful].must_equal is_successful # FIXME: this is wrong!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! key is symbol
183
+ end
184
+
185
+ def assert_b(result, is_successful:raise)
186
+ # everything from A visible
187
+ result["A.class.data"]. must_equal "yes"
188
+ result["mutable.data.from.A"].must_equal "from A!"
189
+
190
+ # B can see everything
191
+ result["can.B.see.A.mutable.data?"].must_equal "from A!"
192
+ result["can.B.see.current_user?"].must_be_nil
193
+ result["can.B.see.params?"].must_equal({})
194
+ result["can.B.see.A.class.data?"].must_equal "yes"
195
+ result["can.B.see.container.data?"].must_be_nil
196
+
197
+ result["can.A.see.B.mutable.data?"].must_equal "from B!"
198
+
199
+ result[:is_successful].must_be_nil
200
+ result.success?.must_equal true # B was successful, so A is successful.
201
+ end
202
+
203
+
204
+ #---
205
+ #- :exec_context
206
+ class Create < Trailblazer::Operation
207
+ class Edit < Trailblazer::Operation
208
+ step :c!
209
+
210
+ def c!(options, **); options[:c] = 1 end
211
+ end
212
+
213
+ step :a!
214
+ step Nested( Edit )
215
+ step :b!
216
+
217
+ def a!(options, **); options[:a] = 2 end
218
+ def b!(options, **); options[:b] = 3 end
219
+ end
220
+
221
+ it { Create.().inspect(:a, :b, :c).must_equal %{<Result:true [2, 3, 1] >} }
222
+ end
223
+
224
+ class NestedWithFastTrackTest < Minitest::Spec
225
+ module Steps
226
+ def b(options, a:, **)
227
+ options["b"] = a+1
228
+ end
229
+
230
+ def f(options, **)
231
+ options["f"] = 3
232
+ end
233
+ end
234
+
235
+ class Edit < Trailblazer::Operation
236
+ pass :a, pass_fast: true
237
+
238
+ def a(options, **)
239
+ options["a"] = 1
240
+ end
241
+ end
242
+
243
+ class Update < Trailblazer::Operation
244
+ step Nested( Edit )
245
+ step :b
246
+ fail :f
247
+
248
+ include Steps
249
+ end
250
+
251
+ # from Nested straight to End.pass_fast.
252
+ it { Update.({}).inspect("a", "b", "f").must_equal %{<Result:true [1, nil, nil] >} }
253
+
254
+ #- Nested, pass_fast: true
255
+ class Upsert < Trailblazer::Operation
256
+ step Nested( Edit ), pass_fast: true # this option is unnecessary.
257
+ step :b
258
+ fail :f
259
+
260
+ include Steps
261
+ end
262
+
263
+ # from Nested straight to End.pass_fast.
264
+ it { Upsert.({}).inspect("a", "b", "f").must_equal %{<Result:true [1, nil, nil] >} }
265
+
266
+ #- mapping
267
+ #- Nested, :pass_fast => :failure
268
+ it "attaches :pass_fast => :failure" do
269
+ op = Class.new(Trailblazer::Operation) do
270
+ step Nested( Edit ), Output(:pass_fast) => :failure
271
+ step :b
272
+ fail :f
273
+
274
+ include Steps
275
+ end
276
+
277
+ # from Nested to :failure track.
278
+ op.({}).inspect("a", "b", "c", "f").must_equal %{<Result:false [1, nil, nil, 3] >}
279
+ end
280
+
281
+ it "goes straigt to End.failure" do
282
+ op = Class.new(Trailblazer::Operation) do
283
+ step Nested( Edit ), Output(:pass_fast) => "End.failure"
284
+ step :b
285
+ fail :f
286
+
287
+ include Steps
288
+ end
289
+
290
+ # from Nested straight to End.failure, no fail step will be visited.
291
+ op.({}).inspect("a", "b", "c", "f").must_equal %{<Result:false [1, nil, nil, nil] >}
292
+ end
293
+ end