navree
Documentation

Factories

Model factories for tests and seeders: definitions with fake data, states, sequences, parents and children, many-to-many attachments, recycled records, hooks, and making, creating or bulk-inserting records.

On this page

A factory knows how to build plausible records of one model, so a test or a seeder asks for "three won deals of one company, each with two activities" in one expression instead of filling every field by hand:

Rust

Records are created through the executor you pass, like any other save, so they get keys and timestamps, run their model events, join the unit of work and fill the current workspace.

Defining a factory

A factory implements ModelFactory for one model: definition returns one new model, usually with fake data. Link it to the model with #[model(factory = ..)], which gives the model a factory() function:

src/modules/deals/factories/deal.rs

Keys and timestamps left empty are filled when a record is saved. ctx.belongs_to(column, factory) declares a parent: creating a deal creates its company first (or reuses a recycled one) and fills the foreign key; making unsaved records leaves the column empty. ctx.index() is the record's position in the batch, ctx.count() the batch's size, and ctx.now() the app's clock.

navree make:factory deals deal writes a factory, and navree make:model deals Deal --factory links one to a new model.

Fake data

ctx.fake() is a Faker:

KindMethods
Peoplefirst_name, last_name, name, username, email, safe_email, phone, job_title
Companies and the webcompany, catch_phrase, domain, url, slug, ipv4
Placesstreet_address, city, postcode, country, country_code
Textword, words(n), sentence, sentences(n), paragraph, title, text(n)
Numbers and choicesnumber(range), decimal(low, high, scale), boolean, chance(percent), optional(percent, f), pick(&items), pick_many(&items, n), shuffle(&mut items)
Timespast(within), future(within), recent(), date_time_between(start, end), date_between(start, end)
Identifiers and secretsuuid, ulid, alphanumeric(n), hex(n), password(), color_name, hex_color, currency_code

ctx.fake().unique(|f| f.word()) draws until it gets a value the faker hasn't returned before, for unique columns (and fails after 10,000 draws in a row that were all seen, when the generator has too few values). The faker is seeded: .seed(42) on a builder makes it generate the same data every run, which keeps a failing test reproducible.

Making and creating

MethodReturns
make(), make_one(), make_many(n)Unsaved records: no database, parents left empty.
raw(), raw_one()The generated values by column, as Attributes (a request body for an HTTP test).
create(exec), create_one(exec), create_many(exec, n)Stored records, with their parents, children and attachments, saved one by one through their events.
create_quietly(exec) and the other _quietly formsThe same without model events (the model's behaviors, such as workspace filling, still run).
insert(exec)One bulk INSERT of the records, without events; returns how many.
Rust

Factory::<DealFactory>::new() starts a builder for a factory that isn't linked to its model.

States

States change the model after the definition, in the order you add them. A named state is a function of the factory:

Rust

set(column, value) sets one column, typed. state_with also gets the context (the faker, the index). prepend_state runs a state before the others, trashed() and trashed_at(time) soft-delete the records, and when(condition, |f| ..) applies changes conditionally. ModelFactory::configure sets defaults for every builder of a factory, such as hooks every record needs.

Sequences

A sequence gives the records of a batch states in turn, starting over after the last:

Rust

for_each_sequence also sets the count to the sequence's length, one record per state, and cross_join_sequence([..]) makes a record for every combination of several sequences.

Relationships

Parents

for_ points every record of the batch at one parent: a factory (one parent is created for the batch) or a stored record (for_record).

Rust

The parent's key is set before the states, so a state can still change it. without_parents() skips the parents the definition declares (their columns stay empty), and without_parent(column) skips one.

Children

has creates children for each record once it is stored, pointing at it:

Rust

Many to many

has_attached attaches records through a pivot table, with values for every pivot row: new records from a factory, created for each record, or stored ones, attached to each.

Rust

Naming relationships

A link is a typed column, a BelongsTo / HasMany / Pivot, or a name the factory declares in relationships:

Rust

Declared relationships appear in model:show. Polymorphic links take BelongsTo::morph and HasMany::morph.

Recycling records

Creating twenty deals with a definition that declares a company parent creates twenty companies. recycle reuses stored records instead, wherever the builder (or a factory nested in it) needs a parent of their model: a random one per record for the definition's parents, one per batch for for_:

Rust

lazy() turns a builder into a LazyFactory: one record, created the first time something needs it, then shared.

Hooks

Rust

after_making runs on each record once it is made (by make, and by create before saving); after_creating after each record is stored, with the executor's target to write more. without_after_making() and without_after_creating() remove the hooks a factory's configure added.

Defaults for an app

FactoryOptions sets defaults for the factories that create records through an app: whether definitions' parents are created, and a seed for every factory without one. Register it in the app (override_value(Arc::new(FactoryOptions::new().seed(7))) in a test app), and .connection("analytics") sends one builder's records to a named connection.

In seeders

Factories make seeders short:

src/modules/deals/seeders/demo.rs