rustrails-record 0.1.2

ORM layer (ActiveRecord equivalent)
Documentation
use rustrails_support::inflector::{foreign_key, tableize};

use super::{AssociationMeta, AssociationType};

/// Builder for [`AssociationType::BelongsTo`] metadata.
#[derive(Debug, Clone)]
pub struct BelongsToBuilder {
    name: String,
    foreign_key: Option<String>,
    primary_key: Option<String>,
    polymorphic: bool,
}

impl BelongsToBuilder {
    /// Creates a builder for the named association.
    #[must_use]
    pub fn new(name: &str) -> Self {
        Self {
            name: name.to_owned(),
            foreign_key: None,
            primary_key: None,
            polymorphic: false,
        }
    }

    /// Overrides the inferred foreign-key column name.
    #[must_use]
    pub fn foreign_key(mut self, fk: &str) -> Self {
        self.foreign_key = Some(fk.to_owned());
        self
    }

    /// Overrides the target primary-key column name.
    #[must_use]
    pub fn primary_key(mut self, key: &str) -> Self {
        self.primary_key = Some(key.to_owned());
        self
    }

    /// Marks the association as polymorphic.
    #[must_use]
    pub fn polymorphic(mut self) -> Self {
        self.polymorphic = true;
        self
    }

    /// Builds association metadata with ActiveRecord-style defaults.
    #[must_use]
    pub fn build(self) -> AssociationMeta {
        let name = self.name;
        let inferred_key = foreign_key(&name);

        AssociationMeta {
            target_table: tableize(&name),
            foreign_key: self.foreign_key.unwrap_or(inferred_key),
            primary_key: self.primary_key.unwrap_or_else(|| "id".to_owned()),
            dependent: None,
            through: None,
            polymorphic: self.polymorphic,
            association_type: AssociationType::BelongsTo,
            name,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::BelongsToBuilder;
    use crate::associations::AssociationType;

    #[test]
    fn build_defaults_foreign_key_from_association_name() {
        let meta = BelongsToBuilder::new("author").build();

        assert_eq!(meta.association_type, AssociationType::BelongsTo);
        assert_eq!(meta.target_table, "authors");
        assert_eq!(meta.foreign_key, "author_id");
        assert_eq!(meta.primary_key, "id");
        assert!(!meta.polymorphic);
    }

    #[test]
    fn build_supports_custom_keys() {
        let meta = BelongsToBuilder::new("account")
            .foreign_key("owner_uuid")
            .primary_key("uuid")
            .build();

        assert_eq!(meta.foreign_key, "owner_uuid");
        assert_eq!(meta.primary_key, "uuid");
    }

    #[test]
    fn build_marks_polymorphic_associations() {
        let meta = BelongsToBuilder::new("attachable").polymorphic().build();

        assert!(meta.polymorphic);
    }
    #[test]
    fn build_preserves_association_name() {
        let meta = BelongsToBuilder::new("author").build();

        assert_eq!(meta.name, "author");
    }

    #[test]
    fn foreign_key_override_keeps_default_primary_key() {
        let meta = BelongsToBuilder::new("author")
            .foreign_key("writer_id")
            .build();

        assert_eq!(meta.foreign_key, "writer_id");
        assert_eq!(meta.primary_key, "id");
    }

    #[test]
    fn primary_key_override_keeps_inferred_foreign_key() {
        let meta = BelongsToBuilder::new("author").primary_key("uuid").build();

        assert_eq!(meta.foreign_key, "author_id");
        assert_eq!(meta.primary_key, "uuid");
    }

    #[test]
    fn polymorphic_preserves_default_keys() {
        let meta = BelongsToBuilder::new("attachable").polymorphic().build();

        assert_eq!(meta.foreign_key, "attachable_id");
        assert_eq!(meta.primary_key, "id");
    }

    #[test]
    fn build_does_not_set_dependent_or_through_metadata() {
        let meta = BelongsToBuilder::new("author").build();

        assert!(meta.dependent.is_none());
        assert!(meta.through.is_none());
    }
}