use rustrails_model::callbacks::ModelEvent;
use rustrails_support::callbacks::{Callback, CallbackChain, CallbackResult};
use crate::Record;
pub trait RecordCallbacks: Record {
fn before_save_callbacks() -> &'static [fn(&mut Self) -> CallbackResult] {
&[]
}
fn after_save_callbacks() -> &'static [fn(&mut Self) -> CallbackResult] {
&[]
}
fn before_create_callbacks() -> &'static [fn(&mut Self) -> CallbackResult] {
&[]
}
fn after_create_callbacks() -> &'static [fn(&mut Self) -> CallbackResult] {
&[]
}
fn before_update_callbacks() -> &'static [fn(&mut Self) -> CallbackResult] {
&[]
}
fn after_update_callbacks() -> &'static [fn(&mut Self) -> CallbackResult] {
&[]
}
fn before_destroy_callbacks() -> &'static [fn(&mut Self) -> CallbackResult] {
&[]
}
fn after_destroy_callbacks() -> &'static [fn(&mut Self) -> CallbackResult] {
&[]
}
#[must_use]
fn callbacks_for(event: ModelEvent) -> &'static [fn(&mut Self) -> CallbackResult] {
match event {
ModelEvent::BeforeValidation | ModelEvent::AfterValidation => &[],
ModelEvent::BeforeSave => Self::before_save_callbacks(),
ModelEvent::AfterSave => Self::after_save_callbacks(),
ModelEvent::BeforeCreate => Self::before_create_callbacks(),
ModelEvent::AfterCreate => Self::after_create_callbacks(),
ModelEvent::BeforeUpdate => Self::before_update_callbacks(),
ModelEvent::AfterUpdate => Self::after_update_callbacks(),
ModelEvent::BeforeDestroy => Self::before_destroy_callbacks(),
ModelEvent::AfterDestroy => Self::after_destroy_callbacks(),
}
}
#[must_use]
fn callback_chain(event: ModelEvent) -> CallbackChain<Self> {
let mut chain = CallbackChain::new(event.as_str());
chain.set_run_after_on_halt(false);
for (index, callback) in Self::callbacks_for(event).iter().copied().enumerate() {
let name = format!("{}_{}", event.as_str(), index);
if is_after_event(event) {
chain.add(Callback::after(name, callback));
} else {
chain.add(Callback::before(name, callback));
}
}
chain
}
fn run_callbacks(&mut self, event: ModelEvent) -> CallbackResult {
Self::callback_chain(event).run(self, event.as_str())
}
}
fn is_after_event(event: ModelEvent) -> bool {
matches!(
event,
ModelEvent::AfterValidation
| ModelEvent::AfterSave
| ModelEvent::AfterCreate
| ModelEvent::AfterUpdate
| ModelEvent::AfterDestroy
)
}
#[cfg(test)]
mod tests {
use rustrails_model::callbacks::ModelEvent;
use rustrails_support::callbacks::CallbackResult;
use super::RecordCallbacks;
use crate::base::test_support::TestUser;
fn before_save(user: &mut TestUser) -> CallbackResult {
user.name.push_str("-before-save");
CallbackResult::Continue
}
fn after_save_first(user: &mut TestUser) -> CallbackResult {
user.email.push('1');
CallbackResult::Continue
}
fn after_save_second(user: &mut TestUser) -> CallbackResult {
user.email.push('2');
CallbackResult::Continue
}
fn before_update_halt(user: &mut TestUser) -> CallbackResult {
user.name.push_str("-halted");
CallbackResult::Halt
}
fn before_update_never_runs(user: &mut TestUser) -> CallbackResult {
user.name.push_str("-second");
CallbackResult::Continue
}
fn after_create(user: &mut TestUser) -> CallbackResult {
user.email.push_str(".created");
CallbackResult::Continue
}
fn after_destroy(user: &mut TestUser) -> CallbackResult {
user.email.push_str(".destroyed");
CallbackResult::Continue
}
impl RecordCallbacks for TestUser {
fn before_save_callbacks() -> &'static [fn(&mut Self) -> CallbackResult] {
&[before_save]
}
fn after_save_callbacks() -> &'static [fn(&mut Self) -> CallbackResult] {
&[after_save_first, after_save_second]
}
fn before_update_callbacks() -> &'static [fn(&mut Self) -> CallbackResult] {
&[before_update_halt, before_update_never_runs]
}
fn after_create_callbacks() -> &'static [fn(&mut Self) -> CallbackResult] {
&[after_create]
}
fn after_destroy_callbacks() -> &'static [fn(&mut Self) -> CallbackResult] {
&[after_destroy]
}
}
#[test]
fn before_save_callbacks_fire_for_before_save_event() {
let mut user = TestUser::default();
let result = user.run_callbacks(ModelEvent::BeforeSave);
assert_eq!(result, CallbackResult::Continue);
assert_eq!(user.name, "-before-save");
}
#[test]
fn after_callbacks_run_in_reverse_order() {
let mut user = TestUser::default();
let result = user.run_callbacks(ModelEvent::AfterSave);
assert_eq!(result, CallbackResult::Continue);
assert_eq!(user.email, "12");
}
#[test]
fn create_and_destroy_events_use_their_specific_callback_lists() {
let mut user = TestUser::default();
assert_eq!(
user.run_callbacks(ModelEvent::AfterCreate),
CallbackResult::Continue
);
assert_eq!(
user.run_callbacks(ModelEvent::AfterDestroy),
CallbackResult::Continue
);
assert_eq!(user.email, ".created.destroyed");
}
#[test]
fn halting_before_update_stops_later_callbacks() {
let mut user = TestUser::default();
let result = user.run_callbacks(ModelEvent::BeforeUpdate);
assert_eq!(result, CallbackResult::Halt);
assert_eq!(user.name, "-halted");
}
#[test]
fn validation_events_default_to_no_callbacks() {
let mut user = TestUser::default();
let result = user.run_callbacks(ModelEvent::BeforeValidation);
assert_eq!(result, CallbackResult::Continue);
assert!(user.name.is_empty());
assert!(user.email.is_empty());
}
#[test]
fn after_validation_defaults_to_no_callbacks() {
let mut user = TestUser::default();
let result = user.run_callbacks(ModelEvent::AfterValidation);
assert_eq!(result, CallbackResult::Continue);
assert!(user.name.is_empty());
assert!(user.email.is_empty());
}
#[test]
fn before_create_defaults_to_no_callbacks() {
let mut user = TestUser::default();
let result = user.run_callbacks(ModelEvent::BeforeCreate);
assert_eq!(result, CallbackResult::Continue);
assert!(user.name.is_empty());
assert!(user.email.is_empty());
}
#[test]
fn after_update_defaults_to_no_callbacks() {
let mut user = TestUser::default();
let result = user.run_callbacks(ModelEvent::AfterUpdate);
assert_eq!(result, CallbackResult::Continue);
assert!(user.name.is_empty());
assert!(user.email.is_empty());
}
#[test]
fn before_destroy_defaults_to_no_callbacks() {
let mut user = TestUser::default();
let result = user.run_callbacks(ModelEvent::BeforeDestroy);
assert_eq!(result, CallbackResult::Continue);
assert!(user.name.is_empty());
assert!(user.email.is_empty());
}
#[test]
fn callbacks_for_before_validation_is_empty() {
assert!(
<TestUser as RecordCallbacks>::callbacks_for(ModelEvent::BeforeValidation).is_empty()
);
}
#[test]
fn callbacks_for_after_validation_is_empty() {
assert!(
<TestUser as RecordCallbacks>::callbacks_for(ModelEvent::AfterValidation).is_empty()
);
}
#[test]
fn callbacks_for_before_save_returns_registered_callback() {
assert_eq!(
<TestUser as RecordCallbacks>::callbacks_for(ModelEvent::BeforeSave).len(),
1
);
}
#[test]
fn callbacks_for_after_save_returns_registered_callbacks() {
assert_eq!(
<TestUser as RecordCallbacks>::callbacks_for(ModelEvent::AfterSave).len(),
2
);
}
#[test]
fn callbacks_for_after_create_returns_registered_callback() {
assert_eq!(
<TestUser as RecordCallbacks>::callbacks_for(ModelEvent::AfterCreate).len(),
1
);
}
#[test]
fn callbacks_for_after_destroy_returns_registered_callback() {
assert_eq!(
<TestUser as RecordCallbacks>::callbacks_for(ModelEvent::AfterDestroy).len(),
1
);
}
#[test]
fn callback_chain_for_before_save_exposes_generated_before_metadata() {
let chain = <TestUser as RecordCallbacks>::callback_chain(ModelEvent::BeforeSave);
assert_eq!(chain.name(), "before_save");
assert_eq!(chain.len(), 1);
let debug = format!("{chain:?}");
assert!(debug.contains("before_save_0"));
assert!(debug.contains("kind: Before"));
assert!(debug.contains("run_after_on_halt: false"));
}
#[test]
fn callback_chain_for_after_save_exposes_generated_after_metadata() {
let chain = <TestUser as RecordCallbacks>::callback_chain(ModelEvent::AfterSave);
assert_eq!(chain.name(), "after_save");
assert_eq!(chain.len(), 2);
let debug = format!("{chain:?}");
assert!(debug.contains("after_save_0"));
assert!(debug.contains("after_save_1"));
assert!(debug.contains("kind: After"));
}
#[test]
fn callback_chain_for_empty_event_is_named_and_empty() {
let chain = <TestUser as RecordCallbacks>::callback_chain(ModelEvent::BeforeCreate);
assert_eq!(chain.name(), "before_create");
assert!(chain.is_empty());
assert_eq!(chain.len(), 0);
}
}