vantage_table/active_entity_ext.rs
1//! Relationship traversal from a loaded record handle.
2//!
3//! [`ActiveEntity`] and [`ActiveRecord`] live in `vantage-dataset` and know
4//! nothing about `Table`, so this `Table`-aware extension is provided here as a
5//! trait. It is the record-level equivalent of `table.get_ref_from_row(...)`:
6//! given a launch you loaded with `get_entity` (typed) or `get_value_record`
7//! (untyped), `launch.get_ref::<LaunchCrew>("launch_crew")` returns the child
8//! table scoped to that launch (and carrying the foreign-key invariant, so
9//! inserts conform — see `Table::add_invariant`).
10//!
11//! `ActiveEntity` wraps a typed struct with no id column, so it serializes the
12//! entity and injects its id before traversing. `ActiveRecord` already holds the
13//! raw row (including id/foreign-key columns), so its impl forwards directly.
14
15use vantage_core::{Result, error};
16use vantage_dataset::prelude::{ActiveEntity, ActiveRecord};
17use vantage_types::{Entity, InvariantValue, TryIntoRecord};
18
19use crate::{
20 table::Table,
21 traits::{column_like::ColumnLike, table_source::TableSource},
22};
23
24/// Traverse a relation from a loaded record.
25pub trait GetRefExt<T: TableSource, E> {
26 /// Return the related set for `relation`, reading the join value out of the
27 /// in-memory entity. Errors if the relation is unknown to the table.
28 fn get_ref<E2: Entity<T::Value> + 'static>(&self, relation: &str) -> Result<Table<T, E2>>;
29}
30
31impl<'a, T, E> GetRefExt<T, E> for ActiveEntity<'a, Table<T, E>, E>
32where
33 T: TableSource,
34 T::Id: Into<T::Value>,
35 T::Value: InvariantValue,
36 E: Entity<T::Value> + 'static,
37 <E as TryIntoRecord<T::Value>>::Error: std::fmt::Debug,
38{
39 fn get_ref<E2: Entity<T::Value> + 'static>(&self, relation: &str) -> Result<Table<T, E2>> {
40 let mut record = self
41 .data()
42 .clone()
43 .try_into_record()
44 .map_err(|e| error!("Failed to serialize entity to record", error = e))?;
45
46 // The entity struct carries no id column, but has-many traversal reads the
47 // parent id out of the row — inject it from the ActiveEntity's known id.
48 let id_field = self
49 .dataset()
50 .id_field()
51 .map(|c| c.name().to_string())
52 .unwrap_or_else(|| "id".to_string());
53 record.insert(id_field, self.id().clone().into());
54
55 self.dataset().get_ref_from_row::<E2>(relation, &record)
56 }
57}
58
59impl<'a, T, E> GetRefExt<T, E> for ActiveRecord<'a, Table<T, E>>
60where
61 T: TableSource,
62 T::Value: InvariantValue,
63 E: Entity<T::Value> + 'static,
64{
65 fn get_ref<E2: Entity<T::Value> + 'static>(&self, relation: &str) -> Result<Table<T, E2>> {
66 // The raw row is already in hand (id and foreign keys included), so unlike
67 // the `ActiveEntity` impl there is nothing to serialize or inject.
68 let row: &vantage_types::Record<T::Value> = self;
69 self.dataset().get_ref_from_row::<E2>(relation, row)
70 }
71}