New post: Seeding Your Database the Idempotent Way #3266
bpamiri
announced in
Announcements
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Seed data has the same shape in every app I've ever worked on: make sure this row exists; if it already does, leave it alone; do it the same way in every environment, every time, without blowing up on the second run. That property has a name — idempotency — and Wheels 4.0 ships a seeder built entirely around it.
Read: https://blog.wheels.dev/posts/idempotent-database-seeding
The whole post is a worked walkthrough of a surprisingly small surface: two file conventions and one helper function. Here's the shape of what it covers.
seedOnce()
The helper takes three named args —
modelName,uniqueProperties(comma-delimited), andproperties(the full struct). It builds aWHEREclause from the unique properties, callsfindOne(), and creates the record only if nothing matches:totalSkipped++, returns{model, action: "skipped", uniqueProperties}model.new(properties).save(), returns{model, action: "created", key}totalFailed++, returns{model, action: "failed", errors}Run it once or a hundred times, you end up with exactly one
adminrole.The file convention
seeds.cfmis shared (roles, settings, lookup tables). The environment file is a siblingseeds/directory —seeds/development.cfm, notseeds-development.cfm. The hyphenated form is what the snippet template is named, and forgetting to rename it on the way intoapp/db/is the easiest mistake to make.The transaction is the interesting design call
The entire run — both files — is wrapped in one transaction. If any single
seedOnce()entry fails validation, the whole run rolls back, including rows that already saved earlier in the same run, and returnssuccess: falsenaming the failures.This was deliberate (issue #2973). The alternative — commit what worked, report what didn't — was explicitly rejected, because a half-applied seed run must never look identical to a fully-applied one. And since
seedOnce()is idempotent, recovery is trivial: fix the broken entry, re-run, everything that existed gets skipped and everything that didn't gets created.CLI
wheels seedruns over the dev-server bridge, so the server has to be up.--generateexists but is legacy random data — not idempotent, duplicates on repeat. And two non-commands to know:wheels db:seedandwheels generate seedboth error. Usewheels seedandwheels generate snippets seed-data.Sharp edges in the post
seedOnce()checks existence and skips; it never updates an existing row.uniquePropertiesname must be present inpropertiesand be a simple value, or you get a specific thrown error.WHEREis string-interpolated (quotes escaped), not parameter-bound — keep unique values simple.result.keyexists only oncreated.Questions for the thread
--generatepath? Curious how many people are still doingWHERE NOT EXISTSby hand.Feedback on the post — what's unclear, what's missing — welcome here.
All reactions