Skip to main content

arcature_data/query/
relation.rs

1//! Eager loading of typed relations over SeaORM.
2//!
3//! [`Relation`] is a thin marker that names a related entity so [`Query`]
4//! can eager-load it without the caller spelling out the `Related` impl. The
5//! underlying load delegates to SeaORM's `find_also_related` — Arcature does
6//! not reimplement the relation engine.
7
8use std::marker::PhantomData;
9
10use arcature_db::sea_orm;
11
12use crate::error::DataError;
13use crate::query::Query;
14
15/// A typed reference to a related entity `R`, for eager loading.
16///
17/// Construct with [`of`] and pass to [`Query::with`]. The relation
18/// is resolved through SeaORM's `Related<R>` impl on the parent entity, so the
19/// caller does not name the relation enum variant — the type alone selects it.
20pub struct Relation<R>
21where
22    R: sea_orm::EntityTrait,
23{
24    // The related entity type is the relation selector. SeaORM's
25    // `find_also_related::<R>` resolves the join through `Related<R>` on the
26    // parent entity, so no relation-name string is needed. `PhantomData`
27    // holds the type without requiring `R: Default`.
28    _related: PhantomData<R>,
29}
30
31/// Reference the related entity `R` for eager loading. Generic over `R` so
32/// the caller can name it explicitly:
33///
34/// ```ignore
35/// use arcature_data::Relation;
36/// query.with(Relation::of::<user::Entity>())
37/// ```
38///
39/// `R` is resolved through SeaORM's `Related<R>` impl on the parent entity.
40#[must_use]
41pub fn of<R>() -> Relation<R>
42where
43    R: sea_orm::EntityTrait,
44{
45    Relation {
46        _related: PhantomData,
47    }
48}
49
50impl<'db, E> Query<'db, E>
51where
52    E: sea_orm::EntityTrait,
53{
54    /// Eager-load a related entity `R` alongside each row of `E`, returning
55    /// `(parent, Option<related>)` pairs. Delegates to SeaORM's
56    /// `find_also_related`.
57    ///
58    /// ```ignore
59    /// let rows = post::Entity::query(db)
60    ///     .with(arcature_data::Relation::of::<user::Entity>())
61    ///     .await?; // Vec<(post::Model, Option<user::Model>)>
62    /// ```
63    ///
64    /// `R` must be `Default` — SeaORM's generated `Entity` is a unit struct
65    /// that derives `Default`, so this is satisfied by every
66    /// `#[derive(DeriveEntityModel)]` entity without extra work.
67    ///
68    /// # Errors
69    ///
70    /// Returns [`DataError::Database`] if the underlying SeaORM query fails.
71    pub async fn with<R>(
72        self,
73        _relation: Relation<R>,
74    ) -> Result<Vec<(E::Model, Option<R::Model>)>, DataError>
75    where
76        R: sea_orm::EntityTrait + Default,
77        E: sea_orm::Related<R>,
78    {
79        self.select
80            .find_also_related(R::default())
81            .all(self.db.orm())
82            .await
83            .map_err(DataError::from)
84    }
85}