Skip to main content

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}