Why I build complete, realistic seed datasets
Realistic seed data gave Plantronic a fast local reset, useful demos, and a safe way for colleagues to contribute without production data access.
- Published
- Reading time
- 7 min read
I like opening a project and finding it already inhabited.
For Plantronic, that meant a local database with people, skills, teams, projects, tasks, assignments, working calendars, and enough variation to make the planning screens useful. The names were fictional and the records were generated, but the relationships and distributions looked plausible. I could reset the database, start Phoenix, and work on an actual planning situation instead of an empty table and one hand-made row.
An April 2025 change to work-hour patterns reminded me how much I had come to value that setup. The individual change was small. The useful retrospective is broader: building a complete seed dataset paid for itself throughout the project.
A usable local system changes the development loop
Many applications have a seed script that creates one user and perhaps a few lookup rows. Everything else is left to the developer. This is quick to write, but it pushes setup work into every feature. Before fixing a planning view, I would need to create a project, add tasks, assign employees, fill calendars, and arrange the exact states that make the view interesting.
I would rather do that modeling once in code.
A complete dataset gives me a known place to begin. I can inspect a busy team, a person with reduced availability, overlapping assignments, projects at different stages, and tasks with different time frames. I can move between features without rebuilding their surrounding data by hand. If an experiment leaves the database in a strange state, I throw it away and recreate it.
This is not test isolation. A seed dataset is too broad for a focused unit test and often contains some controlled randomness. Its purpose is to make the whole application useful. It supports exploratory development, visual checks, debugging, and the ordinary work of understanding how one feature affects another.
The distinction matters. Tests should create the minimum state needed for a reliable assertion. Seeds should create enough state for a human to explore the system.
Realistic does not mean copied from production
The strongest benefit on Plantronic had little to do with convenience. Good seeds let me separate useful development data from production data access.
Colleagues could help with the software using a local system that behaved like the real application without receiving a copy of client records. They could work on layouts, components, queries, tests, and planning behavior against fictional people and projects. The relationships were present, so the work was meaningful. The sensitive source material was not.
That separation made collaboration much cleaner. I did not have to choose between an empty development environment and sharing more data than a colleague needed. Production access remained limited, while the repository contained what was necessary to reproduce the shape of the application.
Seeds are not a complete data-security policy. They do not replace access controls, careful logs, sanitized fixtures, or a rule against copying production dumps onto laptops. They simply remove one common excuse for doing so. If the generated dataset is good enough for daily work, developers have less reason to reach for real records.
The goal was plausibility, not imitation. Fictional data should preserve useful properties such as cardinality, date ranges, optional fields, workloads, and relationships. It should not preserve names, identifiers, comments, or unusual combinations that could point back to a real person or project.
Demos stopped feeling staged
The same dataset made client demos unusually satisfying.
A feature shown against two records named “Test” looks unfinished even when the code is sound. Empty states are important, but an empty planning system cannot demonstrate planning. With the seeds loaded, Plantronic looked alive. Teams had work. Calendars varied. Projects and tasks formed recognizable clusters. Filters produced results that made sense.
That changed the conversation. I could demonstrate a feature in context instead of narrating what it might look like after data arrived. The client could notice whether a screen felt plausible, whether a workload was readable, or whether a label sounded wrong. Seed data helped prepare the discussion as much as it prepared the database.
There was also less demo anxiety. I did not need to assemble a fragile scenario shortly before a meeting and then avoid touching it. If I broke the local state while testing, I could recreate the same kind of useful system. The exact generated records did not have to be identical. The dataset had to retain the same coverage and character.
Ecto and ExMachina make this pleasantly direct
Elixir projects give me a simple way to organize this. Ecto owns schemas, constraints, associations, and persistence. ExMachina provides factories for building valid records. I use the same factories in tests and seeds.
That reuse is important. Without it, projects tend to grow two object-construction systems. Tests learn how to create a valid employee one way, while seeds.exs assembles one with a separate pile of attributes. As schemas evolve, one path is updated and the other quietly rots.
A shared factory keeps the defaults in one place. Tests override only the fields relevant to an assertion. Seeds compose many factory records into a complete scenario, then override values that make the dataset believable. Sequences can provide stable variation for hours, dates, names, and other repeated values. Carefully chosen random values can fill in details where exact reproducibility is not important.
The entrypoint remains a small priv/repo/seeds.exs script. It starts what the application needs and calls the seed module. I like that file to cover the global, idempotent setup of the application first: required roles, labels, configuration, or an initial account should be safe to create again. The larger fictional dataset can then be built for a fresh local database.
In Plantronic, the project aliases made the reset loop explicit. In simplified form, they looked like this:
"ecto.setup": [
"ecto.create",
"ecto.migrate",
"run --no-start priv/repo/seeds.exs"
],
"ecto.reset": ["ecto.drop", "ecto.setup"]
This custom mix ecto.reset command was destructive by design, so I used it only for a local database. It dropped the current database, created it again, ran migrations, and reinstantiated the seeds. One command returned me to a usable application.
That made larger refactors easier. I could change schemas and associations, reset, and immediately browse a coherent dataset. It also kept the seed code honest. A script that runs only when a developer first joins the project will decay unnoticed. A script in my regular reset loop fails while the relevant change is still fresh.
Complete seeds need maintenance
There is a cost. A realistic dataset touches much of the model, so schema changes often require seed changes. Generation order matters when records have associations. Random data can produce nonsense unless its ranges and combinations are constrained. A large seed run can also become slow.
I do not see those problems as reasons to keep seeds trivial. I treat them as limits to manage.
Factories handle valid defaults. Explicit scenario code handles relationships. Small sequences produce plausible variation. Bulk inserts help with dense calendar data. Required global setup stays idempotent, while local demo records can assume an empty database after mix ecto.reset. I also avoid trying to model every possible edge case. Seeds need enough breadth to make the application useful, not enough detail to become a second production system.
The April work-hour change was a typical maintenance pass. Generated availability had been too arbitrary, so I replaced it with more recognizable patterns. Other seeded values became more representative as our understanding improved. None of this was profound on its own. It was ordinary care for a development tool I used every day.
That is why I embrace seed data. It shortens my local feedback loop, gives colleagues a useful system without production access, and makes demos feel like the product rather than a wireframe. In an Elixir application, Ecto, shared ExMachina factories, and a plain seeds.exs script are enough to build that workflow. The populated database gives me a clean, repeatable place to work.