navree

Seeding and factories

Seeders for the data an app needs and demo data, the db:seed command, and model factories for tests.

On this page

Seeders fill a database with the data your app needs to run (roles, settings) or to be demonstrated (accounts, sample records). Factories build records with sensible defaults, so a test or a seeder can create ten deals in one line. Both live in navree-db.

Writing seeders

A seeder implements Seeder: a name, which db:seed selects it by, and a run method that receives the app context and the database. Seeders are providers, so they can inject services:

src/modules/companies/seeder.rs

Register it in its module:

src/modules/companies/mod.rs

A few habits keep seeders safe to run:

  • Make them safe to run twice. The seeder above does nothing when the table already has rows. The CRM's seeders find existing accounts and roles again instead of creating duplicates, so running db:seed twice is harmless.
  • Separate what the app needs from demo data. The CRM has two seeders: crm-roles creates the roles and their permissions, which every environment needs, and crm-demo adds four demo accounts and a sales pipeline. An environment without demo data runs db:seed crm-roles alone.
  • Choose whether the writes are audited. Inserting through Repo in a unit of work (db.transaction, as above) records change events, so the audit log shows the seeded records. Inserting through the transaction with plain SeaORM, or through db.conn(), records nothing. The CRM's demo seeder does the latter, to keep its audit log for real activity.
  • Use your services. ctx.get::<T>() resolves any provider. The CRM creates its demo users with navree-auth's AuthService::create_user, so their passwords are hashed exactly as a sign-up would hash them.

There is no make:seeder generator yet: write the file by hand, as above.

Running seeders

Shell

db:seed runs the seeders of the modules in dependency order (a module's seeders run after those of the modules it imports), and the seeders of one module in the order it registered them. Once they have all run, the command prints a line for each:

Text

A name that no seeder has stops the command before anything runs, with an error that lists the registered names ("unknown seeder nope; registered seeders: demo-companies"). A seeder that fails stops the run too; the seeders after it don't run. With no seeders registered, the command prints "No seeders are registered."

From code, run_seeders(&ctx, &names) does the same and returns the names of the seeders that ran (all of them when names is empty). In a test, run the command:

Rust

Factories

A factory knows how to build one kind of record. Implement Factory for a struct: Entity is the entity it builds, and definition(n) returns the active model of the n-th record of a batch:

src/modules/companies/factory.rs

In navree::prelude, this trait is called ModelFactory, because Factory there is the kernel's provider factory. Import it as navree::db::Factory, as above, or use the prelude name.

Then insert records with create (one record, definition 0) or create_many (definitions 0..n, in order). Both take any SeaORM connection and return the inserted models:

Rust

Pass db.conn() in tests and seeders, or &*uow.tx().await? to insert inside a unit of work.

Variations

A factory is a plain struct, so its fields are its options. Laravel calls these factory states; here they are builder methods:

Rust

Related records are created parent first, passing the parent's id to the child's factory:

Rust

Unique values

n counts from 0 in every create_many call, and create always uses definition 0. A column derived only from n therefore repeats across calls: creating a company after create_many gives a second "Company 0". When a column must be unique, include something that is (a Uuid::now_v7(), or a counter the factory keeps) or create every record in one create_many call.

Navree has no fake-data generator. Derive values from n as above, or use a crate such as fake in your factories.

What factories don't do

Factories insert with plain SeaORM: they record no change events and write no audit rows, which is usually what you want for test data. They don't run your services' rules either, so a factory can create records your API would refuse. When a test is about those rules, create the data through the API instead.

There is no make:factory generator. Keep factories next to the entity they build, so seeders and the tests of every module can use them.

Popular pages

  • Installation
  • Your first app
  • Routing and controllers
  • Validation
  • Authentication
  • Benchmarks