trailblazer-macro 2.1.1 → 2.1.6

Sign up to get free protection for your applications and to get access to all the features.
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