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