Skip to content

Commit dcbbab0

Browse files
Implement If() macro
This commit adds an `If()` macro to conditionally execute steps in an operation. It's implementation is based on the implementation of the `Wrap()` macro to execute the nested sub-activity. Co-authored-by: Roland Schwarzer <schwarzer@webit.de>
1 parent e76d090 commit dcbbab0

File tree

3 files changed

+205
-1
lines changed

3 files changed

+205
-1
lines changed

lib/trailblazer/macro.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
require "trailblazer/macro/rescue"
1212
require "trailblazer/macro/wrap"
1313
require "trailblazer/macro/each"
14+
require "trailblazer/macro/if"
1415

1516
module Trailblazer
1617
module Macro
@@ -83,6 +84,6 @@ module Activity::DSL::Linear::Helper
8384
# Extending the {Linear::Helper} namespace is the canonical way to import
8485
# macros into Railway, FastTrack, Operation, etc.
8586
extend Forwardable
86-
def_delegators Trailblazer::Macro, :Model, :Nested, :Wrap, :Rescue, :Each
87+
def_delegators Trailblazer::Macro, :Model, :Nested, :Wrap, :Rescue, :Each, :If
8788
end # Helper
8889
end

lib/trailblazer/macro/if.rb

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
module Trailblazer
2+
module Macro
3+
def self.If(condition, name: :default, id: Macro.id_for(condition, macro: :If, hint: condition), &block)
4+
unless block_given?
5+
raise ArgumentError, "If() requires a block"
6+
end
7+
8+
option = Trailblazer::Option(condition)
9+
wrap = ->((ctx, flow_options), **circuit_args, &block) {
10+
ctx[:"result.condition.#{name}"] = result =
11+
option.call(ctx, keyword_arguments: ctx.to_hash, **circuit_args)
12+
13+
if result
14+
block.call
15+
else
16+
[Trailblazer::Activity::Right, [ctx, flow_options]]
17+
end
18+
}
19+
20+
Wrap(wrap, id: id, &block)
21+
end
22+
end
23+
end

test/docs/if_test.rb

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
require "test_helper"
2+
3+
class IfMacroTest < Minitest::Spec
4+
class Song
5+
module Activity
6+
class Upload < Trailblazer::Activity::FastTrack
7+
step :model
8+
step If(:condition) {
9+
step :update # this changes the database.
10+
step :transfer # this might even break!
11+
}
12+
step :notify
13+
fail :log_error
14+
#~meths
15+
include T.def_steps(:model, :condition, :update, :transfer, :notify, :log_error)
16+
#~meths end
17+
end
18+
end
19+
end
20+
21+
it do
22+
assert_invoke Song::Activity::Upload, condition: true, seq: "[:model, :condition, :update, :transfer, :notify]", expected_ctx_variables: { "result.condition.default": true }
23+
assert_invoke Song::Activity::Upload, condition: false, seq: "[:model, :condition, :notify]", expected_ctx_variables: { "result.condition.default": false }
24+
assert_invoke Song::Activity::Upload, condition: true, transfer: false, seq: "[:model, :condition, :update, :transfer, :log_error]", terminus: :failure, expected_ctx_variables: { "result.condition.default": true }
25+
end
26+
end
27+
28+
class IfWithCustomNameTest < Minitest::Spec
29+
class Song
30+
module Activity
31+
class Upload < Trailblazer::Activity::FastTrack
32+
step If(:condition, name: :custom_name) {
33+
step :update # this changes the database.
34+
}
35+
#~meths
36+
include T.def_steps(:condition, :update)
37+
#~meths end
38+
end
39+
end
40+
end
41+
42+
it do
43+
assert_invoke Song::Activity::Upload, condition: true, seq: "[:condition, :update]", expected_ctx_variables: { "result.condition.custom_name": true }
44+
assert_invoke Song::Activity::Upload, condition: false, seq: "[:condition]", expected_ctx_variables: { "result.condition.custom_name": false }
45+
end
46+
end
47+
48+
class IfWithProcTest < Minitest::Spec
49+
class Song
50+
module Activity
51+
class Upload < Trailblazer::Activity::FastTrack
52+
step If(->(_ctx, condition:, **) { condition }) {
53+
step :update # this changes the database.
54+
}
55+
#~meths
56+
include T.def_steps(:update)
57+
#~meths end
58+
end
59+
end
60+
end
61+
62+
it do
63+
assert_invoke Song::Activity::Upload, condition: true, seq: "[:update]", expected_ctx_variables: { "result.condition.default": true }
64+
assert_invoke Song::Activity::Upload, condition: false, seq: "[]", expected_ctx_variables: { "result.condition.default": false }
65+
end
66+
end
67+
68+
class IfWithCallableTest < Minitest::Spec
69+
class Song
70+
module Activity
71+
class Upload < Trailblazer::Activity::FastTrack
72+
class Callable
73+
def self.call(_ctx, condition:, **)
74+
condition
75+
end
76+
end
77+
78+
step If(Callable) {
79+
step :update # this changes the database.
80+
}
81+
#~meths
82+
include T.def_steps(:update)
83+
#~meths end
84+
end
85+
end
86+
end
87+
88+
it do
89+
assert_invoke Song::Activity::Upload, condition: true, seq: "[:update]", expected_ctx_variables: { "result.condition.default": true }
90+
assert_invoke Song::Activity::Upload, condition: false, seq: "[]", expected_ctx_variables: { "result.condition.default": false }
91+
end
92+
end
93+
94+
class IfWithNestedIfTest < Minitest::Spec
95+
class Song
96+
module Activity
97+
class Upload < Trailblazer::Activity::FastTrack
98+
step If(:condition) {
99+
step :update # this changes the database.
100+
step If(:nested_condition, name: :nested) {
101+
step :notify
102+
}
103+
step :finalize
104+
}
105+
#~meths
106+
include T.def_steps(:condition, :update, :nested_condition, :notify, :finalize)
107+
#~meths end
108+
end
109+
end
110+
end
111+
112+
it do
113+
assert_invoke Song::Activity::Upload, condition: true, nested_condition: true, seq: "[:condition, :update, :nested_condition, :notify, :finalize]", expected_ctx_variables: { "result.condition.default": true, "result.condition.nested": true }
114+
assert_invoke Song::Activity::Upload, condition: false, seq: "[:condition]", expected_ctx_variables: { "result.condition.default": false }
115+
assert_invoke Song::Activity::Upload, condition: true, nested_condition: false, seq: "[:condition, :update, :nested_condition, :finalize]", expected_ctx_variables: { "result.condition.default": true, "result.condition.nested": false }
116+
end
117+
end
118+
119+
class IfTracingTest < Minitest::Spec
120+
class Song
121+
module Activity
122+
class Upload < Trailblazer::Activity::FastTrack
123+
class DecideWhatToDo
124+
def self.call(*); end
125+
end
126+
127+
def self.my_condition_handler(*); end
128+
129+
step If(:condition) {}
130+
step If(DecideWhatToDo) {}
131+
step If(method(:my_condition_handler)) {}
132+
step If(->(*) {}, id: "proc.my_condition_handler") {}
133+
134+
#~meths
135+
include T.def_steps(:condition)
136+
#~meths end
137+
end
138+
end
139+
end
140+
141+
it do
142+
[
143+
"If/condition",
144+
"If/IfTracingTest::Song::Activity::Upload::DecideWhatToDo",
145+
"If/method(:my_condition_handler)",
146+
"proc.my_condition_handler"
147+
].each do |id|
148+
assert_equal Trailblazer::Developer::Introspect.find_path(Song::Activity::Upload, [id])[0].id, id
149+
end
150+
151+
assert_equal trace(Song::Activity::Upload, { seq: [], condition: false })[0], <<~SEQ.chomp
152+
TOP
153+
|-- Start.default
154+
|-- If/condition
155+
|-- If/IfTracingTest::Song::Activity::Upload::DecideWhatToDo
156+
|-- If/method(:my_condition_handler)
157+
|-- proc.my_condition_handler
158+
`-- End.success
159+
SEQ
160+
end
161+
end
162+
163+
class IfWithoutBlockTest < Minitest::Spec
164+
it do
165+
exception = assert_raises ArgumentError do
166+
class Song
167+
module Activity
168+
class Upload < Trailblazer::Activity::FastTrack
169+
step If(:condition)
170+
171+
#~meths
172+
include T.def_steps(:condition)
173+
#~meths end
174+
end
175+
end
176+
end
177+
end
178+
assert_equal "If() requires a block", exception.message
179+
end
180+
end

0 commit comments

Comments
 (0)