Migration Versioning
Rails normally prefixes migration files with timestamps. Lesli packages replace that timestamp with a stable ten-digit code so migrations from many engines remain unique, ordered, and attributable to their owner.
CC EE NN TT VV
│ │ │ │ └─ Migration version
│ │ │ └──── Table number
│ │ └─────── Feature namespace
│ └────────── Engine number
└───────────── Collection number
Each segment contains exactly two decimal digits.
| Segment | Meaning | Example |
|---|---|---|
CC | Business collection shared by related engines | 07 for IT and help desk |
EE | Engine within the collection | 02 for LesliSupport |
NN | Feature namespace within the engine | 11 for tickets |
TT | Stable table number inside the namespace | 01 for the tickets table |
VV | Migration revision for that table or structure | 10 for version 1.0 |
The Lesli Ecosystem is the source for collection and engine codes. Allocate namespace and table numbers inside the owning engine without reusing an existing code.
Read a Migration Name
Consider the LesliSupport migration:
0702110110_create_lesli_support_tickets.rb
Its formatted code is 07.02.11.01.10:
| Digits | Meaning |
|---|---|
07 | IT and help-desk collection |
02 | LesliSupport engine |
11 | Tickets namespace |
01 | Tickets table |
10 | First migration revision, version 1.0 |
Lesli Core reserves collection and engine code 00.00. Its accounts migration is therefore:
0000000110_create_lesli_accounts.rb
Migration Revisions
Keep CC, EE, NN, and TT stable for the lifetime of a table. Advance only VV when a later migration changes that table.
| Schema release | Migration prefix | Purpose |
|---|---|---|
| 1.0 | 0702110110 | Create lesli_support_tickets |
| 1.1 | 0702110111 | Add a ticket column or index |
| 1.2 | 0702110112 | Apply another compatible change |
| 2.0 | 0702110120 | Apply the table's version 2.0 change |
The two-digit VV value identifies the migration revision; Rails still stores the complete ten-digit prefix in schema_migrations and decides whether it has run.
Every migration version must be globally unique across the complete application. Before choosing a prefix, search all local packages:
find engines gems -path "*/db/migrate/*" -type f | sort
Directory Layout
Keep versioned migrations below the package's db/migrate directory:
my_engine/
└── db/
└── migrate/
├── v1/
│ ├── 0702110110_create_lesli_support_tickets.rb
│ └── 0702110111_add_importance_to_lesli_support_tickets.rb
└── v2/
└── 0702110120_change_lesli_support_ticket_importance.rb
Use v1, v2, and so on for new packages. Some existing engines use folders such as v1.0; Rails discovers both forms recursively, so do not rename a published migration solely to normalize its folder.
The folder organizes a package release. The filename prefix remains the identifier Rails records.
Create a Migration
Generate the migration with Rails or the Lesli scaffold, then assign its Lesli prefix before running it:
bin/rails generate migration CreateLesliSupportTickets
Move the generated file into the current version folder and replace its timestamp:
20261002143000_create_lesli_support_tickets.rb
↓
db/migrate/v1/0702110110_create_lesli_support_tickets.rb
Use create_<table> for the first migration. For later changes, name the operation precisely:
0702110111_add_importance_to_lesli_support_tickets.rb
0702110112_add_account_uid_index_to_lesli_support_tickets.rb
0702110120_change_lesli_support_ticket_importance.rb
Preserve the migration API version generated for the supported Rails version:
class AddImportanceToLesliSupportTickets < ActiveRecord::Migration[8.1]
def change
add_column :lesli_support_tickets, :importance, :string
end
end
Use up and down when Rails cannot infer a safe reversal.
Never Rewrite Published History
Do not rename, renumber, move, or edit a migration after it has been released or applied in a shared environment. Rails tracks the numeric prefix, so changing it can make an existing migration appear new and run it twice.
Some existing packages still contain timestamp-prefixed legacy migrations. Leave published legacy files unchanged and use the ten-digit Lesli format for new migrations.
If a released schema needs correction:
- Keep the original migration unchanged.
- Create a new migration with the same table code and the next available
VVvalue. - Make the new change reversible when practical.
- Test migration from the previous released schema and from an empty database.
Only renumber a generated timestamp before the migration has been shared or executed outside your disposable local database.
Dependency Order
Migration order must satisfy foreign keys:
- Lesli Core identity tables
- An engine's account and shared lookup tables
- Domain tables that reference those identities
- Join, item, and history tables that reference domain records
The numeric prefix controls global ordering, not the folder name. A migration in v2 with a lower numeric prefix will still sort before a higher-prefix migration in v1.
Avoid depending on an optional engine unless that dependency is declared by the gem. A migration that references lesli_shield_* cannot run in an installation where LesliShield is absent.
Verify Migrations
Run migrations through the host Rails application, because it assembles the paths for all installed engines:
bin/rails db:migrate
bin/rails db:migrate:status
For a new migration, verify both directions when it is reversible:
bin/rails db:migrate
bin/rails db:rollback STEP=1
bin/rails db:migrate
Then prepare a clean test database and run the relevant test suite:
RAILS_ENV=test bin/rails db:prepare
bin/rails test
Review the generated db/schema.rb as part of the change. It should contain only the intended tables, columns, indexes, and foreign keys.
Migration Checklist
- Confirm the collection and engine codes in the ecosystem registry.
- Confirm the namespace and table code are unused in the package.
- Confirm the full ten-digit migration version is globally unique.
- Rename a generated timestamp before running or sharing the migration.
- Use the owning engine's table prefix.
- Add explicit foreign keys and query-driven indexes.
- Keep account scoping and soft-deletion behavior consistent with the model.
- Add a new migration instead of editing released history.
- Verify migrate, rollback when supported, clean database setup, and tests.
- Update the package's table-code registry when it maintains one.
Last Update: 2026/10/02