Skip to main content

ferro_rs/database/
model.rs

1//! Model traits for Ferro ORM
2//!
3//! Provides Laravel-like active record pattern over SeaORM entities.
4//!
5//! # Scoped Queries
6//!
7//! Use the `ScopedQuery` trait to define reusable query scopes:
8//!
9//! ```rust,ignore
10//! impl ScopedQuery for animals::Entity {
11//!     type Scope = AnimalScope;
12//! }
13//!
14//! enum AnimalScope {
15//!     ForShelter(i64),
16//!     Available,
17//!     Species(String),
18//! }
19//!
20//! // Usage:
21//! let animals = Animal::scoped(AnimalScope::ForShelter(shelter_id))
22//!     .and(AnimalScope::Available)
23//!     .all()
24//!     .await?;
25//! ```
26
27use async_trait::async_trait;
28use sea_orm::{
29    ActiveModelBehavior, ActiveModelTrait, ColumnTrait, EntityTrait, IntoActiveModel, ModelTrait,
30    PaginatorTrait, PrimaryKeyTrait, TryIntoModel,
31};
32
33use crate::database::{QueryBuilder, DB};
34use crate::error::FrameworkError;
35
36/// Trait providing Laravel-like read operations on SeaORM entities
37///
38/// Implement this trait on your SeaORM Entity to get convenient static methods
39/// for querying records.
40///
41/// # Example
42///
43/// ```rust,ignore
44/// use ferro_rs::database::Model;
45/// use sea_orm::entity::prelude::*;
46///
47/// #[derive(Clone, Debug, DeriveEntityModel)]
48/// #[sea_orm(table_name = "users")]
49/// pub struct Model {
50///     #[sea_orm(primary_key)]
51///     pub id: i32,
52///     pub name: String,
53///     pub email: String,
54/// }
55///
56/// #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
57/// pub enum Relation {}
58///
59/// impl ActiveModelBehavior for ActiveModel {}
60///
61/// // Add Ferro's Model trait
62/// impl ferro_rs::database::Model for Entity {}
63///
64/// // Now you can use:
65/// let users = Entity::all().await?;
66/// let user = Entity::find_by_pk(1).await?;
67/// ```
68#[async_trait]
69pub trait Model: EntityTrait + Sized
70where
71    Self::Model: ModelTrait<Entity = Self> + Send + Sync,
72{
73    /// Find all records
74    ///
75    /// # Example
76    /// ```rust,ignore
77    /// let users = user::Entity::all().await?;
78    /// ```
79    async fn all() -> Result<Vec<Self::Model>, FrameworkError> {
80        let db = DB::connection()?;
81        Self::find()
82            .all(db.inner())
83            .await
84            .map_err(|e| FrameworkError::database(e.to_string()))
85    }
86
87    /// Find a record by primary key (generic version)
88    ///
89    /// # Example
90    /// ```rust,ignore
91    /// let user = user::Entity::find_by_pk(1).await?;
92    /// ```
93    async fn find_by_pk<K>(id: K) -> Result<Option<Self::Model>, FrameworkError>
94    where
95        K: Into<<Self::PrimaryKey as PrimaryKeyTrait>::ValueType> + Send,
96    {
97        let db = DB::connection()?;
98        Self::find_by_id(id)
99            .one(db.inner())
100            .await
101            .map_err(|e| FrameworkError::database(e.to_string()))
102    }
103
104    /// Find a record by primary key or return an error
105    ///
106    /// # Example
107    /// ```rust,ignore
108    /// let user = user::Entity::find_or_fail(1).await?;
109    /// ```
110    async fn find_or_fail<K>(id: K) -> Result<Self::Model, FrameworkError>
111    where
112        K: Into<<Self::PrimaryKey as PrimaryKeyTrait>::ValueType> + Send + std::fmt::Debug + Copy,
113    {
114        Self::find_by_pk(id).await?.ok_or_else(|| {
115            FrameworkError::database(format!(
116                "{} with id {:?} not found",
117                std::any::type_name::<Self>(),
118                id
119            ))
120        })
121    }
122
123    /// Count all records
124    ///
125    /// # Example
126    /// ```rust,ignore
127    /// let count = user::Entity::count_all().await?;
128    /// ```
129    async fn count_all() -> Result<u64, FrameworkError> {
130        let db = DB::connection()?;
131        Self::find()
132            .count(db.inner())
133            .await
134            .map_err(|e| FrameworkError::database(e.to_string()))
135    }
136
137    /// Check if any records exist
138    ///
139    /// # Example
140    /// ```rust,ignore
141    /// if user::Entity::exists_any().await? {
142    ///     println!("Users exist!");
143    /// }
144    /// ```
145    async fn exists_any() -> Result<bool, FrameworkError> {
146        Ok(Self::count_all().await? > 0)
147    }
148
149    /// Get the first record
150    ///
151    /// # Example
152    /// ```rust,ignore
153    /// let first_user = user::Entity::first().await?;
154    /// ```
155    async fn first() -> Result<Option<Self::Model>, FrameworkError> {
156        let db = DB::connection()?;
157        Self::find()
158            .one(db.inner())
159            .await
160            .map_err(|e| FrameworkError::database(e.to_string()))
161    }
162}
163
164/// Trait providing Laravel-like write operations on SeaORM entities
165///
166/// Implement this trait alongside `Model` to get insert/update/delete methods.
167///
168/// # Example
169///
170/// ```rust,ignore
171/// use ferro_rs::database::{Model, ModelMut};
172/// use sea_orm::Set;
173///
174/// // Implement both traits
175/// impl ferro_rs::database::Model for Entity {}
176/// impl ferro_rs::database::ModelMut for Entity {}
177///
178/// // Insert a new record
179/// let new_user = user::ActiveModel {
180///     name: Set("John".to_string()),
181///     email: Set("john@example.com".to_string()),
182///     ..Default::default()
183/// };
184/// let user = user::Entity::insert_one(new_user).await?;
185///
186/// // Delete by ID
187/// user::Entity::delete_by_pk(user.id).await?;
188/// ```
189#[async_trait]
190pub trait ModelMut: Model
191where
192    Self::Model: ModelTrait<Entity = Self> + IntoActiveModel<Self::ActiveModel> + Send + Sync,
193    Self::ActiveModel: ActiveModelTrait<Entity = Self> + ActiveModelBehavior + Send,
194{
195    /// Insert a new record
196    ///
197    /// # Example
198    /// ```rust,ignore
199    /// let new_user = user::ActiveModel {
200    ///     name: Set("John".to_string()),
201    ///     email: Set("john@example.com".to_string()),
202    ///     ..Default::default()
203    /// };
204    /// let user = user::Entity::insert_one(new_user).await?;
205    /// ```
206    async fn insert_one(model: Self::ActiveModel) -> Result<Self::Model, FrameworkError> {
207        let db = DB::connection()?;
208        model
209            .insert(db.inner())
210            .await
211            .map_err(|e| FrameworkError::database(e.to_string()))
212    }
213
214    /// Update an existing record
215    ///
216    /// # Example
217    /// ```rust,ignore
218    /// let mut user: user::ActiveModel = user.into();
219    /// user.name = Set("Updated Name".to_string());
220    /// let updated = user::Entity::update_one(user).await?;
221    /// ```
222    async fn update_one(model: Self::ActiveModel) -> Result<Self::Model, FrameworkError> {
223        let db = DB::connection()?;
224        model
225            .update(db.inner())
226            .await
227            .map_err(|e| FrameworkError::database(e.to_string()))
228    }
229
230    /// Delete a record by primary key
231    ///
232    /// # Example
233    /// ```rust,ignore
234    /// let rows_deleted = user::Entity::delete_by_pk(1).await?;
235    /// ```
236    async fn delete_by_pk<K>(id: K) -> Result<u64, FrameworkError>
237    where
238        K: Into<<Self::PrimaryKey as PrimaryKeyTrait>::ValueType> + Send,
239    {
240        let db = DB::connection()?;
241        let result = Self::delete_by_id(id)
242            .exec(db.inner())
243            .await
244            .map_err(|e| FrameworkError::database(e.to_string()))?;
245        Ok(result.rows_affected)
246    }
247
248    /// Save a model (insert or update based on whether primary key is set)
249    ///
250    /// # Example
251    /// ```rust,ignore
252    /// let user = user::ActiveModel {
253    ///     name: Set("John".to_string()),
254    ///     ..Default::default()
255    /// };
256    /// let saved = user::Entity::save_one(user).await?;
257    /// ```
258    async fn save_one(model: Self::ActiveModel) -> Result<Self::Model, FrameworkError>
259    where
260        Self::ActiveModel: TryIntoModel<Self::Model>,
261    {
262        let db = DB::connection()?;
263        let saved = model
264            .save(db.inner())
265            .await
266            .map_err(|e| FrameworkError::database(e.to_string()))?;
267        saved
268            .try_into_model()
269            .map_err(|e| FrameworkError::database(e.to_string()))
270    }
271}
272
273// ============================================================================
274// SCOPED QUERIES
275// ============================================================================
276
277/// Trait for defining reusable query scopes on entities
278///
279/// Implement this trait to define common filters that can be applied
280/// to queries in a chainable, reusable way.
281///
282/// # Example
283///
284/// ```rust,ignore
285/// use ferro_rs::database::{ScopedQuery, Scope};
286///
287/// // Define scopes for your entity
288/// pub enum AnimalScope {
289///     ForShelter(i64),
290///     Available,
291///     Species(String),
292///     Size(String),
293/// }
294///
295/// impl Scope<animals::Entity> for AnimalScope {
296///     fn apply(self, query: QueryBuilder<animals::Entity>) -> QueryBuilder<animals::Entity> {
297///         use animals::Column;
298///         match self {
299///             Self::ForShelter(id) => query.filter(Column::ShelterId.eq(id)),
300///             Self::Available => query.filter(Column::Status.eq("available")),
301///             Self::Species(s) => query.filter(Column::Species.eq(s)),
302///             Self::Size(s) => query.filter(Column::Size.eq(s)),
303///         }
304///     }
305/// }
306///
307/// // Implement ScopedQuery for your entity
308/// impl ScopedQuery for animals::Entity {
309///     type Scope = AnimalScope;
310/// }
311///
312/// // Now use scopes in queries:
313/// let dogs = Animal::scoped(AnimalScope::Species("dog".into()))
314///     .and(AnimalScope::Available)
315///     .all()
316///     .await?;
317/// ```
318pub trait ScopedQuery: EntityTrait + Sized
319where
320    Self::Model: Send + Sync,
321{
322    /// The scope type for this entity
323    type Scope: Scope<Self>;
324
325    /// Start a query with the given scope applied
326    fn scoped(scope: Self::Scope) -> ScopedQueryBuilder<Self> {
327        let builder = QueryBuilder::new();
328        ScopedQueryBuilder {
329            inner: scope.apply(builder),
330        }
331    }
332
333    /// Start a query for records belonging to a specific owner
334    ///
335    /// This is a convenience method for common "for_user" or "for_owner" patterns.
336    ///
337    /// # Example
338    ///
339    /// ```rust,ignore
340    /// // If your entity has a user_id column
341    /// let user_favorites = Favorite::for_owner(user.id, Column::UserId).all().await?;
342    /// ```
343    fn for_owner<C, V>(owner_id: V, column: C) -> QueryBuilder<Self>
344    where
345        C: ColumnTrait,
346        V: Into<sea_orm::Value>,
347    {
348        QueryBuilder::new().filter(column.eq(owner_id))
349    }
350}
351
352/// A scope that can be applied to a query
353pub trait Scope<E: EntityTrait>
354where
355    E::Model: Send + Sync,
356{
357    /// Apply this scope to a query builder
358    fn apply(self, query: QueryBuilder<E>) -> QueryBuilder<E>;
359}
360
361/// Query builder with scopes applied
362pub struct ScopedQueryBuilder<E>
363where
364    E: EntityTrait,
365    E::Model: Send + Sync,
366{
367    inner: QueryBuilder<E>,
368}
369
370impl<E> ScopedQueryBuilder<E>
371where
372    E: EntityTrait,
373    E::Model: Send + Sync,
374{
375    /// Add another scope to the query
376    pub fn and<S: Scope<E>>(self, scope: S) -> Self {
377        Self {
378            inner: scope.apply(self.inner),
379        }
380    }
381
382    /// Add a filter condition
383    pub fn filter<F>(self, filter: F) -> Self
384    where
385        F: sea_orm::sea_query::IntoCondition,
386    {
387        Self {
388            inner: self.inner.filter(filter),
389        }
390    }
391
392    /// Get the underlying query builder
393    pub fn into_query(self) -> QueryBuilder<E> {
394        self.inner
395    }
396
397    /// Execute query and return all results
398    pub async fn all(self) -> Result<Vec<E::Model>, FrameworkError> {
399        self.inner.all().await
400    }
401
402    /// Execute query and return first result
403    pub async fn first(self) -> Result<Option<E::Model>, FrameworkError> {
404        self.inner.first().await
405    }
406
407    /// Execute query and return first result or error
408    pub async fn first_or_fail(self) -> Result<E::Model, FrameworkError> {
409        self.inner.first_or_fail().await
410    }
411
412    /// Count matching records
413    pub async fn count(self) -> Result<u64, FrameworkError> {
414        self.inner.count().await
415    }
416
417    /// Check if any records exist
418    pub async fn exists(self) -> Result<bool, FrameworkError> {
419        self.inner.exists().await
420    }
421}
422
423/// Macro to define scopes for an entity
424///
425/// # Example
426///
427/// ```rust,ignore
428/// define_scopes!(animals::Entity {
429///     ForShelter(shelter_id: i64) => Column::ShelterId.eq(shelter_id),
430///     Available => Column::Status.eq("available"),
431///     Species(species: String) => Column::Species.eq(species),
432/// });
433///
434/// // Usage:
435/// let dogs = Animal::scoped(AnimalScope::Species("dog".into())).all().await?;
436/// ```
437#[macro_export]
438macro_rules! define_scopes {
439    ($entity:ty { $($scope_name:ident $(($($arg:ident : $arg_ty:ty),*))? => $filter:expr),* $(,)? }) => {
440        pub enum Scope {
441            $($scope_name $(($($arg_ty),*))?,)*
442        }
443
444        impl $crate::database::Scope<$entity> for Scope {
445            fn apply(self, query: $crate::database::QueryBuilder<$entity>) -> $crate::database::QueryBuilder<$entity> {
446                match self {
447                    $(Self::$scope_name $(($($arg),*))? => query.filter($filter),)*
448                }
449            }
450        }
451
452        impl $crate::database::ScopedQuery for $entity {
453            type Scope = Scope;
454        }
455    };
456}
457
458pub use define_scopes;