Skip to main content

anvil_core/
seeder.rs

1//! Seeders and factories. Mirrors Laravel's `Illuminate\Database\Seeder` +
2//! `Illuminate\Database\Eloquent\Factories\Factory`.
3//!
4//! A **seeder** is a unit struct implementing `Seeder` that knows how to
5//! populate the DB with canonical data — e.g. `RolesSeeder` writes the same
6//! three rows on every run.
7//!
8//! A **factory** is a unit struct implementing `Factory<M>` that generates
9//! randomized fake data for tests + dev (via the `fake` crate).
10//!
11//! ```ignore
12//! use anvilforge::prelude::*;
13//! use anvilforge::seeder::{Seeder, Factory};
14//! use anvilforge::async_trait::async_trait;
15//!
16//! pub struct RolesSeeder;
17//! #[async_trait]
18//! impl Seeder for RolesSeeder {
19//!     async fn run(&self, c: &Container) -> Result<()> {
20//!         for name in ["admin", "editor", "viewer"] {
21//!             sqlx::query("INSERT INTO roles (name) VALUES ($1) ON CONFLICT DO NOTHING")
22//!                 .bind(name).execute(c.pool()).await.map_err(Error::Database)?;
23//!         }
24//!         Ok(())
25//!     }
26//! }
27//!
28//! pub struct UserFactory;
29//! impl Factory<User> for UserFactory {
30//!     fn definition() -> User {
31//!         use fake::{Fake, faker::{name::en::Name, internet::en::SafeEmail}};
32//!         User {
33//!             id: 0,
34//!             name: Name().fake(),
35//!             email: SafeEmail().fake(),
36//!             ..Default::default()
37//!         }
38//!     }
39//! }
40//! ```
41
42use std::collections::HashMap;
43use std::sync::Arc;
44
45use async_trait::async_trait;
46use parking_lot::RwLock;
47
48use crate::container::Container;
49use crate::Error;
50
51/// A database seeder. Mirrors Laravel's `Seeder::run()`.
52#[async_trait]
53pub trait Seeder: Send + Sync {
54    /// Human-readable name (defaults to type name via the derive).
55    fn name(&self) -> &'static str {
56        std::any::type_name::<Self>()
57    }
58
59    /// Run the seeder against the container.
60    async fn run(&self, c: &Container) -> Result<(), Error>;
61}
62
63/// Boxed seeder. The scaffolded `DatabaseSeeder` holds a `Vec<BoxedSeeder>`.
64pub type BoxedSeeder = Box<dyn Seeder>;
65
66/// Inventory entry for a seeder. The `#[derive(Seeder)]` macro emits one of these
67/// per type. `SeederRegistry::from_inventory()` builds a registry from every
68/// registered seeder — apps never need to write a manual registration list.
69pub struct SeederRegistration {
70    pub name: &'static str,
71    pub builder: fn() -> Arc<dyn Seeder>,
72}
73
74inventory::collect!(SeederRegistration);
75
76/// Iterate every seeder registered via `#[derive(Seeder)]`.
77pub fn collected() -> Vec<(&'static str, Arc<dyn Seeder>)> {
78    inventory::iter::<SeederRegistration>
79        .into_iter()
80        .map(|r| (r.name, (r.builder)()))
81        .collect()
82}
83
84/// Helper to call another seeder from inside one — mirrors `$this->call([...])`.
85pub async fn call_seeders(c: &Container, seeders: &[BoxedSeeder]) -> Result<(), Error> {
86    for s in seeders {
87        tracing::info!(seeder = %s.name(), "running");
88        s.run(c).await?;
89    }
90    Ok(())
91}
92
93/// Registry of named seeders — populated at app startup so `smith db:seed --class=Name`
94/// can resolve a seeder by string name. The scaffolded `DatabaseSeeder` registers
95/// every known seeder with the global `SEEDERS` registry.
96#[derive(Default, Clone)]
97pub struct SeederRegistry {
98    inner: Arc<RwLock<HashMap<String, Arc<dyn Seeder>>>>,
99}
100
101impl SeederRegistry {
102    pub fn new() -> Self {
103        Self::default()
104    }
105
106    /// Build a registry from every seeder registered via `#[derive(Seeder)]`.
107    /// Mirrors Laravel's auto-discovery of `database/seeders/*.php`.
108    pub fn from_inventory() -> Self {
109        let registry = Self::new();
110        for (name, seeder) in collected() {
111            registry.inner.write().insert(name.to_string(), seeder);
112        }
113        registry
114    }
115
116    pub fn register<S: Seeder + 'static>(&self, name: impl Into<String>, seeder: S) {
117        self.inner.write().insert(name.into(), Arc::new(seeder));
118    }
119
120    pub fn get(&self, name: &str) -> Option<Arc<dyn Seeder>> {
121        self.inner.read().get(name).cloned()
122    }
123
124    pub fn names(&self) -> Vec<String> {
125        let mut names: Vec<String> = self.inner.read().keys().cloned().collect();
126        names.sort();
127        names
128    }
129
130    pub async fn run(&self, c: &Container, name: &str) -> Result<(), Error> {
131        let seeder = self
132            .get(name)
133            .ok_or_else(|| Error::Internal(format!("unknown seeder: {name}")))?;
134        seeder.run(c).await
135    }
136}
137
138/// A model factory. Generates randomized fake instances of `M` for tests/dev.
139///
140/// Mirrors Laravel's `Post::factory()->count(50)->create()`. The `definition()`
141/// method returns a single random instance; `make_many()` / `create_many()`
142/// generate batches.
143pub trait Factory<M>: Sized {
144    /// Generate one random in-memory instance.
145    fn definition() -> M;
146
147    /// Generate `count` random in-memory instances (not persisted).
148    fn make_many(count: usize) -> Vec<M> {
149        (0..count).map(|_| Self::definition()).collect()
150    }
151}
152
153/// Helper for factories that want to persist instances.
154///
155/// Implementing this manually per model lets the factory call into the model's
156/// insert SQL.
157#[async_trait]
158pub trait PersistentFactory<M>: Factory<M>
159where
160    M: Send,
161{
162    async fn save(c: &Container, model: M) -> Result<M, Error>;
163
164    /// Generate + persist a single instance.
165    async fn create(c: &Container) -> Result<M, Error> {
166        Self::save(c, Self::definition()).await
167    }
168
169    /// Generate + persist `count` instances. Returns them in insertion order.
170    async fn create_many(c: &Container, count: usize) -> Result<Vec<M>, Error> {
171        let mut out = Vec::with_capacity(count);
172        for instance in Self::make_many(count) {
173            out.push(Self::save(c, instance).await?);
174        }
175        Ok(out)
176    }
177}
178
179/// Bind a model to its factory. Mirrors Laravel's convention of `Database\Factories\UserFactory`
180/// being associated with `App\Models\User`.
181///
182/// Implement on the model:
183/// ```ignore
184/// impl HasFactory for User {
185///     type Factory = UserFactory;
186/// }
187/// ```
188///
189/// Then `User::factory().count(50).create(&c).await?` works.
190pub trait HasFactory: Sized {
191    type Factory: Factory<Self>;
192
193    fn factory() -> FactoryBuilder<Self, Self::Factory> {
194        FactoryBuilder::new()
195    }
196}
197
198/// Fluent builder returned by `Model::factory()`. Mirrors Laravel's
199/// `Post::factory()->count(50)->create()`.
200pub struct FactoryBuilder<M, F: Factory<M>> {
201    count: usize,
202    _phantom: std::marker::PhantomData<(M, F)>,
203}
204
205impl<M, F: Factory<M>> FactoryBuilder<M, F> {
206    pub fn new() -> Self {
207        Self {
208            count: 1,
209            _phantom: std::marker::PhantomData,
210        }
211    }
212
213    /// Set how many instances the builder produces.
214    pub fn count(mut self, n: usize) -> Self {
215        self.count = n;
216        self
217    }
218
219    /// Produce in-memory instances (no DB hit). Equivalent to Laravel's `->make()`.
220    pub fn make(self) -> Vec<M> {
221        F::make_many(self.count)
222    }
223
224    /// Produce + persist instances. Equivalent to Laravel's `->create()`.
225    pub async fn create(self, c: &Container) -> Result<Vec<M>, Error>
226    where
227        M: Send,
228        F: PersistentFactory<M> + Send,
229    {
230        F::create_many(c, self.count).await
231    }
232
233    /// Convenience: produce exactly one persisted instance.
234    pub async fn create_one(self, c: &Container) -> Result<M, Error>
235    where
236        M: Send,
237        F: PersistentFactory<M> + Send,
238    {
239        F::create(c).await
240    }
241}
242
243impl<M, F: Factory<M>> Default for FactoryBuilder<M, F> {
244    fn default() -> Self {
245        Self::new()
246    }
247}