Skip to main content

vantage_table/references/
many.rs

1//! HasMany — one-to-many relationship where the foreign key is on the target table.
2//!
3//! Example: Bakery has many Clients (via client.bakery_id)
4
5use std::fmt::Display;
6use std::{any::Any, marker::PhantomData, sync::Arc};
7
8use vantage_core::{Result, error};
9use vantage_types::{EmptyEntity, Entity, Record};
10use vantage_vista::{JoinKey, Reference as VistaReference};
11
12use crate::{references::Reference, table::Table, traits::table_source::TableSource};
13
14pub struct HasMany<T: TableSource, SourceE: Entity<T::Value>, TargetE: Entity<T::Value>> {
15    /// Foreign key column on the target table (e.g. "bakery_id" on client)
16    target_foreign_key: String,
17    /// Factory: given a data source, produce the target table
18    build_target: Arc<dyn Fn(T) -> Table<T, TargetE> + Send + Sync>,
19    _phantom: PhantomData<SourceE>,
20}
21
22impl<T: TableSource, SourceE: Entity<T::Value>, TargetE: Entity<T::Value>>
23    HasMany<T, SourceE, TargetE>
24{
25    pub fn new(
26        target_foreign_key: impl Into<String>,
27        build_target: impl Fn(T) -> Table<T, TargetE> + Send + Sync + 'static,
28    ) -> Self {
29        Self {
30            target_foreign_key: target_foreign_key.into(),
31            build_target: Arc::new(build_target),
32            _phantom: PhantomData,
33        }
34    }
35}
36
37impl<T: TableSource, SourceE: Entity<T::Value>, TargetE: Entity<T::Value>> Clone
38    for HasMany<T, SourceE, TargetE>
39{
40    fn clone(&self) -> Self {
41        Self {
42            target_foreign_key: self.target_foreign_key.clone(),
43            build_target: self.build_target.clone(),
44            _phantom: PhantomData,
45        }
46    }
47}
48
49impl<T: TableSource, SourceE: Entity<T::Value>, TargetE: Entity<T::Value>> std::fmt::Debug
50    for HasMany<T, SourceE, TargetE>
51{
52    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53        f.debug_struct("HasMany")
54            .field("target_foreign_key", &self.target_foreign_key)
55            .finish()
56    }
57}
58
59impl<
60    T: TableSource + 'static,
61    SourceE: Entity<T::Value> + 'static,
62    TargetE: Entity<T::Value> + 'static,
63> Reference for HasMany<T, SourceE, TargetE>
64where
65    T::Value: Into<ciborium::Value> + From<ciborium::Value>,
66    T::Id: Display + From<String>,
67{
68    fn columns(&self, source_id: &str, _target_id: &str) -> (String, String) {
69        (source_id.to_string(), self.target_foreign_key.clone())
70    }
71
72    fn vista_reference(&self, name: &str, target: String, source_id: &str) -> VistaReference {
73        VistaReference::has_many(
74            name,
75            target,
76            JoinKey::new(source_id, &self.target_foreign_key),
77        )
78    }
79
80    fn build_target(&self, data_source: &dyn Any) -> Box<dyn Any> {
81        let ds = data_source
82            .downcast_ref::<T>()
83            .expect("data source type mismatch in HasMany::build_target");
84        Box::new((self.build_target)(ds.clone()))
85    }
86
87    fn build_target_erased(&self, data_source: &dyn Any) -> Box<dyn Any> {
88        let ds = data_source
89            .downcast_ref::<T>()
90            .expect("data source type mismatch in HasMany::build_target_erased");
91        Box::new((self.build_target)(ds.clone()).into_entity::<EmptyEntity>())
92    }
93
94    fn resolve_from_row(
95        &self,
96        data_source: &dyn Any,
97        source_id_field: &str,
98        source_row: &dyn Any,
99    ) -> Result<Box<dyn Any>> {
100        let ds = data_source
101            .downcast_ref::<T>()
102            .ok_or_else(|| error!("data source type mismatch in HasMany::resolve_from_row"))?;
103        let row = source_row
104            .downcast_ref::<Record<T::Value>>()
105            .ok_or_else(|| error!("source row type mismatch in HasMany::resolve_from_row"))?;
106
107        let mut target = (self.build_target)(ds.clone());
108        let (src_col, tgt_col) = self.columns(source_id_field, "");
109        let join_value = row
110            .get(&src_col)
111            .cloned()
112            .ok_or_else(|| error!("source row missing id field", field = src_col.as_str()))?;
113        // Rows arriving from a script/JSON surface carry ids in string form.
114        let join_value = ds.coerce_reference_value(join_value);
115
116        let condition = ds.eq_value_condition(&tgt_col, join_value.clone())?;
117        target.add_condition(condition);
118        // Inserting a child into this set should fill the foreign key.
119        target.add_invariant(tgt_col, join_value);
120
121        Ok(Box::new(target.into_entity::<EmptyEntity>()))
122    }
123
124    fn target_table_name(&self, data_source: &dyn Any) -> String {
125        let ds = data_source
126            .downcast_ref::<T>()
127            .expect("data source type mismatch in HasMany::target_table_name");
128        (self.build_target)(ds.clone()).table_name().to_string()
129    }
130}