rustrails_record/
callbacks.rs1use rustrails_model::callbacks::ModelEvent;
2use rustrails_support::callbacks::{Callback, CallbackChain, CallbackResult};
3
4use crate::Record;
5
6pub trait RecordCallbacks: Record {
8 fn before_save_callbacks() -> &'static [fn(&mut Self) -> CallbackResult] {
10 &[]
11 }
12
13 fn after_save_callbacks() -> &'static [fn(&mut Self) -> CallbackResult] {
15 &[]
16 }
17
18 fn before_create_callbacks() -> &'static [fn(&mut Self) -> CallbackResult] {
20 &[]
21 }
22
23 fn after_create_callbacks() -> &'static [fn(&mut Self) -> CallbackResult] {
25 &[]
26 }
27
28 fn before_update_callbacks() -> &'static [fn(&mut Self) -> CallbackResult] {
30 &[]
31 }
32
33 fn after_update_callbacks() -> &'static [fn(&mut Self) -> CallbackResult] {
35 &[]
36 }
37
38 fn before_destroy_callbacks() -> &'static [fn(&mut Self) -> CallbackResult] {
40 &[]
41 }
42
43 fn after_destroy_callbacks() -> &'static [fn(&mut Self) -> CallbackResult] {
45 &[]
46 }
47
48 #[must_use]
50 fn callbacks_for(event: ModelEvent) -> &'static [fn(&mut Self) -> CallbackResult] {
51 match event {
52 ModelEvent::BeforeValidation | ModelEvent::AfterValidation => &[],
53 ModelEvent::BeforeSave => Self::before_save_callbacks(),
54 ModelEvent::AfterSave => Self::after_save_callbacks(),
55 ModelEvent::BeforeCreate => Self::before_create_callbacks(),
56 ModelEvent::AfterCreate => Self::after_create_callbacks(),
57 ModelEvent::BeforeUpdate => Self::before_update_callbacks(),
58 ModelEvent::AfterUpdate => Self::after_update_callbacks(),
59 ModelEvent::BeforeDestroy => Self::before_destroy_callbacks(),
60 ModelEvent::AfterDestroy => Self::after_destroy_callbacks(),
61 }
62 }
63
64 #[must_use]
66 fn callback_chain(event: ModelEvent) -> CallbackChain<Self> {
67 let mut chain = CallbackChain::new(event.as_str());
68 chain.set_run_after_on_halt(false);
69
70 for (index, callback) in Self::callbacks_for(event).iter().copied().enumerate() {
71 let name = format!("{}_{}", event.as_str(), index);
72 if is_after_event(event) {
73 chain.add(Callback::after(name, callback));
74 } else {
75 chain.add(Callback::before(name, callback));
76 }
77 }
78
79 chain
80 }
81
82 fn run_callbacks(&mut self, event: ModelEvent) -> CallbackResult {
84 Self::callback_chain(event).run(self, event.as_str())
85 }
86}
87
88fn is_after_event(event: ModelEvent) -> bool {
89 matches!(
90 event,
91 ModelEvent::AfterValidation
92 | ModelEvent::AfterSave
93 | ModelEvent::AfterCreate
94 | ModelEvent::AfterUpdate
95 | ModelEvent::AfterDestroy
96 )
97}
98
99#[cfg(test)]
100mod tests {
101 use rustrails_model::callbacks::ModelEvent;
102 use rustrails_support::callbacks::CallbackResult;
103
104 use super::RecordCallbacks;
105 use crate::base::test_support::TestUser;
106
107 fn before_save(user: &mut TestUser) -> CallbackResult {
108 user.name.push_str("-before-save");
109 CallbackResult::Continue
110 }
111
112 fn after_save_first(user: &mut TestUser) -> CallbackResult {
113 user.email.push('1');
114 CallbackResult::Continue
115 }
116
117 fn after_save_second(user: &mut TestUser) -> CallbackResult {
118 user.email.push('2');
119 CallbackResult::Continue
120 }
121
122 fn before_update_halt(user: &mut TestUser) -> CallbackResult {
123 user.name.push_str("-halted");
124 CallbackResult::Halt
125 }
126
127 fn before_update_never_runs(user: &mut TestUser) -> CallbackResult {
128 user.name.push_str("-second");
129 CallbackResult::Continue
130 }
131
132 fn after_create(user: &mut TestUser) -> CallbackResult {
133 user.email.push_str(".created");
134 CallbackResult::Continue
135 }
136
137 fn after_destroy(user: &mut TestUser) -> CallbackResult {
138 user.email.push_str(".destroyed");
139 CallbackResult::Continue
140 }
141
142 impl RecordCallbacks for TestUser {
143 fn before_save_callbacks() -> &'static [fn(&mut Self) -> CallbackResult] {
144 &[before_save]
145 }
146
147 fn after_save_callbacks() -> &'static [fn(&mut Self) -> CallbackResult] {
148 &[after_save_first, after_save_second]
149 }
150
151 fn before_update_callbacks() -> &'static [fn(&mut Self) -> CallbackResult] {
152 &[before_update_halt, before_update_never_runs]
153 }
154
155 fn after_create_callbacks() -> &'static [fn(&mut Self) -> CallbackResult] {
156 &[after_create]
157 }
158
159 fn after_destroy_callbacks() -> &'static [fn(&mut Self) -> CallbackResult] {
160 &[after_destroy]
161 }
162 }
163
164 #[test]
165 fn before_save_callbacks_fire_for_before_save_event() {
166 let mut user = TestUser::default();
167
168 let result = user.run_callbacks(ModelEvent::BeforeSave);
169
170 assert_eq!(result, CallbackResult::Continue);
171 assert_eq!(user.name, "-before-save");
172 }
173
174 #[test]
175 fn after_callbacks_run_in_reverse_order() {
176 let mut user = TestUser::default();
177
178 let result = user.run_callbacks(ModelEvent::AfterSave);
179
180 assert_eq!(result, CallbackResult::Continue);
181 assert_eq!(user.email, "12");
182 }
183
184 #[test]
185 fn create_and_destroy_events_use_their_specific_callback_lists() {
186 let mut user = TestUser::default();
187
188 assert_eq!(
189 user.run_callbacks(ModelEvent::AfterCreate),
190 CallbackResult::Continue
191 );
192 assert_eq!(
193 user.run_callbacks(ModelEvent::AfterDestroy),
194 CallbackResult::Continue
195 );
196 assert_eq!(user.email, ".created.destroyed");
197 }
198
199 #[test]
200 fn halting_before_update_stops_later_callbacks() {
201 let mut user = TestUser::default();
202
203 let result = user.run_callbacks(ModelEvent::BeforeUpdate);
204
205 assert_eq!(result, CallbackResult::Halt);
206 assert_eq!(user.name, "-halted");
207 }
208
209 #[test]
210 fn validation_events_default_to_no_callbacks() {
211 let mut user = TestUser::default();
212
213 let result = user.run_callbacks(ModelEvent::BeforeValidation);
214
215 assert_eq!(result, CallbackResult::Continue);
216 assert!(user.name.is_empty());
217 assert!(user.email.is_empty());
218 }
219
220 #[test]
221 fn after_validation_defaults_to_no_callbacks() {
222 let mut user = TestUser::default();
223
224 let result = user.run_callbacks(ModelEvent::AfterValidation);
225
226 assert_eq!(result, CallbackResult::Continue);
227 assert!(user.name.is_empty());
228 assert!(user.email.is_empty());
229 }
230
231 #[test]
232 fn before_create_defaults_to_no_callbacks() {
233 let mut user = TestUser::default();
234
235 let result = user.run_callbacks(ModelEvent::BeforeCreate);
236
237 assert_eq!(result, CallbackResult::Continue);
238 assert!(user.name.is_empty());
239 assert!(user.email.is_empty());
240 }
241
242 #[test]
243 fn after_update_defaults_to_no_callbacks() {
244 let mut user = TestUser::default();
245
246 let result = user.run_callbacks(ModelEvent::AfterUpdate);
247
248 assert_eq!(result, CallbackResult::Continue);
249 assert!(user.name.is_empty());
250 assert!(user.email.is_empty());
251 }
252
253 #[test]
254 fn before_destroy_defaults_to_no_callbacks() {
255 let mut user = TestUser::default();
256
257 let result = user.run_callbacks(ModelEvent::BeforeDestroy);
258
259 assert_eq!(result, CallbackResult::Continue);
260 assert!(user.name.is_empty());
261 assert!(user.email.is_empty());
262 }
263
264 #[test]
265 fn callbacks_for_before_validation_is_empty() {
266 assert!(
267 <TestUser as RecordCallbacks>::callbacks_for(ModelEvent::BeforeValidation).is_empty()
268 );
269 }
270
271 #[test]
272 fn callbacks_for_after_validation_is_empty() {
273 assert!(
274 <TestUser as RecordCallbacks>::callbacks_for(ModelEvent::AfterValidation).is_empty()
275 );
276 }
277
278 #[test]
279 fn callbacks_for_before_save_returns_registered_callback() {
280 assert_eq!(
281 <TestUser as RecordCallbacks>::callbacks_for(ModelEvent::BeforeSave).len(),
282 1
283 );
284 }
285
286 #[test]
287 fn callbacks_for_after_save_returns_registered_callbacks() {
288 assert_eq!(
289 <TestUser as RecordCallbacks>::callbacks_for(ModelEvent::AfterSave).len(),
290 2
291 );
292 }
293
294 #[test]
295 fn callbacks_for_after_create_returns_registered_callback() {
296 assert_eq!(
297 <TestUser as RecordCallbacks>::callbacks_for(ModelEvent::AfterCreate).len(),
298 1
299 );
300 }
301
302 #[test]
303 fn callbacks_for_after_destroy_returns_registered_callback() {
304 assert_eq!(
305 <TestUser as RecordCallbacks>::callbacks_for(ModelEvent::AfterDestroy).len(),
306 1
307 );
308 }
309
310 #[test]
311 fn callback_chain_for_before_save_exposes_generated_before_metadata() {
312 let chain = <TestUser as RecordCallbacks>::callback_chain(ModelEvent::BeforeSave);
313
314 assert_eq!(chain.name(), "before_save");
315 assert_eq!(chain.len(), 1);
316
317 let debug = format!("{chain:?}");
318 assert!(debug.contains("before_save_0"));
319 assert!(debug.contains("kind: Before"));
320 assert!(debug.contains("run_after_on_halt: false"));
321 }
322
323 #[test]
324 fn callback_chain_for_after_save_exposes_generated_after_metadata() {
325 let chain = <TestUser as RecordCallbacks>::callback_chain(ModelEvent::AfterSave);
326
327 assert_eq!(chain.name(), "after_save");
328 assert_eq!(chain.len(), 2);
329
330 let debug = format!("{chain:?}");
331 assert!(debug.contains("after_save_0"));
332 assert!(debug.contains("after_save_1"));
333 assert!(debug.contains("kind: After"));
334 }
335
336 #[test]
337 fn callback_chain_for_empty_event_is_named_and_empty() {
338 let chain = <TestUser as RecordCallbacks>::callback_chain(ModelEvent::BeforeCreate);
339
340 assert_eq!(chain.name(), "before_create");
341 assert!(chain.is_empty());
342 assert_eq!(chain.len(), 0);
343 }
344}