Skip to main content

renox_core/db/
factory.rs

1use std::future::Future;
2
3use super::{Db, Model};
4use crate::Result;
5
6/// Builds models with fake data for seeders and tests.
7///
8/// ```
9/// # use renox::prelude::*;
10/// # #[derive(Model, serde::Serialize, Default)] struct Product { id: i64, name: String, price: i64 }
11/// use renox::fake::{Fake, faker::lorem::en::Word};
12///
13/// impl Factory for Product {
14///     fn definition() -> Self {
15///         Product { name: Word().fake(), price: (5_000..50_000).fake(), ..Default::default() }
16///     }
17/// }
18///
19/// # async fn demo(db: Db) -> Result {
20/// Product::factory().count(50).create(&db).await?;
21/// # Ok(()) }
22/// ```
23pub trait Factory: Model {
24    /// A model with sample values (usually fake data), not saved.
25    fn definition() -> Self;
26
27    /// A builder for models with states and sequences (Laravel's
28    /// `Product::factory()->count(3)->state(…)->create()`):
29    ///
30    /// ```
31    /// # use renox::prelude::*;
32    /// # #[derive(Model, serde::Serialize, Default)] struct Product { id: i64, name: String, stock: i64 }
33    /// # impl Factory for Product { fn definition() -> Self { Product { name: "Coffee".into(), stock: 10, ..Default::default() } } }
34    /// /// A state: a plain function (or closure) that changes the model.
35    /// fn sold_out(p: &mut Product) {
36    ///     p.stock = 0;
37    /// }
38    ///
39    /// # async fn demo(db: Db) -> Result {
40    /// let gone = Product::factory().count(3).state(sold_out).create(&db).await?;
41    /// let named = Product::factory()
42    ///     .count(2)
43    ///     .sequence(|i, p| p.name = format!("Coffee {}", i + 1)) // "Coffee 1", "Coffee 2"
44    ///     .make(); // unsaved
45    /// # let _ = (gone, named); Ok(()) }
46    /// ```
47    fn factory() -> FactoryBuilder<Self> {
48        FactoryBuilder {
49            count: 1,
50            steps: Vec::new(),
51        }
52    }
53}
54
55type Step<M> = Box<dyn FnMut(usize, &mut M) + Send>;
56
57/// Models from [`Factory::definition`] with states and sequences applied,
58/// from [`Factory::factory`].
59#[must_use = "a factory makes nothing until `make` or `create`"]
60pub struct FactoryBuilder<M> {
61    count: usize,
62    steps: Vec<Step<M>>,
63}
64
65impl<M: Factory> FactoryBuilder<M> {
66    /// How many models to make (1 by default).
67    pub fn count(mut self, count: usize) -> Self {
68        self.count = count;
69        self
70    }
71
72    /// Changes every model, after the definition and the steps before it.
73    pub fn state(mut self, mut state: impl FnMut(&mut M) + Send + 'static) -> Self {
74        self.steps.push(Box::new(move |_, model| state(model)));
75        self
76    }
77
78    /// Changes each model knowing its position (0, 1, …), e.g. to cycle
79    /// through values: `.sequence(|i, p| p.size = ["S", "M", "L"][i % 3].into())`.
80    pub fn sequence(mut self, step: impl FnMut(usize, &mut M) + Send + 'static) -> Self {
81        self.steps.push(Box::new(step));
82        self
83    }
84
85    /// The models, unsaved.
86    pub fn make(mut self) -> Vec<M> {
87        (0..self.count)
88            .map(|i| {
89                let mut model = M::definition();
90                for step in &mut self.steps {
91                    step(i, &mut model);
92                }
93                model
94            })
95            .collect()
96    }
97
98    /// One model, unsaved (the first of `count`).
99    pub fn make_one(self) -> M {
100        self.count(1).make().pop().expect("one model")
101    }
102
103    /// The models, inserted in one transaction.
104    pub fn create(self, db: &Db) -> impl Future<Output = Result<Vec<M>>> + Send + '_ {
105        // Made before the future, so it holds no closures across `.await`.
106        let models = self.make();
107        async move {
108            let mut tx = db.begin().await?;
109            let mut saved = Vec::with_capacity(models.len());
110            for mut model in models {
111                model.insert(&mut tx).await?;
112                saved.push(model);
113            }
114            tx.commit().await?;
115            Ok(saved)
116        }
117    }
118
119    /// One model, inserted.
120    pub fn create_one(self, db: &Db) -> impl Future<Output = Result<M>> + Send + '_ {
121        let mut model = self.make_one();
122        async move {
123            model.insert(db).await?;
124            Ok(model)
125        }
126    }
127}