rustrails-record 0.1.2

ORM layer (ActiveRecord equivalent)
Documentation
/// Timestamp behavior for records with `created_at` and `updated_at` fields.
pub trait Timestamps {
    /// Returns the creation timestamp when present.
    fn created_at(&self) -> Option<chrono::DateTime<chrono::Utc>>;

    /// Returns the last update timestamp when present.
    fn updated_at(&self) -> Option<chrono::DateTime<chrono::Utc>>;

    /// Updates the `updated_at` timestamp to the current time.
    fn touch(&mut self);

    /// Sets the `created_at` timestamp when creating a record.
    fn set_created_at(&mut self);
}

#[cfg(test)]
mod tests {
    use chrono::{Duration, Utc};

    use super::Timestamps;

    #[derive(Debug, Default)]
    struct TimestampedRecord {
        created_at: Option<chrono::DateTime<Utc>>,
        updated_at: Option<chrono::DateTime<Utc>>,
    }

    impl Timestamps for TimestampedRecord {
        fn created_at(&self) -> Option<chrono::DateTime<Utc>> {
            self.created_at
        }

        fn updated_at(&self) -> Option<chrono::DateTime<Utc>> {
            self.updated_at
        }

        fn touch(&mut self) {
            self.updated_at = Some(Utc::now());
        }

        fn set_created_at(&mut self) {
            self.created_at = Some(Utc::now());
        }
    }

    #[tokio::test]
    async fn touch_updates_timestamp() {
        let mut record = TimestampedRecord {
            updated_at: Some(Utc::now() - Duration::minutes(5)),
            ..Default::default()
        };
        let before = record.updated_at().expect("updated_at should be set");

        record.touch();

        assert!(record.updated_at().expect("updated_at should be set") >= before);
    }

    #[tokio::test]
    async fn set_created_at_sets_timestamp() {
        let mut record = TimestampedRecord::default();

        record.set_created_at();

        assert!(record.created_at().is_some());
    }

    #[tokio::test]
    async fn created_and_updated_timestamps_are_independent() {
        let mut record = TimestampedRecord::default();

        record.set_created_at();
        let created_at = record.created_at().expect("created_at should be set");
        record.touch();

        assert_eq!(
            record.created_at().expect("created_at should remain set"),
            created_at
        );
        assert!(record.updated_at().is_some());
    }

    #[tokio::test]
    async fn default_state_has_no_timestamps() {
        let record = TimestampedRecord::default();

        assert!(record.created_at().is_none());
        assert!(record.updated_at().is_none());
    }

    #[tokio::test]
    async fn touch_does_not_set_created_at_when_absent() {
        let mut record = TimestampedRecord::default();

        record.touch();

        assert!(record.created_at().is_none());
        assert!(record.updated_at().is_some());
    }

    #[tokio::test]
    async fn set_created_at_does_not_set_updated_at_when_absent() {
        let mut record = TimestampedRecord::default();

        record.set_created_at();

        assert!(record.created_at().is_some());
        assert!(record.updated_at().is_none());
    }

    #[tokio::test]
    async fn repeated_touch_updates_only_updated_at() {
        let created_at = Utc::now() - Duration::days(1);
        let mut record = TimestampedRecord {
            created_at: Some(created_at),
            updated_at: Some(Utc::now() - Duration::minutes(5)),
        };

        record.touch();
        let first_update = record.updated_at().expect("updated_at should be set");
        record.touch();
        let second_update = record.updated_at().expect("updated_at should stay set");

        assert_eq!(record.created_at(), Some(created_at));
        assert!(second_update >= first_update);
    }

    #[tokio::test]
    async fn repeated_set_created_at_updates_only_created_at() {
        let updated_at = Utc::now() - Duration::hours(1);
        let mut record = TimestampedRecord {
            created_at: Some(Utc::now() - Duration::days(1)),
            updated_at: Some(updated_at),
        };

        record.set_created_at();
        let first_created_at = record.created_at().expect("created_at should be set");
        record.set_created_at();
        let second_created_at = record.created_at().expect("created_at should stay set");

        assert_eq!(record.updated_at(), Some(updated_at));
        assert!(second_created_at >= first_created_at);
    }
}