rustrails-record 0.1.2

ORM layer (ActiveRecord equivalent)
Documentation
use std::cell::Cell;

thread_local! {
    static NO_TOUCHING_DEPTH: Cell<usize> = const { Cell::new(0) };
}

/// Disables touch updates within the scope of a closure.
pub struct NoTouching;

impl NoTouching {
    /// Runs `f` with touch propagation disabled on the current thread.
    pub fn apply<F, R>(f: F) -> R
    where
        F: FnOnce() -> R,
    {
        struct Guard;

        impl Drop for Guard {
            fn drop(&mut self) {
                NO_TOUCHING_DEPTH.with(|depth| depth.set(depth.get().saturating_sub(1)));
            }
        }

        NO_TOUCHING_DEPTH.with(|depth| depth.set(depth.get() + 1));
        let _guard = Guard;
        f()
    }

    /// Returns `true` when touches are disabled on the current thread.
    #[must_use]
    pub fn is_active() -> bool {
        NO_TOUCHING_DEPTH.with(|depth| depth.get() > 0)
    }
}

pub(crate) fn is_disabled() -> bool {
    NoTouching::is_active()
}

#[cfg(test)]
mod tests {
    use std::thread;

    use super::{NoTouching, is_disabled};

    #[test]
    fn apply_enables_no_touching_for_block() {
        let active = NoTouching::apply(NoTouching::is_active);
        assert!(active);
        assert!(!NoTouching::is_active());
    }

    #[test]
    fn apply_restores_previous_state_after_return() {
        NoTouching::apply(|| {
            assert!(NoTouching::is_active());
        });

        assert!(!NoTouching::is_active());
    }

    #[test]
    fn apply_restores_state_after_panic() {
        let result = std::panic::catch_unwind(|| {
            NoTouching::apply(|| panic!("boom"));
        });

        assert!(result.is_err());
        assert!(!NoTouching::is_active());
    }

    #[test]
    fn nested_apply_keeps_no_touching_active_until_outer_scope_exits() {
        NoTouching::apply(|| {
            assert!(NoTouching::is_active());
            NoTouching::apply(|| assert!(NoTouching::is_active()));
            assert!(NoTouching::is_active());
        });

        assert!(!NoTouching::is_active());
    }

    #[test]
    fn state_is_thread_local() {
        let handle = thread::spawn(NoTouching::is_active);
        NoTouching::apply(|| {
            assert!(NoTouching::is_active());
            assert!(!handle.join().expect("thread should complete"));
        });
    }

    #[test]
    fn crate_helper_tracks_public_state() {
        assert_eq!(is_disabled(), NoTouching::is_active());
    }
}