use rustrails_support::inflector::{foreign_key, singularize, tableize};
use super::{AssociationMeta, AssociationType, DependentAction};
#[derive(Debug, Clone)]
pub struct HasManyBuilder {
name: String,
foreign_key: Option<String>,
primary_key: Option<String>,
dependent: Option<DependentAction>,
through: Option<String>,
}
impl HasManyBuilder {
#[must_use]
pub fn new(name: &str) -> Self {
Self {
name: name.to_owned(),
foreign_key: None,
primary_key: None,
dependent: None,
through: None,
}
}
#[must_use]
pub fn foreign_key(mut self, fk: &str) -> Self {
self.foreign_key = Some(fk.to_owned());
self
}
#[must_use]
pub fn primary_key(mut self, key: &str) -> Self {
self.primary_key = Some(key.to_owned());
self
}
#[must_use]
pub fn dependent(mut self, action: DependentAction) -> Self {
self.dependent = Some(action);
self
}
#[must_use]
pub fn through(mut self, junction: &str) -> Self {
self.through = Some(junction.to_owned());
self
}
#[must_use]
pub fn build(self) -> AssociationMeta {
let name = self.name;
let inferred_key = foreign_key(&singularize(&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: self.dependent,
through: self.through,
polymorphic: false,
association_type: AssociationType::HasMany,
name,
}
}
}
#[cfg(test)]
mod tests {
use super::HasManyBuilder;
use crate::associations::{AssociationType, DependentAction};
#[test]
fn build_uses_plural_target_table_and_default_keys() {
let meta = HasManyBuilder::new("comments").build();
assert_eq!(meta.name, "comments");
assert_eq!(meta.association_type, AssociationType::HasMany);
assert_eq!(meta.target_table, "comments");
assert_eq!(meta.foreign_key, "comment_id");
assert_eq!(meta.primary_key, "id");
assert_eq!(meta.through, None);
}
#[test]
fn build_applies_custom_keys_and_dependent_action() {
let meta = HasManyBuilder::new("children")
.foreign_key("parent_uuid")
.primary_key("uuid")
.dependent(DependentAction::Delete)
.build();
assert_eq!(meta.foreign_key, "parent_uuid");
assert_eq!(meta.primary_key, "uuid");
assert_eq!(meta.dependent, Some(DependentAction::Delete));
}
#[test]
fn build_tracks_through_association() {
let meta = HasManyBuilder::new("tags").through("taggings").build();
assert_eq!(meta.through.as_deref(), Some("taggings"));
}
#[test]
fn build_preserves_association_name() {
let meta = HasManyBuilder::new("comments").build();
assert_eq!(meta.name, "comments");
}
#[test]
fn dependent_defaults_to_none() {
let meta = HasManyBuilder::new("comments").build();
assert!(meta.dependent.is_none());
}
#[test]
fn foreign_key_override_keeps_default_primary_key() {
let meta = HasManyBuilder::new("comments")
.foreign_key("owner_id")
.build();
assert_eq!(meta.foreign_key, "owner_id");
assert_eq!(meta.primary_key, "id");
}
#[test]
fn primary_key_override_keeps_inferred_foreign_key() {
let meta = HasManyBuilder::new("comments").primary_key("uuid").build();
assert_eq!(meta.foreign_key, "comment_id");
assert_eq!(meta.primary_key, "uuid");
}
#[test]
fn through_and_dependent_can_coexist() {
let meta = HasManyBuilder::new("tags")
.through("taggings")
.dependent(DependentAction::Destroy)
.build();
assert_eq!(meta.through.as_deref(), Some("taggings"));
assert_eq!(meta.dependent, Some(DependentAction::Destroy));
}
#[test]
fn restrict_dependent_action_is_preserved() {
let meta = HasManyBuilder::new("comments")
.dependent(DependentAction::Restrict)
.build();
assert_eq!(meta.dependent, Some(DependentAction::Restrict));
}
}