Skip to main content

cast_core/
model.rs

1//! The `Model` trait — every Cast model implements this.
2
3use std::marker::PhantomData;
4
5use sqlx::FromRow;
6
7use crate::query::QueryBuilder;
8use crate::Error;
9
10pub trait Model: Sized + Send + Sync + Unpin + 'static
11where
12    for<'r> Self: FromRow<'r, sqlx::postgres::PgRow>,
13{
14    /// When true, the query builder automatically applies a `deleted_at IS NULL`
15    /// filter to `Model::query()`. Set via `#[soft_deletes]` on the struct.
16    const SOFT_DELETES: bool = false;
17
18    type PrimaryKey: sqlx::Type<sqlx::Postgres>
19        + for<'q> sqlx::Encode<'q, sqlx::Postgres>
20        + Send
21        + Sync
22        + Clone
23        + 'static;
24
25    /// Table name (e.g. `"users"`).
26    const TABLE: &'static str;
27
28    /// Primary key column name (e.g. `"id"`).
29    const PK_COLUMN: &'static str = "id";
30
31    /// All column names in this model's table.
32    const COLUMNS: &'static [&'static str];
33
34    /// Return this row's primary key.
35    fn primary_key(&self) -> &Self::PrimaryKey;
36
37    /// Start a new query builder for this model.
38    fn query() -> QueryBuilder<Self> {
39        QueryBuilder::new()
40    }
41
42    /// Fetch by primary key. Takes a Postgres pool directly — Cast Models are
43    /// Postgres-only in v0.1. For multi-driver schema + raw sqlx, use the
44    /// `cast::Pool` enum.
45    fn find(
46        pool: &sqlx::PgPool,
47        id: Self::PrimaryKey,
48    ) -> futures::future::BoxFuture<'_, Result<Option<Self>, Error>> {
49        Box::pin(async move {
50            let sql = format!(
51                "SELECT {} FROM {} WHERE {} = $1 LIMIT 1",
52                Self::COLUMNS.join(", "),
53                Self::TABLE,
54                Self::PK_COLUMN,
55            );
56            let result = sqlx::query_as::<_, Self>(&sql)
57                .bind(id)
58                .fetch_optional(pool)
59                .await?;
60            Ok(result)
61        })
62    }
63
64    /// Fetch all rows.
65    fn all(pool: &sqlx::PgPool) -> futures::future::BoxFuture<'_, Result<Vec<Self>, Error>> {
66        Box::pin(async move {
67            let sql = format!(
68                "SELECT {} FROM {}",
69                Self::COLUMNS.join(", "),
70                Self::TABLE
71            );
72            let rows = sqlx::query_as::<_, Self>(&sql).fetch_all(pool).await?;
73            Ok(rows)
74        })
75    }
76}
77
78/// Wrapper for eager-loaded query results. Generic over the loading shape;
79/// in practice a concrete `LoadedUserWithPosts` type is generated per derive.
80pub struct Loaded<M: Model, R = ()> {
81    pub items: Vec<M>,
82    pub _relations: PhantomData<R>,
83}
84
85impl<M: Model, R> Loaded<M, R> {
86    pub fn new(items: Vec<M>) -> Self {
87        Self {
88            items,
89            _relations: PhantomData,
90        }
91    }
92
93    pub fn into_inner(self) -> Vec<M> {
94        self.items
95    }
96
97    pub fn iter(&self) -> std::slice::Iter<'_, M> {
98        self.items.iter()
99    }
100}