write-migration
Use this skill when writing or running Sequel database migrations in Hanami 2.x.
Core principle: Sequel migration DSL is provided by jeremyevans/sequel — not ActiveRecord. Never use ActiveRecord syntax here.
Quick Reference
| Scenario |
Approach |
| Create a new table |
create_table(:table_name) { ... } inside Sequel.migration { change { ... } } |
| Add a column to existing table |
alter_table(:table_name) { add_column :col, :type } |
| Remove a column |
alter_table(:table_name) { drop_column :col } |
| Add an index |
alter_table(:table_name) { add_index :col } |
| Rename a column |
alter_table(:table_name) { rename_column :old, :new } |
| Reversible migration |
Use change { } block — Sequel infers the inverse automatically |
| Irreversible migration |
Use up { } / down { } blocks explicitly |
| Generate migration file |
hanami generate migration create_users |
| Run pending migrations |
hanami db migrate |
| Roll back last migration |
hanami db rollback |
Core Rules
Generate the migration file using the Hanami CLI:
hanami generate migration <migration_name>
This creates db/migrate/<timestamp>_<migration_name>.rb.
Open the generated file and write the migration body inside the
Sequel.migration block. Prefer change { } for reversible operations.
Define the schema change using the Sequel DSL. Always specify column types explicitly — do not rely on inference.
Run the migration:
hanami db migrate
Verify the schema change in the database or via hanami console:
Hanami.app["db.rom"].relations[:users].schema.to_h
Update the ROM Relation (define-relation) to reflect any new
or removed columns. The Relation schema must stay in sync with the database.
Update Entities and Structs (define-entity) if
attribute lists change.
Run the test suite to confirm nothing is broken.
Common Mistakes & Red Flags
| Mistake / Red Flag |
Reality |
Severity |
ActiveRecord syntax in migration files (e.g. add_column :users, :email, :string) |
Sequel uses alter_table(:users) { add_column :email, :text }. Column types are Sequel generic types (:text, :integer), not Rails types (:string, :bigint). |
🔴 Blocker |
Missing null: false on required columns |
Sequel does not add NOT NULL by default. Always declare null: false for required columns — omitting it allows NULL values silently. |
🔴 Blocker |
Using change { } for drop_column or rename_column |
These operations are not automatically reversible by Sequel. Use explicit up { } / down { } blocks. |
🔴 Blocker |
| Schema changes without corresponding ROM Relation updates |
After adding or removing columns, the ROM Relation schema must be updated. If using schema :table, infer: true, the schema is re-inferred at boot, but explicit attribute declarations will be stale. |
🟠 High |
Running migrations without checking HANAMI_ENV |
hanami db migrate uses DATABASE_URL from the current environment. Always confirm HANAMI_ENV is set correctly before running in staging or production. |
🟠 High |
Using :timestamp without timezone |
Use :timestamptz (PostgreSQL) rather than :timestamp to avoid timezone-naive storage bugs. |
🟡 Medium |
| Migration files with duplicate timestamps |
Sequel applies migrations in timestamp order; duplicates cause undefined behaviour. |
🟡 Medium |
Integration
| Related Skill |
When to chain |
| define-relation |
After every migration that adds, removes, or renames columns — update the Relation schema |
| define-entity |
When column changes affect the Entity attribute list |
| create-repository |
When new columns require new query methods or write operations |
| add-table-column (workflow) |
Use the full workflow when adding a column end-to-end: migration → Relation → Entity → Repository → tests |
| hanami-manage-database |
For hanami db create, hanami db rollback, and hanami db seed CLI reference |
Reference Files
- RAILS_MAPPING.md — Side-by-side Rails (ActiveRecord) → Hanami 2.x (Sequel) syntax reference.
- COLUMN_TYPES.md — Sequel generic column types and database mappings.
Examples
Create a table with a primary key and columns
# db/migrate/20240601120000_create_users.rb
Sequel.migration do
change do
# create_table takes a symbol matching the intended table name
create_table(:users) do
# primary_key generates an auto-incrementing integer PK named :id
primary_key :id
# column :name, :type — always specify type explicitly
column :email, :text, null: false
column :first_name, :text, null: false
column :last_name, :text, null: false
column :role, :text, null: false, default: "member"
column :created_at, :timestamptz, null: false
column :updated_at, :timestamptz, null: false
# unique constraint on a single column
unique [:email]
end
end
end
Add a column to an existing table (reversible)
# db/migrate/20240602090000_add_bio_to_users.rb
Sequel.migration do
change do
# alter_table wraps all modifications to an existing table
alter_table(:users) do
# add_column :name, :type, options
add_column :bio, :text, null: true
end
end
end
Drop a column (irreversible — use up/down)
# db/migrate/20240603100000_remove_legacy_token_from_users.rb
Sequel.migration do
up do
alter_table(:users) do
drop_column :legacy_token
end
end
down do
alter_table(:users) do
add_column :legacy_token, :text, null: true
end
end
end
1---2name: write-migration3description: Use when creating or modifying database schemas in Hanami 2.x with Sequel. Covers create_table, add_column, drop_column, alter_table, primary_key, indexes, and migration lifecycle commands.4license: MIT5---67# write-migration89Use this skill when writing or running Sequel database migrations in Hanami 2.x.1011**Core principle:** Sequel migration DSL is provided by `jeremyevans/sequel` — **not** ActiveRecord. Never use ActiveRecord syntax here.1213---1415## Quick Reference1617| Scenario | Approach |18|---|---|19| Create a new table | `create_table(:table_name) { ... }` inside `Sequel.migration { change { ... } }` |20| Add a column to existing table | `alter_table(:table_name) { add_column :col, :type }` |21| Remove a column | `alter_table(:table_name) { drop_column :col }` |22| Add an index | `alter_table(:table_name) { add_index :col }` |23| Rename a column | `alter_table(:table_name) { rename_column :old, :new }` |24| Reversible migration | Use `change { }` block — Sequel infers the inverse automatically |25| Irreversible migration | Use `up { } / down { }` blocks explicitly |26| Generate migration file | `hanami generate migration create_users` |27| Run pending migrations | `hanami db migrate` |28| Roll back last migration | `hanami db rollback` |2930---3132## Core Rules33341. **Generate the migration file** using the Hanami CLI:3536 ```bash37 hanami generate migration <migration_name>38 ```3940 This creates `db/migrate/<timestamp>_<migration_name>.rb`.41422. **Open the generated file** and write the migration body inside the43 `Sequel.migration` block. Prefer `change { }` for reversible operations.44453. **Define the schema change** using the Sequel DSL. Always specify column types explicitly — do not rely on inference.46474. **Run the migration**:4849 ```bash50 hanami db migrate51 ```52535. **Verify** the schema change in the database or via `hanami console`:5455 ```ruby56 Hanami.app["db.rom"].relations[:users].schema.to_h57 ```58596. **Update the ROM Relation** (`define-relation`) to reflect any new60 or removed columns. The Relation schema must stay in sync with the database.61627. **Update Entities and Structs** (`define-entity`) if63 attribute lists change.64658. **Run the test suite** to confirm nothing is broken.6667---6869## Common Mistakes & Red Flags7071| Mistake / Red Flag | Reality | Severity |72|---|---|---|73| ActiveRecord syntax in migration files (e.g. `add_column :users, :email, :string`) | Sequel uses `alter_table(:users) { add_column :email, :text }`. Column types are Sequel generic types (`:text`, `:integer`), not Rails types (`:string`, `:bigint`). | 🔴 Blocker |74| Missing `null: false` on required columns | Sequel does **not** add `NOT NULL` by default. Always declare `null: false` for required columns — omitting it allows NULL values silently. | 🔴 Blocker |75| Using `change { }` for `drop_column` or `rename_column` | These operations are **not** automatically reversible by Sequel. Use explicit `up { } / down { }` blocks. | 🔴 Blocker |76| Schema changes without corresponding ROM Relation updates | After adding or removing columns, the ROM Relation schema must be updated. If using `schema :table, infer: true`, the schema is re-inferred at boot, but explicit attribute declarations will be stale. | 🟠 High |77| Running migrations without checking `HANAMI_ENV` | `hanami db migrate` uses `DATABASE_URL` from the current environment. Always confirm `HANAMI_ENV` is set correctly before running in staging or production. | 🟠 High |78| Using `:timestamp` without timezone | Use `:timestamptz` (PostgreSQL) rather than `:timestamp` to avoid timezone-naive storage bugs. | 🟡 Medium |79| Migration files with duplicate timestamps | Sequel applies migrations in timestamp order; duplicates cause undefined behaviour. | 🟡 Medium |8081---8283## Integration8485| Related Skill | When to chain |86|---|---|87| **define-relation** | After every migration that adds, removes, or renames columns — update the Relation schema |88| **define-entity** | When column changes affect the Entity attribute list |89| **create-repository** | When new columns require new query methods or write operations |90| **add-table-column** (workflow) | Use the full workflow when adding a column end-to-end: migration → Relation → Entity → Repository → tests |91| **hanami-manage-database** | For `hanami db create`, `hanami db rollback`, and `hanami db seed` CLI reference |9293---9495## Reference Files9697- [RAILS_MAPPING.md](RAILS_MAPPING.md) — Side-by-side Rails (ActiveRecord) → Hanami 2.x (Sequel) syntax reference.98- [COLUMN_TYPES.md](COLUMN_TYPES.md) — Sequel generic column types and database mappings.99100---101102## Examples103104### Create a table with a primary key and columns105106```ruby107# db/migrate/20240601120000_create_users.rb108109Sequel.migration do110 change do111 # create_table takes a symbol matching the intended table name112 create_table(:users) do113 # primary_key generates an auto-incrementing integer PK named :id114 primary_key :id115116 # column :name, :type — always specify type explicitly117 column :email, :text, null: false118 column :first_name, :text, null: false119 column :last_name, :text, null: false120 column :role, :text, null: false, default: "member"121 column :created_at, :timestamptz, null: false122 column :updated_at, :timestamptz, null: false123124 # unique constraint on a single column125 unique [:email]126 end127 end128end129```130131### Add a column to an existing table (reversible)132133```ruby134# db/migrate/20240602090000_add_bio_to_users.rb135136Sequel.migration do137 change do138 # alter_table wraps all modifications to an existing table139 alter_table(:users) do140 # add_column :name, :type, options141 add_column :bio, :text, null: true142 end143 end144end145```146147### Drop a column (irreversible — use up/down)148149```ruby150# db/migrate/20240603100000_remove_legacy_token_from_users.rb151152Sequel.migration do153 up do154 alter_table(:users) do155 drop_column :legacy_token156 end157 end158159 down do160 alter_table(:users) do161 add_column :legacy_token, :text, null: true162 end163 end164end165```