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::{table::Table, traits::table_source::TableSource};
20
21/// Traverse a relation from a loaded record.
22pub trait GetRefExt<T: TableSource, E> {
23 /// Return the related set for `relation`, reading the join value out of the
24 /// in-memory entity. Errors if the relation is unknown to the table.
25 fn get_ref<E2: Entity<T::Value> + 'static>(&self, relation: &str) -> Result<Table<T, E2>>;
26}
27
28impl<'a, T, E> GetRefExt<T, E> for ActiveEntity<'a, Table<T, E>, E>
29where
30 T: TableSource,
31 T::Id: Into<T::Value>,
32 T::Value: InvariantValue,
33 E: Entity<T::Value> + 'static,
34 <E as TryIntoRecord<T::Value>>::Error: std::fmt::Debug,
35{
36 fn get_ref<E2: Entity<T::Value> + 'static>(&self, relation: &str) -> Result<Table<T, E2>> {
37 let mut record = self
38 .data()
39 .clone()
40 .try_into_record()
41 .map_err(|e| error!("Failed to serialize entity to record", error = e))?;
42
43 // The entity struct carries no id column, but has-many traversal reads the
44 // parent id out of the row — inject it from the ActiveEntity's known id.
45 let id_field = self.dataset().id_field_name();
46 record.insert(id_field, self.id().clone().into());
47
48 self.dataset().get_ref_from_row::<E2>(relation, &record)
49 }
50}
51
52impl<'a, T, E> GetRefExt<T, E> for ActiveRecord<'a, Table<T, E>>
53where
54 T: TableSource,
55 T::Value: InvariantValue,
56 E: Entity<T::Value> + 'static,
57{
58 fn get_ref<E2: Entity<T::Value> + 'static>(&self, relation: &str) -> Result<Table<T, E2>> {
59 // The raw row is already in hand (id and foreign keys included), so unlike
60 // the `ActiveEntity` impl there is nothing to serialize or inject.
61 let row: &vantage_types::Record<T::Value> = self;
62 self.dataset().get_ref_from_row::<E2>(relation, row)
63 }
64}