Tip
Mighost is the recovery engine behind the ghost-migration features of
railbow, which wraps it in rich, colorful
db:migrate:status output. If you want beautiful Rails CLI output and ghost recovery
in one step, you probably want to install railbow directly — it detects mighost and
uses it automatically.
The Problem
When working with Rails migrations across git branches:
- You run migrations on a feature branch
- Switch back to
main - Run
db:migrate- Rails dumps schema including changes from the other branch - You can't rollback because the migration files don't exist in current branch
Usual workaround: Switch to the other branch, rollback, switch back. Tedious.
The Solution
Mighost automatically captures migration file contents when they run, enabling rollback even when the original file is not present. It uses a 3-tier recovery strategy:
- Stored snapshots — captured at migration time into a local SQLite file
- Git history — searches across all branches
- Worktree recovery — finds files in other git worktrees
Installation
Add to your Gemfile:
gem 'mighost', group: [:development, :test]Then run:
bundle install
rails generate mighost:installAdd .mighost.sqlite3 to your .gitignore.
Requires Ruby >= 3.1 and Rails 6.1–8.1.
Usage
Check for orphaned migrations
rails mighost:statusRollback orphaned migrations
# Interactive (with confirmation)
rails mighost:rollback
# Force (no confirmation)
FORCE=1 rails mighost:rollback
# Specific version
rails mighost:rollback VERSION=20240115123456Scan existing migrations & git history
If you install Mighost on an existing project:
rails mighost:scanAll commands
| Command | Description | Options |
|---|---|---|
mighost:status |
Show orphaned migrations |
DEBUG=1, SHOW_DISMISSED=1
|
mighost:rollback |
Rollback orphaned migrations |
VERSION=x, FORCE=1
|
mighost:scan |
Capture migrations + search git | RESCAN=1 |
mighost:list (ls) |
List stored migration records | LIMIT=N |
mighost:dismiss |
Hide migrations from status |
VERSION=x, VERSION=a..b, OLD=1
|
mighost:undismiss |
Restore dismissed migrations | VERSION=x |
mighost:dismissed |
List dismissed migrations | |
mighost:cleanup |
Remove stale records | FORCE=1 |
mighost:help |
Show command reference |
Version ranges
VERSION=20240115123456 # Single version
VERSION=20240110..20240120 # Inclusive range
VERSION=20240110.. # From version onwards
VERSION=..20240120 # Up to versionConfiguration
# config/initializers/mighost.rb
Mighost.configure do |config|
# Path to SQLite file (relative to Rails.root)
config.storage_path = ".mighost.sqlite3"
# Auto-capture on migrate
config.auto_capture = true
# Store git metadata
config.store_git_metadata = true
# Enable/disable (disabled in production by default)
config.enabled = !Rails.env.production?
# Hide superseded orphans from status/detection (off by default —
# supersession is a name-suffix heuristic, and silently dropping rows
# could mask genuine schema drift)
config.hide_superseded = false
endSuperseded migrations
Some ghosts aren't lost — they were re-timestamped. When a migration ships with a bad
timestamp and gets renamed to a new version, restoring from a production dump leaves the old
version row in schema_migrations forever, with no file anywhere.
Mighost detects this: a ghost whose migration name suffix is owned by another applied
version with a file on disk is classified as superseded and listed in its own calmer
Superseded (safe to forget) section of mighost:status, showing the superseding version
instead of the alarming ghost treatment. OrphanedMigration#superseded_by /
#superseded? expose the classification programmatically.
Honest branch attribution
When Mighost recovers a ghost from git history, the reported branch is one whose tip still
has the file — preferring the current branch, then the default branch, then local over
remote branches (remote-only matches keep their origin/ prefix). If the file is gone from
every tip, Mighost reports no branch at all and instead records where it went:
deleted_in_sha, deleted_at, deleted_by — rendered as deleted in:<sha> in status
output. No more pointing you at an unrelated branch that merely contains the commit.
Rich Output with Railbow
By default, Mighost outputs plain, uncolored text. For rich, colorized output with tables, emojis, and git enrichment, use the railbow gem:
gem 'railbow'Railbow automatically detects Mighost and enhances its output.
Public API
Mighost exposes a stable API for programmatic access:
# Detect orphaned migrations
Mighost::API.orphaned_migrations # => [OrphanedMigration, ...]
# Find snapshots
Mighost::API.find_snapshot("20240115123456") # => Snapshot or nil
Mighost::API.find_or_recover_snapshot("20240115") # => Snapshot (with git/worktree fallback)
# Scan and recover
Mighost::API.scan!(rescan: false) # => {captured:, git_recovered:, ...}
# Rollback
Mighost::API.rollback_version!("20240115123456") # raises on failure
# Supersession check (read-only)
Mighost::API.superseded_by("20240115123456") # => superseding version or nilUI Adapter
Mighost uses a pluggable UI adapter for output. Replace it to customize formatting:
# In your railtie or initializer:
Mighost.ui = YourCustomUI.newThe adapter must implement: render_orphans, render_records, render_recoverable_list, render_scan_details, render_scan_results, render_dismiss_preview, render_dismiss_remaining, render_dismissed_list, confirm?, confirm_each?, confirm_dismiss?, success, error, warning, info, hint. See Mighost::PlainUI for the reference implementation.
How It Works
-
Capture: When you run
db:migrate, Mighost hooks intoActiveRecord::Migratorand stores the migration file content along with git metadata (branch, SHA) in a local SQLite file. -
Detect:
mighost:statuscomparesschema_migrationstable with actual migration files to find orphans. -
Recover: For missing files, Mighost searches stored snapshots, git history (all branches), and other git worktrees.
-
Rollback: Writes recovered content to a temp file, loads it, calls the
downmethod, then removes the version fromschema_migrations.
Limitations
-
Irreversible migrations: If the migration raises
ActiveRecord::IrreversibleMigration, Mighost can't help. - Model dependencies: If your migration references models/classes that don't exist in the current branch, the rollback may fail.
- Manual SQL changes: If someone manually modified the database, rollback might not work correctly.
- Trust: Recovered migration code (from snapshots, git history, or worktrees) is executed with the same trust as your own migrations — it is code from your repository, run by you.
Contributing
Bug reports and pull requests are welcome on GitHub. Run bundle exec rake (specs + standardrb) before submitting.
License
MIT License. See LICENSE.