Skip to content

Commit be6acb2

Browse files
authored
1066/operation callbacks (#1108)
* feat: Add operation callbacks * refactor: 🔥 Remove debugging log * docs: Remove dev clause from spec
1 parent d26a521 commit be6acb2

2 files changed

Lines changed: 127 additions & 8 deletions

File tree

spec/avram/operations/operation_callbacks_spec.cr

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,64 @@ private class OperationWithCallbacks < Avram::Operation
3737
end
3838
end
3939

40+
class OperationWithIf < Avram::Operation
41+
include TestableOperation
42+
attribute number : Int32 = 1
43+
44+
before_run :update_number, if: :should_run_callback?
45+
46+
def run
47+
number.value
48+
end
49+
50+
private def update_number
51+
mark_callback("before_run_update_number")
52+
number.value = 10
53+
end
54+
55+
private def should_run_callback?
56+
true
57+
end
58+
end
59+
60+
class OperationWithUnless < Avram::Operation
61+
include TestableOperation
62+
attribute number : Int32 = 1
63+
64+
before_run :update_number, unless: :skip_callback?
65+
66+
def run
67+
number.value
68+
end
69+
70+
private def update_number
71+
mark_callback("before_run_update_number")
72+
number.value = 20
73+
end
74+
75+
private def skip_callback?
76+
true
77+
end
78+
end
79+
80+
class OperationWithBlockUnless < Avram::Operation
81+
include TestableOperation
82+
attribute number : Int32 = 1
83+
84+
before_run(unless: :skip_callback?) do
85+
mark_callback("before_run_block")
86+
number.value = 30
87+
end
88+
89+
def run
90+
number.value
91+
end
92+
93+
private def skip_callback?
94+
false
95+
end
96+
end
97+
4098
describe "Avram::Operation callbacks" do
4199
it "runs before_run and after_run callbacks" do
42100
OperationWithCallbacks.run do |operation, value|
@@ -49,4 +107,25 @@ describe "Avram::Operation callbacks" do
49107
operation.callbacks_that_ran.should contain "after_run_in_a_block with 4"
50108
end
51109
end
110+
111+
it "runs before_run with if condition" do
112+
OperationWithIf.run do |operation, value|
113+
operation.callbacks_that_ran.should contain "before_run_update_number"
114+
value.should eq 10
115+
end
116+
end
117+
118+
it "does not run before_run with unless condition" do
119+
OperationWithUnless.run do |operation, value|
120+
operation.callbacks_that_ran.should_not contain "before_run_update_number"
121+
value.should eq 1
122+
end
123+
end
124+
125+
it "runs before_run block with unless condition" do
126+
OperationWithBlockUnless.run do |operation, value|
127+
operation.callbacks_that_ran.should contain "before_run_block"
128+
value.should eq 30
129+
end
130+
end
52131
end

src/avram/callbacks/callbacks.cr

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,75 @@
11
module Avram::Callbacks
22
# Run the given method before `run` is called on an `Operation`.
33
#
4-
# ```
5-
# before_run :validate_inputs
4+
# Examples:
5+
#
6+
# before_run :validate_inputs
7+
#
8+
# before_run :validate_inputs, if: :should_validate?
9+
# before_run :validate_inputs, unless: :skip_validation?
610
#
711
# private def validate_inputs
812
# validate_required data
913
# end
10-
# ```
11-
macro before_run(method_name)
12-
before_run do
14+
macro before_run(method_name, if _if = nil, unless _unless = nil)
15+
{% unless _if.is_a?(SymbolLiteral) || _if.is_a?(NilLiteral) %}
16+
conditional_error_for_inline_callbacks(:before_run, {{ method_name }}, :if)
17+
{% end %}
18+
{% unless _unless.is_a?(SymbolLiteral) || _unless.is_a?(NilLiteral) %}
19+
conditional_error_for_inline_callbacks(:before_run, {{ method_name }}, :unless)
20+
{% end %}
21+
before_run(if: {{ _if }}, unless: {{ _unless }}) do
1322
{{ method_name.id }}
1423
end
1524
end
1625

1726
# Run the given block before `run` is called on an `Operation`.
1827
#
28+
# This runs before `run` is invoked on the operation.
29+
# You can set defaults, validate, or perform any other setup necessary before running the operation.
30+
#
31+
# Optionally you can pass an `if` or `unless` argument which allows you to
32+
# run this conditionally. The symbol should reference a method you've defined
33+
# that returns a truthy/falsey value.
34+
#
1935
# ```
20-
# before_run do
36+
# before_run(unless: :skip_callback?) do
2137
# validate_required data
2238
# end
39+
#
40+
# private def skip_callback?
41+
# false
42+
# end
2343
# ```
24-
macro before_run
44+
macro before_run(if _if = nil, unless _unless = nil)
45+
{% if _if != nil && _unless != nil %}
46+
{% raise "Your before_run callbacks should only specify `if` or `unless`, but not both." %}
47+
{% end %}
48+
{% unless _if.is_a?(SymbolLiteral) || _if.is_a?(NilLiteral) %}
49+
conditional_error_for_block_callbacks(:before_run, :if)
50+
{% end %}
51+
{% unless _unless.is_a?(SymbolLiteral) || _unless.is_a?(NilLiteral) %}
52+
conditional_error_for_block_callbacks(:before_run, :unless)
53+
{% end %}
54+
2555
def before_run : Nil
2656
{% if @type.methods.map(&.name).includes?(:before_run.id) %}
2757
previous_def
2858
{% else %}
2959
super
3060
{% end %}
3161

32-
{{ yield }}
62+
{% if _if %}
63+
if {{ _if.id }}
64+
{{ yield }}
65+
end
66+
{% elsif _unless %}
67+
unless {{ _unless.id }}
68+
{{ yield }}
69+
end
70+
{% else %}
71+
{{ yield }}
72+
{% end %}
3373
end
3474
end
3575

0 commit comments

Comments
 (0)