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}