Skip to main content

rustrails_record/
callbacks.rs

1use rustrails_model::callbacks::ModelEvent;
2use rustrails_support::callbacks::{Callback, CallbackChain, CallbackResult};
3
4use crate::Record;
5
6/// Trait for records that expose persistence-related callback hooks.
7pub trait RecordCallbacks: Record {
8    /// Returns callbacks that run before save operations.
9    fn before_save_callbacks() -> &'static [fn(&mut Self) -> CallbackResult] {
10        &[]
11    }
12
13    /// Returns callbacks that run after save operations.
14    fn after_save_callbacks() -> &'static [fn(&mut Self) -> CallbackResult] {
15        &[]
16    }
17
18    /// Returns callbacks that run before create operations.
19    fn before_create_callbacks() -> &'static [fn(&mut Self) -> CallbackResult] {
20        &[]
21    }
22
23    /// Returns callbacks that run after create operations.
24    fn after_create_callbacks() -> &'static [fn(&mut Self) -> CallbackResult] {
25        &[]
26    }
27
28    /// Returns callbacks that run before update operations.
29    fn before_update_callbacks() -> &'static [fn(&mut Self) -> CallbackResult] {
30        &[]
31    }
32
33    /// Returns callbacks that run after update operations.
34    fn after_update_callbacks() -> &'static [fn(&mut Self) -> CallbackResult] {
35        &[]
36    }
37
38    /// Returns callbacks that run before destroy operations.
39    fn before_destroy_callbacks() -> &'static [fn(&mut Self) -> CallbackResult] {
40        &[]
41    }
42
43    /// Returns callbacks that run after destroy operations.
44    fn after_destroy_callbacks() -> &'static [fn(&mut Self) -> CallbackResult] {
45        &[]
46    }
47
48    /// Returns the raw callback list for the given lifecycle event.
49    #[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    /// Builds a callback chain for the requested lifecycle event.
65    #[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    /// Runs the callbacks registered for the requested lifecycle event.
83    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}