use std::marker::PhantomData;
use super::foreign_key::CascadeAction;
#[derive(Debug, Clone)]
pub struct PolymorphicAssociation<K> {
pub association_name: String,
pub id_field: String,
pub type_field: String,
pub on_delete: CascadeAction,
pub null: bool,
pub db_index: bool,
_phantom: PhantomData<K>,
}
impl<K> PolymorphicAssociation<K> {
pub fn new(association_name: impl Into<String>) -> Self {
let name = association_name.into();
Self {
id_field: format!("{}_id", name),
type_field: format!("{}_type", name),
association_name: name,
on_delete: CascadeAction::default(),
null: false,
db_index: true,
_phantom: PhantomData,
}
}
pub fn id_field(mut self, field_name: impl Into<String>) -> Self {
self.id_field = field_name.into();
self
}
pub fn type_field(mut self, field_name: impl Into<String>) -> Self {
self.type_field = field_name.into();
self
}
pub fn on_delete(mut self, action: CascadeAction) -> Self {
self.on_delete = action;
self
}
pub fn null(mut self, null: bool) -> Self {
self.null = null;
self
}
pub fn db_index(mut self, db_index: bool) -> Self {
self.db_index = db_index;
self
}
pub fn association_name(&self) -> &str {
&self.association_name
}
pub fn get_id_field(&self) -> &str {
&self.id_field
}
pub fn get_type_field(&self) -> &str {
&self.type_field
}
pub fn get_on_delete(&self) -> CascadeAction {
self.on_delete
}
pub fn is_null(&self) -> bool {
self.null
}
pub fn has_db_index(&self) -> bool {
self.db_index
}
}
impl<K> Default for PolymorphicAssociation<K> {
fn default() -> Self {
Self::new("polymorphic")
}
}
#[derive(Debug, Clone)]
pub struct PolymorphicManyToMany<K> {
pub association_name: String,
pub through: Option<String>,
pub source_field: String,
pub target_id_field: String,
pub target_type_field: String,
pub on_delete: CascadeAction,
pub lazy: bool,
pub db_constraint_prefix: Option<String>,
_phantom: PhantomData<K>,
}
impl<K> PolymorphicManyToMany<K> {
pub fn new(association_name: impl Into<String>) -> Self {
let name = association_name.into();
Self {
association_name: name.clone(),
through: None,
source_field: String::new(),
target_id_field: format!("{}_id", name),
target_type_field: format!("{}_type", name),
on_delete: CascadeAction::Cascade,
lazy: true,
db_constraint_prefix: None,
_phantom: PhantomData,
}
}
pub fn through(mut self, table_name: impl Into<String>) -> Self {
self.through = Some(table_name.into());
self
}
pub fn source_field(mut self, field_name: impl Into<String>) -> Self {
self.source_field = field_name.into();
self
}
pub fn target_id_field(mut self, field_name: impl Into<String>) -> Self {
self.target_id_field = field_name.into();
self
}
pub fn target_type_field(mut self, field_name: impl Into<String>) -> Self {
self.target_type_field = field_name.into();
self
}
pub fn on_delete(mut self, action: CascadeAction) -> Self {
self.on_delete = action;
self
}
pub fn lazy(mut self, lazy: bool) -> Self {
self.lazy = lazy;
self
}
pub fn db_constraint_prefix(mut self, prefix: impl Into<String>) -> Self {
self.db_constraint_prefix = Some(prefix.into());
self
}
pub fn association_name(&self) -> &str {
&self.association_name
}
pub fn get_through(&self) -> Option<&str> {
self.through.as_deref()
}
pub fn get_source_field(&self) -> &str {
&self.source_field
}
pub fn get_target_id_field(&self) -> &str {
&self.target_id_field
}
pub fn get_target_type_field(&self) -> &str {
&self.target_type_field
}
pub fn get_on_delete(&self) -> CascadeAction {
self.on_delete
}
pub fn is_lazy(&self) -> bool {
self.lazy
}
pub fn get_db_constraint_prefix(&self) -> Option<&str> {
self.db_constraint_prefix.as_deref()
}
}
impl<K> Default for PolymorphicManyToMany<K> {
fn default() -> Self {
Self::new("polymorphic")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_polymorphic_association_creation() {
let rel: PolymorphicAssociation<i64> = PolymorphicAssociation::new("commentable");
assert_eq!(rel.association_name(), "commentable");
assert_eq!(rel.get_id_field(), "commentable_id");
assert_eq!(rel.get_type_field(), "commentable_type");
assert_eq!(rel.get_on_delete(), CascadeAction::NoAction);
assert!(!rel.is_null());
assert!(rel.has_db_index());
}
#[test]
fn test_polymorphic_association_builder() {
let rel: PolymorphicAssociation<i64> = PolymorphicAssociation::new("taggable")
.id_field("object_id")
.type_field("content_type")
.on_delete(CascadeAction::Cascade)
.null(true)
.db_index(false);
assert_eq!(rel.association_name(), "taggable");
assert_eq!(rel.get_id_field(), "object_id");
assert_eq!(rel.get_type_field(), "content_type");
assert_eq!(rel.get_on_delete(), CascadeAction::Cascade);
assert!(rel.is_null());
assert!(!rel.has_db_index());
}
#[test]
fn test_polymorphic_association_default_field_names() {
let rel: PolymorphicAssociation<i64> = PolymorphicAssociation::new("imageable");
assert_eq!(rel.get_id_field(), "imageable_id");
assert_eq!(rel.get_type_field(), "imageable_type");
}
#[test]
fn test_polymorphic_many_to_many_creation() {
let rel: PolymorphicManyToMany<i64> = PolymorphicManyToMany::new("taggable");
assert_eq!(rel.association_name(), "taggable");
assert_eq!(rel.get_through(), None);
assert_eq!(rel.get_source_field(), "");
assert_eq!(rel.get_target_id_field(), "taggable_id");
assert_eq!(rel.get_target_type_field(), "taggable_type");
assert_eq!(rel.get_on_delete(), CascadeAction::Cascade);
assert!(rel.is_lazy());
}
#[test]
fn test_polymorphic_many_to_many_builder() {
let rel: PolymorphicManyToMany<i64> = PolymorphicManyToMany::new("taggable")
.through("taggings")
.source_field("tag_id")
.target_id_field("object_id")
.target_type_field("content_type")
.on_delete(CascadeAction::Restrict)
.lazy(false)
.db_constraint_prefix("poly_tag");
assert_eq!(rel.association_name(), "taggable");
assert_eq!(rel.get_through(), Some("taggings"));
assert_eq!(rel.get_source_field(), "tag_id");
assert_eq!(rel.get_target_id_field(), "object_id");
assert_eq!(rel.get_target_type_field(), "content_type");
assert_eq!(rel.get_on_delete(), CascadeAction::Restrict);
assert!(!rel.is_lazy());
assert_eq!(rel.get_db_constraint_prefix(), Some("poly_tag"));
}
#[test]
fn test_polymorphic_many_to_many_default_field_names() {
let rel: PolymorphicManyToMany<i64> = PolymorphicManyToMany::new("likeable");
assert_eq!(rel.get_target_id_field(), "likeable_id");
assert_eq!(rel.get_target_type_field(), "likeable_type");
}
#[test]
fn test_cascade_actions_polymorphic() {
let actions = vec![
CascadeAction::NoAction,
CascadeAction::Restrict,
CascadeAction::SetNull,
CascadeAction::SetDefault,
CascadeAction::Cascade,
];
for action in actions {
let rel: PolymorphicAssociation<i64> =
PolymorphicAssociation::new("commentable").on_delete(action);
assert_eq!(rel.get_on_delete(), action);
}
}
#[test]
fn test_null_configuration_polymorphic() {
let rel1: PolymorphicAssociation<i64> =
PolymorphicAssociation::new("commentable").null(true);
assert!(rel1.is_null());
let rel2: PolymorphicAssociation<i64> =
PolymorphicAssociation::new("commentable").null(false);
assert!(!rel2.is_null());
}
#[test]
fn test_db_index_configuration_polymorphic() {
let rel1: PolymorphicAssociation<i64> =
PolymorphicAssociation::new("commentable").db_index(true);
assert!(rel1.has_db_index());
let rel2: PolymorphicAssociation<i64> =
PolymorphicAssociation::new("commentable").db_index(false);
assert!(!rel2.has_db_index());
}
}