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}