trailblazer-macro 2.1.1 → 2.1.6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 120b5d524c120b02535b2bb0f35a1064473682d62067f2c438e527e2f3ff5fe2
4
- data.tar.gz: 8c385fd83b0b9770718a5bb9816ba0dd4f92ff68807eee2719b2087d33ae9846
3
+ metadata.gz: d789d88096949c41a15147feed363efab5c86e586b4f76c819e12f6e8f2d4a70
4
+ data.tar.gz: fbe7775b4330bc7b607058da66c896fe461e73b1337e747c23eb87acb04b8d30
5
5
  SHA512:
6
- metadata.gz: 2ce693a9d6a46bdebdd23fcf46807b8d1c4054ca31038114bfe9e9ffdd294f33bff5f98a6877d7be17bd69f7b821715dff2d954c7aca719fb4584cd93c43c6fa
7
- data.tar.gz: 56ab8bd84575cfd7f4fcc1cf82a8fac977532de8003ff6efdbfa91ed14179b28ea50cb2bf512e8ef376a6f41c98b528e44dbb524dc0e7ad208e40f91e1b3672a
6
+ metadata.gz: df7f0de0c1107ea8c9b3803e8343907c0deb78e943ac2298a114db9fedb6de88252f5055a247b516fa3c80a2432f52913fd6d84ceb189779b58c87da6204a20f
7
+ data.tar.gz: a1e7a11851d02b26badfbff8aa6932810bc2e32ae3d1f4ce5fd73f2bf26284bff62a88b1fd27d29f5b82a7962e61fd0655b97f483483ff6df5cffdb3326f51e0
@@ -0,0 +1,17 @@
1
+ name: CI
2
+ on: [push, pull_request]
3
+ jobs:
4
+ test:
5
+ strategy:
6
+ fail-fast: false
7
+ matrix:
8
+ # Due to https://github.com/actions/runner/issues/849, we have to use quotes for '3.0'
9
+ ruby: [2.5, 2.6, 2.7, '3.0', head, jruby, jruby-head]
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v2
13
+ - uses: ruby/setup-ruby@v1
14
+ with:
15
+ ruby-version: ${{ matrix.ruby }}
16
+ bundler-cache: true # runs 'bundle install' and caches installed gems automatically
17
+ - run: bundle exec rake
data/CHANGES.md CHANGED
@@ -1,3 +1,26 @@
1
+ # 2.1.6
2
+
3
+ * Allow connecting ends of the dynamically selected activity for `Nested()` using `:auto_wire`.
4
+
5
+ # 2.1.5
6
+
7
+ * Support for Ruby 3.0.
8
+
9
+ # 2.1.4
10
+
11
+ * Upgrade DSL version to fix step's circuit interface eating passed arguments
12
+ * Upgrade OP version to remove OP::Container reference in tests
13
+
14
+ # 2.1.3
15
+
16
+ * Rename Model()'s `not_found_end` kwarg to `not_found_terminus` for consistency.
17
+
18
+ # 2.1.2
19
+
20
+ * Fix to make macros available in all Linear::DSL strategies.
21
+ * Make `params` optional in `Model`.
22
+ * Support for adding `End.not_found` end in `Model`.
23
+
1
24
  # 2.1.1
2
25
 
3
26
  * Fix case when Macros generate same id due to small entropy
data/Gemfile CHANGED
@@ -5,11 +5,11 @@ gemspec
5
5
 
6
6
  # gem "trailblazer", github: "trailblazer/trailblazer"
7
7
  # gem "trailblazer-activity", path: "../trailblazer-activity"
8
- # gem "trailblazer-activity-dsl-linear", github: "trailblazer/trailblazer-activity-dsl-linear", branch: 'master'
9
8
  # gem "trailblazer-activity-dsl-linear", path: "../trailblazer-activity-dsl-linear"
10
- # gem "trailblazer-operation", github: "trailblazer/trailblazer-operation", branch: 'linear'
11
- # gem "trailblazer-activity"#, github: "trailblazer/trailblazer-activity"
12
9
  # gem "trailblazer-macro-contract", git: "https://github.com/trailblazer/trailblazer-macro-contract"
10
+ # gem "trailblazer-context", path: "../trailblazer-context"
11
+ # gem "trailblazer-operation", path: "../trailblazer-operation"
12
+
13
13
 
14
14
  gem "minitest-line"
15
15
  gem "rubocop", require: false
@@ -1,5 +1,6 @@
1
+ require "forwardable"
1
2
  require "trailblazer/activity"
2
- require "trailblazer/activity/dsl/linear" # TODO: remove this dependency
3
+ require "trailblazer/activity/dsl/linear"
3
4
  require "trailblazer/operation" # TODO: remove this dependency
4
5
 
5
6
  require "trailblazer/macro/model"
@@ -12,18 +13,16 @@ require "trailblazer/macro/wrap"
12
13
 
13
14
  module Trailblazer
14
15
  module Macro
15
- # All macros sit in the {Trailblazer::Macro} namespace, where we forward calls from
16
- # operations and activities to.
17
- def self.forward_macros(target)
18
- target.singleton_class.def_delegators Trailblazer::Macro, :Model, :Wrap, :Rescue, :Nested
19
- target.const_set(:Policy, Trailblazer::Macro::Policy)
20
- end
21
16
  end
22
- end
23
17
 
24
- # TODO: Forwardable.def_delegators(Operation, Macro, :Model, :Wrap) would be amazing. It really sucks to extend a foreign class.
25
- # Trailblazer::Operation.singleton_class.extend Forwardable
26
- # Trailblazer::Macro.forward_macros(Trailblazer::Operation)
18
+ # All macros sit in the {Trailblazer::Macro} namespace, where we forward calls from
19
+ # operations and activities to.
20
+ module Activity::DSL::Linear::Helper
21
+ Policy = Trailblazer::Macro::Policy
27
22
 
28
- Trailblazer::Activity::FastTrack.singleton_class.extend Forwardable
29
- Trailblazer::Macro.forward_macros(Trailblazer::Activity::FastTrack) # monkey-patching sucks.
23
+ module ClassMethods
24
+ extend Forwardable
25
+ def_delegators Trailblazer::Macro, :Model, :Nested, :Wrap, :Rescue
26
+ end # ClassMethods
27
+ end # Helper
28
+ end
@@ -6,11 +6,11 @@ module Trailblazer::Macro
6
6
 
7
7
  module Guard
8
8
  def self.build(callable)
9
- option = Trailblazer::Option::KW(callable)
9
+ option = Trailblazer::Option(callable)
10
10
 
11
11
  # this gets wrapped in a Operation::Result object.
12
- ->((options, *), circuit_args) do
13
- Trailblazer::Operation::Result.new(!!option.call(options, circuit_args), {})
12
+ ->((ctx, *), **circuit_args) do
13
+ Trailblazer::Operation::Result.new(!!option.call(ctx, keyword_arguments: ctx.to_hash, **circuit_args), {})
14
14
  end
15
15
  end
16
16
  end
@@ -1,5 +1,8 @@
1
1
  module Trailblazer::Macro
2
- def self.Model(model_class, action = nil, find_by_key = nil)
2
+
3
+ Linear = Trailblazer::Activity::DSL::Linear
4
+
5
+ def self.Model(model_class, action = nil, find_by_key = nil, id: 'model.build', not_found_terminus: false)
3
6
  task = Trailblazer::Activity::TaskBuilder::Binary(Model.new)
4
7
 
5
8
  injection = Trailblazer::Activity::TaskWrap::Inject::Defaults::Extension(
@@ -8,11 +11,14 @@ module Trailblazer::Macro
8
11
  :"model.find_by_key" => find_by_key
9
12
  )
10
13
 
11
- {task: task, id: "model.build", extensions: [injection]}
14
+ options = { task: task, id: id, extensions: [injection] }
15
+ options = options.merge(Linear::Output(:failure) => Linear::End(:not_found)) if not_found_terminus
16
+
17
+ options
12
18
  end
13
19
 
14
20
  class Model
15
- def call(options, params:, **)
21
+ def call(options, params: nil, **)
16
22
  builder = Model::Builder.new
17
23
  options[:model] = model = builder.call(options, params)
18
24
  options[:"result.model"] = result = Trailblazer::Operation::Result.new(!model.nil?, {})
@@ -2,14 +2,14 @@
2
2
  module Trailblazer
3
3
  module Macro
4
4
  # {Nested} macro.
5
- def self.Nested(callable, id: "Nested(#{callable})")
5
+ def self.Nested(callable, id: "Nested(#{callable})", auto_wire: [])
6
6
  if callable.is_a?(Class) && callable < Nested.operation_class
7
7
  warn %{[Trailblazer] Using the `Nested()` macro with operations and activities is deprecated. Replace `Nested(Create)` with `Subprocess(Create)`.}
8
8
  return Nested.operation_class.Subprocess(callable)
9
9
  end
10
10
 
11
11
  # dynamic
12
- task = Nested::Dynamic.new(callable)
12
+ task = Nested::Dynamic.new(callable, auto_wire: auto_wire)
13
13
 
14
14
  merge = [
15
15
  [Activity::TaskWrap::Pipeline.method(:insert_before), "task_wrap.call_task", ["Nested.compute_nested_activity", task.method(:compute_nested_activity)]],
@@ -33,17 +33,24 @@ module Trailblazer
33
33
  end
34
34
 
35
35
  # For dynamic `Nested`s that do not expose an {Activity} interface.
36
- # Since we do not know its outputs, we have to map them to :success and :failure, only.
37
36
  #
38
- # This is what {Nested} in 2.0 used to do, where the outcome could only be true/false (or success/failure).
37
+ # Dynamic doesn't automatically connect outputs of runtime {Activity}
38
+ # at compile time (as we don't know which activity will be nested, obviously).
39
+ # So by default, it only connects good old success/failure ends. But it is also
40
+ # possible to connect all the ends of all possible dynamic activities
41
+ # by passing their list to {:auto_wire} option.
42
+ #
43
+ # step Nested(:compute_nested, auto_wire: [Create, Update])
39
44
  class Dynamic
40
- def initialize(nested_activity_decider)
41
- @nested_activity_decider = Option::KW(nested_activity_decider)
45
+ STATIC_OUTPUTS = {
46
+ :success => Activity::Output(Activity::Railway::End::Success.new(semantic: :success), :success),
47
+ :failure => Activity::Output(Activity::Railway::End::Failure.new(semantic: :failure), :failure),
48
+ }
42
49
 
43
- @outputs = {
44
- :success => Activity::Output(Activity::Railway::End::Success.new(semantic: :success), :success),
45
- :failure => Activity::Output(Activity::Railway::End::Failure.new(semantic: :failure), :failure)
46
- }
50
+ def initialize(nested_activity_decider, auto_wire: [])
51
+ @nested_activity_decider = Trailblazer::Option(nested_activity_decider)
52
+ @known_activities = Array(auto_wire)
53
+ @outputs = compute_task_outputs
47
54
  end
48
55
 
49
56
  attr_reader :outputs
@@ -53,7 +60,7 @@ module Trailblazer
53
60
  (ctx, _), original_circuit_options = original_args
54
61
 
55
62
  # TODO: evaluate the option to get the actual "object" to call.
56
- activity = @nested_activity_decider.(ctx, original_circuit_options)
63
+ activity = @nested_activity_decider.(ctx, keyword_arguments: ctx.to_hash, **original_circuit_options)
57
64
 
58
65
  # Overwrite :task so task_wrap.call_task will call this activity.
59
66
  # This is a trick so we don't have to repeat logic from #call_task here.
@@ -63,13 +70,28 @@ module Trailblazer
63
70
  end
64
71
 
65
72
  def compute_return_signal(wrap_ctx, original_args)
66
- # Translate the genuine nested signal to the generic Dynamic end (success/failure, only).
67
- # Note that here we lose information about what specific event was emitted.
68
- wrap_ctx[:return_signal] = wrap_ctx[:return_signal].kind_of?(Activity::Railway::End::Success) ?
69
- @outputs[:success].signal : @outputs[:failure].signal
73
+ # NOOP when @known_activities are present as all possible signals have been registered already.
74
+ if @known_activities.empty?
75
+ # Translate the genuine nested signal to the generic Dynamic end (success/failure, only).
76
+ # Note that here we lose information about what specific event was emitted.
77
+ wrap_ctx[:return_signal] = wrap_ctx[:return_signal].kind_of?(Activity::Railway::End::Success) ?
78
+ @outputs[:success].signal : @outputs[:failure].signal
79
+ end
70
80
 
71
81
  return wrap_ctx, original_args
72
82
  end
83
+
84
+ private def compute_task_outputs
85
+ # If :auto_wire is empty, we map outputs to :success and :failure only, for backward compatibility.
86
+ # This is what {Nested} in 2.0 used to do, where the outcome could only be true/false (or success/failure).
87
+ return STATIC_OUTPUTS if @known_activities.empty?
88
+
89
+ # Merge activity#outputs from all given auto_wirable activities to wire up for this dynamic task.
90
+ @known_activities.map do |activity|
91
+ # TODO: Replace this when it's helper gets added.
92
+ Hash[activity.to_h[:outputs].collect{ |output| [output.semantic, output] }]
93
+ end.inject(:merge)
94
+ end
73
95
  end
74
96
  end
75
97
  end
@@ -1,7 +1,7 @@
1
1
  module Trailblazer
2
2
  module Version
3
3
  module Macro
4
- VERSION = "2.1.1"
4
+ VERSION = "2.1.6"
5
5
  end
6
6
  end
7
7
  end
@@ -48,7 +48,7 @@ module Trailblazer
48
48
 
49
49
  def call((ctx, flow_options), **circuit_options)
50
50
  block_calling_wrapped = -> {
51
- call_wrapped_activity([ctx, flow_options], circuit_options)
51
+ call_wrapped_activity([ctx, flow_options], **circuit_options)
52
52
  }
53
53
 
54
54
  # call the user's Wrap {} block in the operation.
@@ -113,8 +113,6 @@ end
113
113
  class DocsGuardInjectionTest < Minitest::Spec
114
114
  #:di-op
115
115
  class Create < Trailblazer::Operation
116
- extend Trailblazer::Operation::Container
117
-
118
116
  step Policy::Guard( ->(options, current_user:, **) { current_user == Module } )
119
117
  end
120
118
  #:di-op end
@@ -124,9 +122,8 @@ class DocsGuardInjectionTest < Minitest::Spec
124
122
  result =
125
123
  #:di-call
126
124
  Create.(
127
- {},
128
- :current_user => Module,
129
- :"policy.default.eval" => Trailblazer::Operation::Policy::Guard.build(->(options, **) { false })
125
+ :current_user => Module,
126
+ :"policy.default.eval" => Trailblazer::Operation::Policy::Guard.build(->(options, **) { false })
130
127
  )
131
128
  #:di-call end
132
129
  result.inspect("").must_equal %{<Result:false [nil] >} }
@@ -45,6 +45,13 @@ class DocsModelTest < Minitest::Spec
45
45
  end
46
46
  #:update-with-find-by-key end
47
47
 
48
+ #:update-with-not-found-end
49
+ class UpdateFailureWithModelNotFound < Trailblazer::Operation
50
+ step Model( Song, :find_by, not_found_terminus: true )
51
+ # ..
52
+ end
53
+ #:update-with-not-found-end end
54
+
48
55
  it do
49
56
  #:update-ok
50
57
  result = Update.(params: { id: 1 })
@@ -83,6 +90,19 @@ class DocsModelTest < Minitest::Spec
83
90
  result.success?.must_equal false
84
91
  end
85
92
 
93
+ it do
94
+ #:update-with-not-found-end-use
95
+ result = UpdateFailureWithModelNotFound.(params: {title: nil})
96
+ result[:model] #=> nil
97
+ result.success? #=> false
98
+ result.event #=> #<Trailblazer::Activity::End semantic=:not_found>
99
+ #:update-with-not-found-end-use end
100
+
101
+ result[:model].must_be_nil
102
+ result.success?.must_equal false
103
+ result.event.inspect.must_equal %{#<Trailblazer::Activity::End semantic=:not_found>}
104
+ end
105
+
86
106
  #:show
87
107
  class Show < Trailblazer::Operation
88
108
  step Model( Song, :[] )
@@ -22,6 +22,11 @@ class NestedInput < Minitest::Spec
22
22
  include T.def_steps(:validate)
23
23
  end
24
24
 
25
+ class JsonValidate < Validate
26
+ step :json
27
+ include T.def_steps(:json)
28
+ end
29
+
25
30
  it "Nested(Edit), without any options" do
26
31
  module A
27
32
 
@@ -79,11 +84,6 @@ class NestedInput < Minitest::Spec
79
84
  #~meths end
80
85
  end
81
86
  #:nested-dynamic end
82
-
83
- class JsonValidate < Validate
84
- step :json
85
- include T.def_steps(:json)
86
- end
87
87
  # `edit` and `update` can be called from Nested()
88
88
 
89
89
  # edit/success
@@ -119,11 +119,6 @@ class NestedInput < Minitest::Spec
119
119
  end
120
120
  #:nested-dynamic end
121
121
 
122
- class JsonValidate < Validate
123
- step :json
124
- include T.def_steps(:json)
125
- end
126
-
127
122
  # `edit` and `update` can be called from Nested()
128
123
  end
129
124
 
@@ -131,29 +126,64 @@ class NestedInput < Minitest::Spec
131
126
  C::Create.(seq: [], params: nil).inspect(:seq).must_equal %{<Result:true [[:create, :validate, :json, :save]] >}
132
127
  end
133
128
 
134
- let(:compute_edit) {
135
- ->(ctx, what:, **) { what }
136
- }
129
+ it "Nested(:method), with pass_fast returned from nested" do
130
+ class JustPassFast < Trailblazer::Operation
131
+ step :just_pass_fast, pass_fast: true
132
+ include T.def_steps(:just_pass_fast)
133
+ end
137
134
 
138
- it "Nested(:method), :pass_fast => :fail_fast doesn't work with standard wiring" do
139
- skip "we need to allow adding :outputs"
135
+ module D
140
136
 
141
- compute_edit = self.compute_edit
137
+ create =
138
+ #:nested-with-pass-fast
139
+ class Create < Trailblazer::Operation
142
140
 
143
- pass_fast = Class.new(Trailblazer::Operation) do
144
- step :p, pass_fast: true
145
- include T.def_steps(:p)
146
- end
141
+ def compute_nested(ctx, **)
142
+ JustPassFast
143
+ end
147
144
 
148
- create = Class.new(Trailblazer::Operation) do
149
- step :a
150
- step Nested(compute_edit, auto_wire: [pass_fast]), Output(:pass_fast) => Track(:fail_fast)
151
- step :b
152
- include T.def_steps(:a, :b)
145
+ step :create
146
+ step Nested(:compute_nested)
147
+ step :save
148
+ #~meths
149
+ include T.def_steps(:create, :save)
150
+ #~meths end
151
+ end
152
+ #:nested-with-pass-fast end
153
+
154
+ # pass fast
155
+ create.(seq: []).inspect(:seq).must_equal %{<Result:true [[:create, :just_pass_fast, :save]] >}
153
156
  end
157
+ end
158
+
159
+ it "Nested(:method, auto_wire: *activities) with :pass_fast => End()" do
160
+ module E
161
+ class JsonValidate < Trailblazer::Operation
162
+ step :validate, Output(:success) => End(:json_validate)
163
+ include T.def_steps(:validate)
164
+ end
165
+
166
+ #:nested-with-auto-wire
167
+ class Create < Trailblazer::Operation
168
+ step :create
169
+ step Nested(:compute_nested, auto_wire: [Validate, JsonValidate]),
170
+ Output(:json_validate) => End(:jsoned)
154
171
 
172
+ #~meths
173
+ def compute_nested(ctx, what:, **)
174
+ what
175
+ end
155
176
 
156
- create.(seq: [], what: pass_fast).inspect(:seq).must_equal %{<Result:false [[:a, :c]] >}
177
+ include T.def_steps(:create)
178
+ #~meths end
179
+ end
180
+ #:nested-with-auto-wire end
181
+
182
+ result = Create.(seq: [], what: JsonValidate)
183
+
184
+ result.inspect(:seq).must_equal %{<Result:false [[:create, :validate]] >}
185
+ result.event.inspect.must_equal %{#<Trailblazer::Activity::End semantic=:jsoned>}
186
+ end
157
187
  end
158
188
  end
159
189
 
@@ -126,4 +126,29 @@ Rescue( handler: :instance_method )
126
126
  it { Memo::Create.( { seq: [], } ).inspect(:seq, :exception_class).must_equal %{<Result:true [[:find_model, :update, :rehash, :notify], nil] >} }
127
127
  it { Memo::Create.( { seq: [], rehash_raise: true } ).inspect(:seq, :exception_class).must_equal %{<Result:false [[:find_model, :update, :rehash, :log_error], RuntimeError] >} }
128
128
  end
129
+
130
+ =begin
131
+ Rescue(), fast_track: true {}
132
+ =end
133
+ class RescueWithFastTrack < Minitest::Spec
134
+ Memo = Class.new
135
+
136
+ #:rescue-fasttrack
137
+ class Memo::Create < Trailblazer::Operation
138
+ rescue_block = ->(*) {
139
+ step :update, Output(:failure) => End(:fail_fast)
140
+ step :rehash
141
+ }
142
+
143
+ step :find_model
144
+ step Rescue(&rescue_block), fail_fast: true
145
+ step :notify
146
+ fail :log_error
147
+ #~methods
148
+ include T.def_steps(:find_model, :update, :notify, :log_error, :rehash)
149
+ end
150
+
151
+ it { Memo::Create.( { seq: [], } ).inspect(:seq).must_equal %{<Result:true [[:find_model, :update, :rehash, :notify]] >} }
152
+ it { Memo::Create.( { seq: [], update: false } ).inspect(:seq).must_equal %{<Result:false [[:find_model, :update]] >} }
153
+ end
129
154
  end
@@ -1,9 +1,6 @@
1
1
  require "test_helper"
2
2
 
3
3
  class DocsWrapTest < Minitest::Spec
4
- module Memo
5
- end
6
-
7
4
  =begin
8
5
  When success: return the block's returns
9
6
  When raise: return {Railway.fail!}
@@ -11,11 +8,10 @@ When raise: return {Railway.fail!}
11
8
  #:wrap-handler
12
9
  class HandleUnsafeProcess
13
10
  def self.call((ctx, flow_options), *, &block)
14
- begin
15
- yield # calls the wrapped steps
16
- rescue
17
- [ Trailblazer::Operation::Railway.fail!, [ctx, flow_options] ]
18
- end
11
+ yield # calls the wrapped steps
12
+ rescue
13
+ ctx[:exception] = $!.message
14
+ [ Trailblazer::Operation::Railway.fail!, [ctx, flow_options] ]
19
15
  end
20
16
  end
21
17
  #:wrap-handler end
@@ -64,6 +60,11 @@ result.wtf? #=>
64
60
  =end
65
61
  end
66
62
 
63
+ =begin
64
+ Writing into ctx in a Wrap()
65
+ =end
66
+ it { Memo::Create.( { seq: [], rehash_raise: true } )[:exception].must_equal("nope!") }
67
+
67
68
  =begin
68
69
  When success: return the block's returns
69
70
  When raise: return {Railway.fail!}, but wire Wrap() to {fail_fast: true}
@@ -74,13 +75,9 @@ When raise: return {Railway.fail!}, but wire Wrap() to {fail_fast: true}
74
75
  class Memo::Create < Trailblazer::Operation
75
76
  class HandleUnsafeProcess
76
77
  def self.call((ctx), *, &block)
77
- begin
78
- yield # calls the wrapped steps
79
- rescue
80
- puts $!
81
- ctx[:exception] = $!.message
82
- [ Trailblazer::Operation::Railway.fail!, [ctx, {}] ]
83
- end
78
+ yield # calls the wrapped steps
79
+ rescue
80
+ [ Trailblazer::Operation::Railway.fail!, [ctx, {}] ]
84
81
  end
85
82
  end
86
83
 
@@ -112,11 +109,9 @@ When raise: return {Railway.fail_fast!} and configure Wrap() to {fast_track: t
112
109
  #:fail-fast-handler
113
110
  class HandleUnsafeProcess
114
111
  def self.call((ctx), *, &block)
115
- begin
116
- yield # calls the wrapped steps
117
- rescue
118
- [ Trailblazer::Operation::Railway.fail_fast!, [ctx, {}] ]
119
- end
112
+ yield # calls the wrapped steps
113
+ rescue
114
+ [ Trailblazer::Operation::Railway.fail_fast!, [ctx, {}] ]
120
115
  end
121
116
  end
122
117
  #:fail-fast-handler end
@@ -145,122 +140,176 @@ When raise: return {Railway.fail_fast!} and configure Wrap() to {fast_track: t
145
140
  When success: return the block's returns
146
141
  When raise: return {Railway.fail!} or {Railway.pass!}
147
142
  =end
148
- class WrapWithTransactionTest < Minitest::Spec
149
- Memo = Module.new
143
+ class WrapWithCustomEndsTest < Minitest::Spec
144
+ Memo = Module.new
150
145
 
151
- module Sequel
152
- def self.transaction
153
- begin
154
- end_event, (ctx, flow_options) = yield
155
- true
156
- rescue
157
- false
158
- end
146
+ #:custom-handler
147
+ class MyTransaction
148
+ MyFailSignal = Class.new(Trailblazer::Activity::Signal)
149
+
150
+ def self.call((ctx, flow_options), *, &block)
151
+ yield # calls the wrapped steps
152
+ rescue
153
+ MyFailSignal
159
154
  end
160
155
  end
156
+ #:custom-handler end
161
157
 
162
- #:transaction-handler
163
- class MyTransaction
164
- def self.call((ctx, flow_options), *, &block)
165
- result = Sequel.transaction { yield }
158
+ #:custom
159
+ class Memo::Create < Trailblazer::Operation
160
+ step :find_model
161
+ step Wrap( MyTransaction ) {
162
+ step :update
163
+ step :rehash
164
+ },
165
+ Output(:success) => End(:transaction_worked),
166
+ Output(MyTransaction::MyFailSignal, :failure) => End(:transaction_failed)
167
+ step :notify
168
+ fail :log_error
169
+ #~methods
170
+ include T.def_steps(:find_model, :update, :notify, :log_error)
171
+ include Rehash
172
+ #~methods end
173
+ end
174
+ #:custom end
166
175
 
167
- signal = result ? Trailblazer::Operation::Railway.pass! : Trailblazer::Operation::Railway.fail!
176
+ it do
177
+ result = Memo::Create.( { seq: [] } )
178
+ result.inspect(:seq).must_equal %{<Result:false [[:find_model, :update, :rehash]] >}
179
+ result.event.inspect.must_equal %{#<Trailblazer::Activity::End semantic=:transaction_worked>}
180
+ end
168
181
 
169
- [ signal, [ctx, flow_options] ]
170
- end
182
+ it do
183
+ result = Memo::Create.( { seq: [], rehash_raise: true } )
184
+ result.inspect(:seq).must_equal %{<Result:false [[:find_model, :update, :rehash]] >}
185
+ result.event.inspect.must_equal %{#<Trailblazer::Activity::End semantic=:transaction_failed>}
171
186
  end
172
- #:transaction-handler end
187
+ end
188
+
189
+ =begin
190
+ When success: return the block's returns
191
+ When raise: return {Railway.pass!} and go "successful"
192
+ =end
193
+ class WrapGoesIntoPassFromRescueTest < Minitest::Spec
194
+ Memo = Module.new
173
195
 
174
- #:transaction
175
196
  class Memo::Create < Trailblazer::Operation
197
+ class HandleUnsafeProcess
198
+ def self.call((ctx), *, &block)
199
+ yield # calls the wrapped steps
200
+ rescue
201
+ [ Trailblazer::Operation::Railway.pass!, [ctx, {}] ]
202
+ end
203
+ end
204
+
176
205
  step :find_model
177
- #:wrap-only
178
- step Wrap( MyTransaction ) {
206
+ step Wrap( HandleUnsafeProcess ) {
179
207
  step :update
180
208
  step :rehash
181
209
  }
182
- #:wrap-only end
183
210
  step :notify
184
211
  fail :log_error
212
+
185
213
  #~methods
186
214
  include T.def_steps(:find_model, :update, :notify, :log_error)
187
215
  include Rehash
188
216
  #~methods end
189
217
  end
190
- #:transaction end
191
218
 
192
219
  it { Memo::Create.( { seq: [] } ).inspect(:seq).must_equal %{<Result:true [[:find_model, :update, :rehash, :notify]] >} }
193
- it { Memo::Create.( { seq: [], rehash_raise: true } ).inspect(:seq).must_equal %{<Result:false [[:find_model, :update, :rehash, :log_error]] >} }
220
+ it { Memo::Create.( { seq: [], rehash_raise: true } ).inspect(:seq).must_equal %{<Result:true [[:find_model, :update, :rehash, :notify]] >} }
194
221
  end
195
222
 
196
223
  =begin
197
224
  When success: return the block's returns
198
- When raise: return {Railway.fail!} or {Railway.pass!}
225
+ When raise: return {true} and go "successful"
226
+ You can return boolean true in wrap.
199
227
  =end
200
- class WrapWithCustomEndsTest < Minitest::Spec
201
- Memo = Module.new
202
- Sequel = WrapWithTransactionTest::Sequel
228
+ class WrapGoesIntoBooleanTrueFromRescueTest < Minitest::Spec
229
+ Memo = Module.new
203
230
 
204
- #:custom-handler
205
- class MyTransaction
206
- MyFailSignal = Class.new(Trailblazer::Activity::Signal)
231
+ class Memo::Create < Trailblazer::Operation
232
+ class HandleUnsafeProcess
233
+ def self.call((ctx), *, &block)
234
+ yield # calls the wrapped steps
235
+ rescue
236
+ true
237
+ end
238
+ end
207
239
 
208
- def self.call((ctx, flow_options), *, &block)
209
- result = Sequel.transaction { yield }
240
+ step :find_model
241
+ step Wrap( HandleUnsafeProcess ) {
242
+ step :update
243
+ step :rehash
244
+ }
245
+ step :notify
246
+ fail :log_error
210
247
 
211
- signal = result ? Trailblazer::Operation::Railway.pass! : MyFailSignal
248
+ #~methods
249
+ include T.def_steps(:find_model, :update, :notify, :log_error)
250
+ include Rehash
251
+ #~methods end
252
+ end
212
253
 
213
- [ signal, [ctx, flow_options] ]
214
- end
254
+ it "translates true returned form a wrap to a signal with a `success` semantic" do
255
+ result = Memo::Create.( { seq: [], rehash_raise: true } )
256
+ result.inspect(:seq).must_equal %{<Result:true [[:find_model, :update, :rehash, :notify]] >}
257
+ result.event.inspect.must_equal %{#<Trailblazer::Activity::Railway::End::Success semantic=:success>}
215
258
  end
216
- #:custom-handler end
259
+ end
260
+
261
+ =begin
262
+ When success: return the block's returns
263
+ When raise: return {false} and go "failed"
264
+ You can return boolean false in wrap.
265
+ =end
266
+ class WrapGoesIntoBooleanFalseFromRescueTest < Minitest::Spec
267
+ Memo = Module.new
217
268
 
218
- #:custom
219
269
  class Memo::Create < Trailblazer::Operation
270
+ class HandleUnsafeProcess
271
+ def self.call((ctx), *, &block)
272
+ yield # calls the wrapped steps
273
+ rescue
274
+ false
275
+ end
276
+ end
277
+
220
278
  step :find_model
221
- step Wrap( MyTransaction ) {
279
+ step Wrap( HandleUnsafeProcess ) {
222
280
  step :update
223
281
  step :rehash
224
- },
225
- Output(:success) => End(:transaction_worked),
226
- Output(MyTransaction::MyFailSignal, :failure) => End(:transaction_failed)
282
+ }
227
283
  step :notify
228
284
  fail :log_error
285
+
229
286
  #~methods
230
287
  include T.def_steps(:find_model, :update, :notify, :log_error)
231
288
  include Rehash
232
289
  #~methods end
233
290
  end
234
- #:custom end
235
-
236
- it do
237
- result = Memo::Create.( { seq: [] } )
238
- result.inspect(:seq).must_equal %{<Result:false [[:find_model, :update, :rehash]] >}
239
- result.event.inspect.must_equal %{#<Trailblazer::Activity::End semantic=:transaction_worked>}
240
- end
241
291
 
242
- it do
292
+ it "translates false returned form a wrap to a signal with a `failure` semantic" do
243
293
  result = Memo::Create.( { seq: [], rehash_raise: true } )
244
- result.inspect(:seq).must_equal %{<Result:false [[:find_model, :update, :rehash]] >}
245
- result.event.inspect.must_equal %{#<Trailblazer::Activity::End semantic=:transaction_failed>}
294
+ result.inspect(:seq).must_equal %{<Result:false [[:find_model, :update, :rehash, :log_error]] >}
295
+ result.event.inspect.must_equal %{#<Trailblazer::Activity::Railway::End::Failure semantic=:failure>}
246
296
  end
247
297
  end
248
298
 
249
299
  =begin
250
300
  When success: return the block's returns
251
- When raise: return {Railway.pass!} and go "successful"
301
+ When raise: return {nil} and go "failed"
302
+ You can return nil in wrap.
252
303
  =end
253
- class WrapGoesIntoPassFromRescueTest < Minitest::Spec
304
+ class WrapGoesIntoNilFromRescueTest < Minitest::Spec
254
305
  Memo = Module.new
255
306
 
256
307
  class Memo::Create < Trailblazer::Operation
257
308
  class HandleUnsafeProcess
258
309
  def self.call((ctx), *, &block)
259
- begin
260
- yield # calls the wrapped steps
261
- rescue
262
- [ Trailblazer::Operation::Railway.pass!, [ctx, {}] ]
263
- end
310
+ yield # calls the wrapped steps
311
+ rescue
312
+ nil
264
313
  end
265
314
  end
266
315
 
@@ -278,7 +327,94 @@ When raise: return {Railway.pass!} and go "successful"
278
327
  #~methods end
279
328
  end
280
329
 
330
+ it "translates nil returned form a wrap to a signal with a `failure` semantic" do
331
+ result = Memo::Create.( { seq: [], rehash_raise: true } )
332
+ result.inspect(:seq).must_equal %{<Result:false [[:find_model, :update, :rehash, :log_error]] >}
333
+ result.event.inspect.must_equal %{#<Trailblazer::Activity::Railway::End::Failure semantic=:failure>}
334
+ end
335
+ end
336
+
337
+ =begin
338
+ When success: return the block's returns
339
+ When raise: return {Railway.fail!}
340
+ This one is mostly to show how one could wrap steps in a transaction
341
+ =end
342
+ class WrapWithTransactionTest < Minitest::Spec
343
+ Memo = Module.new
344
+
345
+ module Sequel
346
+ def self.transaction
347
+ end_event, (ctx, flow_options) = yield
348
+ end
349
+ end
350
+
351
+ #:transaction-handler
352
+ class MyTransaction
353
+ def self.call((ctx, flow_options), *, &block)
354
+ Sequel.transaction { yield } # calls the wrapped steps
355
+ rescue
356
+ [ Trailblazer::Operation::Railway.fail!, [ctx, flow_options] ]
357
+ end
358
+ end
359
+ #:transaction-handler end
360
+
361
+ #:transaction
362
+ class Memo::Create < Trailblazer::Operation
363
+ step :find_model
364
+ step Wrap( MyTransaction ) {
365
+ step :update
366
+ step :rehash
367
+ }
368
+ step :notify
369
+ fail :log_error
370
+ #~methods
371
+ include T.def_steps(:find_model, :update, :notify, :log_error)
372
+ include Rehash
373
+ #~methods end
374
+ end
375
+ #:transaction end
376
+
281
377
  it { Memo::Create.( { seq: [] } ).inspect(:seq).must_equal %{<Result:true [[:find_model, :update, :rehash, :notify]] >} }
282
- it { Memo::Create.( { seq: [], rehash_raise: true } ).inspect(:seq).must_equal %{<Result:true [[:find_model, :update, :rehash, :notify]] >} }
378
+ it { Memo::Create.( { seq: [], rehash_raise: true } ).inspect(:seq).must_equal %{<Result:false [[:find_model, :update, :rehash, :log_error]] >} }
379
+ end
380
+
381
+ =begin
382
+ When success: return {Railway.pass_fast!}
383
+ When failure: return {Railway.fail!}
384
+ This one is mostly to show how one could evaluate Wrap()'s return value based on Wrap() block's return
385
+ =end
386
+ class WrapWithBlockReturnSignatureCheckTest < Minitest::Spec
387
+ Memo = Module.new
388
+
389
+ #:handler-with-signature-evaluator
390
+ class HandleUnsafeProcess
391
+ def self.call((_ctx, _flow_options), *, &block)
392
+ signal, (ctx, flow_options) = yield
393
+ evaluated_signal = if signal.to_h[:semantic] == :success
394
+ Trailblazer::Operation::Railway.pass_fast!
395
+ else
396
+ Trailblazer::Operation::Railway.fail!
397
+ end
398
+ [ evaluated_signal, [ctx, flow_options] ]
399
+ end
400
+ end
401
+ #:handler-with-signature-evaluator end
402
+
403
+ #:transaction
404
+ class Memo::Create < Trailblazer::Operation
405
+ step :find_model
406
+ step Wrap( HandleUnsafeProcess ) {
407
+ step :update
408
+ }, fast_track: true # because Wrap can return pass_fast! now
409
+ step :notify
410
+ fail :log_error
411
+ #~methods
412
+ include T.def_steps(:find_model, :update, :notify, :log_error)
413
+ #~methods end
414
+ end
415
+ #:transaction end
416
+
417
+ it { Memo::Create.( { seq: [] } ).inspect(:seq).must_equal %{<Result:true [[:find_model, :update]] >} }
418
+ it { Memo::Create.( { seq: [], update: false } ).inspect(:seq).must_equal %{<Result:false [[:find_model, :update, :log_error]] >} }
283
419
  end
284
420
  end
@@ -19,11 +19,8 @@ class ModelTest < Minitest::Spec
19
19
  end
20
20
 
21
21
  # :new new.
22
- it { Create.(params: {})[:model].inspect.must_equal %{#<struct ModelTest::Song id=nil, title=nil>} }
23
- it do
24
-
25
- result = Create.(params: {})
26
-
22
+ it "initializes new model's instance" do
23
+ result = Create.()
27
24
  result[:model].inspect.must_equal %{#<struct ModelTest::Song id=nil, title=nil>}
28
25
  end
29
26
 
@@ -0,0 +1,65 @@
1
+ require 'test_helper'
2
+
3
+ class NestedTest < Minitest::Spec
4
+ DatabaseError = Class.new(Trailblazer::Activity::Signal)
5
+
6
+ class SignUp < Trailblazer::Operation
7
+ def self.b(ctx, **)
8
+ ctx[:seq] << :b
9
+ return DatabaseError
10
+ end
11
+
12
+ step method(:b), Output(DatabaseError, :db_error) => End(:db_error)
13
+ end
14
+
15
+ class SignIn < Trailblazer::Operation
16
+ include T.def_steps(:c)
17
+ step :c
18
+ end
19
+
20
+ it "allows connection with custom output of a nested activity" do
21
+ create = Class.new(Trailblazer::Operation) do
22
+ include T.def_steps(:a, :d)
23
+
24
+ step :a
25
+ step Nested(SignUp), Output(:db_error) => Track(:no_user)
26
+ step :d, magnetic_to: :no_user
27
+ end
28
+
29
+ result = create.(seq: [])
30
+ result.inspect(:seq).must_equal %{<Result:true [[:a, :b, :d]] >}
31
+ result.event.inspect.must_equal %{#<Trailblazer::Activity::Railway::End::Success semantic=:success>}
32
+ end
33
+
34
+ it "allows connecting dynamically nested activities with custom output when auto wired" do
35
+ create = Class.new(Trailblazer::Operation) do
36
+ def compute_nested(ctx, what:, **)
37
+ what
38
+ end
39
+
40
+ include T.def_steps(:a, :d)
41
+
42
+ step :a
43
+ step Nested(:compute_nested, auto_wire: [SignUp, SignIn]), Output(:db_error) => Track(:no_user)
44
+ step :d, magnetic_to: :no_user
45
+ end
46
+
47
+ result = create.(seq: [], what: SignUp)
48
+ result.inspect(:seq).must_equal %{<Result:true [[:a, :b, :d]] >}
49
+ result.event.inspect.must_equal %{#<Trailblazer::Activity::Railway::End::Success semantic=:success>}
50
+ end
51
+
52
+ it "raises RuntimeError if dynamically nested activities with custom output are not auto wired" do
53
+ exception = assert_raises RuntimeError do
54
+ Class.new(Trailblazer::Operation) do
55
+ def compute_nested(ctx, what:, **)
56
+ what
57
+ end
58
+
59
+ step Nested(:compute_nested), Output(:db_error) => Track(:no_user)
60
+ end
61
+ end
62
+
63
+ exception.inspect.must_match 'No `db_error` output found'
64
+ end
65
+ end
data/test/test_helper.rb CHANGED
@@ -1,9 +1,10 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+ require "trailblazer/macro"
3
+
1
4
  require "delegate" # Ruby 2.2
2
5
  require "minitest/autorun"
3
6
 
4
7
  require "trailblazer/developer"
5
- require "trailblazer/macro"
6
-
7
8
 
8
9
  module Mock
9
10
  class Result
@@ -39,7 +40,7 @@ T = Trailblazer::Activity::Testing
39
40
  module Rehash
40
41
  def rehash(ctx, seq:, rehash_raise: false, **)
41
42
  seq << :rehash
42
- raise if rehash_raise
43
+ raise "nope!" if rehash_raise
43
44
  true
44
45
  end
45
46
  end
@@ -25,7 +25,8 @@ Gem::Specification.new do |spec|
25
25
  spec.add_development_dependency "roar"
26
26
  spec.add_development_dependency "trailblazer-developer"
27
27
 
28
- spec.add_dependency "trailblazer-operation", ">= 0.6.0" # TODO: this dependency will be removed.
28
+ spec.add_dependency "trailblazer-activity-dsl-linear", ">= 0.4.0", "< 0.5.0"
29
+ spec.add_dependency "trailblazer-operation", ">= 0.7.0" # TODO: this dependency will be removed.
29
30
 
30
31
  spec.required_ruby_version = ">= 2.2.0"
31
32
  end
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: trailblazer-macro
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.1
4
+ version: 2.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick Sutterer
8
8
  - Marc Tich
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2019-12-19 00:00:00.000000000 Z
12
+ date: 2021-03-11 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -95,20 +95,40 @@ dependencies:
95
95
  - - ">="
96
96
  - !ruby/object:Gem::Version
97
97
  version: '0'
98
+ - !ruby/object:Gem::Dependency
99
+ name: trailblazer-activity-dsl-linear
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: 0.4.0
105
+ - - "<"
106
+ - !ruby/object:Gem::Version
107
+ version: 0.5.0
108
+ type: :runtime
109
+ prerelease: false
110
+ version_requirements: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ version: 0.4.0
115
+ - - "<"
116
+ - !ruby/object:Gem::Version
117
+ version: 0.5.0
98
118
  - !ruby/object:Gem::Dependency
99
119
  name: trailblazer-operation
100
120
  requirement: !ruby/object:Gem::Requirement
101
121
  requirements:
102
122
  - - ">="
103
123
  - !ruby/object:Gem::Version
104
- version: 0.6.0
124
+ version: 0.7.0
105
125
  type: :runtime
106
126
  prerelease: false
107
127
  version_requirements: !ruby/object:Gem::Requirement
108
128
  requirements:
109
129
  - - ">="
110
130
  - !ruby/object:Gem::Version
111
- version: 0.6.0
131
+ version: 0.7.0
112
132
  description: Macros for Trailblazer's operation
113
133
  email:
114
134
  - apotonick@gmail.com
@@ -117,10 +137,10 @@ executables: []
117
137
  extensions: []
118
138
  extra_rdoc_files: []
119
139
  files:
140
+ - ".github/workflows/ci.yml"
120
141
  - ".gitignore"
121
142
  - ".rubocop.yml"
122
143
  - ".rubocop_todo.yml"
123
- - ".travis.yml"
124
144
  - CHANGES.md
125
145
  - Gemfile
126
146
  - LICENSE
@@ -145,6 +165,7 @@ files:
145
165
  - test/docs/wrap_test.rb
146
166
  - test/operation/integration_test.rb
147
167
  - test/operation/model_test.rb
168
+ - test/operation/nested_test.rb
148
169
  - test/operation/pundit_test.rb
149
170
  - test/test_helper.rb
150
171
  - trailblazer-macro.gemspec
@@ -152,7 +173,7 @@ homepage: http://trailblazer.to
152
173
  licenses:
153
174
  - LGPL-3.0
154
175
  metadata: {}
155
- post_install_message:
176
+ post_install_message:
156
177
  rdoc_options: []
157
178
  require_paths:
158
179
  - lib
@@ -167,8 +188,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
167
188
  - !ruby/object:Gem::Version
168
189
  version: '0'
169
190
  requirements: []
170
- rubygems_version: 3.0.3
171
- signing_key:
191
+ rubygems_version: 3.2.3
192
+ signing_key:
172
193
  specification_version: 4
173
194
  summary: 'Macros for Trailblazer''s operation: Policy, Wrap, Rescue and more.'
174
195
  test_files:
@@ -181,5 +202,6 @@ test_files:
181
202
  - test/docs/wrap_test.rb
182
203
  - test/operation/integration_test.rb
183
204
  - test/operation/model_test.rb
205
+ - test/operation/nested_test.rb
184
206
  - test/operation/pundit_test.rb
185
207
  - test/test_helper.rb
data/.travis.yml DELETED
@@ -1,9 +0,0 @@
1
- language: ruby
2
- rvm:
3
- - 2.6.5
4
- - 2.5.5
5
- - 2.4.4
6
- - 2.3.7
7
- - 2.2
8
-
9
- cache: bundler