AfterMigrate
Automatically run database maintenance after Rails migrations
after_migrate detects tables touched during rails db:migrate (or related tasks) and runs the appropriate optimizer commands:
- PostgreSQL β
ANALYZE(affected tables or all) + optionalVACUUM - SQLite β
PRAGMA optimize(orVACUUM+ANALYZE) - MySQL β
ANALYZE TABLE
Stale statistics and fragmentation after schema changes silently hurt query performance.
after_migrate fixes it - automatically and precisely.

Because every migration deserves a cleanup.
β¨ Features
- Smart detection of affected tables (CREATE/ALTER/INSERT/UPDATE/DELETE/etc.)
- Zero false positives - ignores views, columns, system tables, and complex joins
- Configurable via environment variables or initializer block
- Supports
db:migrate,db:rollback,db:migrate:redo - No monkey-patching of ActiveRecord core classes
- Works in development, test, CI, and production
- Rails 7.0+ / Ruby 3.2+ only
π¦ Installation
Add to your Gemfile:
gem 'after_migrate'
Then run:
bundle install
π Usage
The gem activates automatically. No code required for default behavior.
Default behavior (recommended)
Out of the box, it runs ANALYZE on only the tables touched during the migration (PostgreSQL default).
Configuration
Create config/initializers/after_migrate.rb:
enabledis auto-detected -- do not set it here.
AFTER_MIGRATE_ENABLEDresult truealways on falsealways off (explicit opt-out wins) unset on only while running db:migrate,db:rollback,apartment:migrate,db:schema:load,after_migrate:*It must be resolved from the environment, because Rails runs railtie initializers before
config/initializers/*.rb-- by the time this block executes, the gem has already decided whether to subscribe. Assigningconfig.enabledhere still affectsAfterMigrate.run!, but cannot switch collection on or off, and setting it to a fixed value will override auto-detection forrun!. Leave it alone unless you mean it.
AfterMigrate.configure do |config|
# Log whatβs happening
config.verbose = true
# Run VACUUM on affected tables (PostgreSQL only)
config.vacuum = false
# Choose ANALYZE strategy
# "only_affected_tables" - default, precise
# "all_tables" - full database analyze
# "none" - skip ANALYZE entirely
config.analyze = "only_affected_tables"
# Enhance rake tasks (runs maintenance after db:migrate etc.)
# Set to false in test env if needed
config.rake_tasks_enhanced = true
end
Store backends
The collected table names are kept in a store. Choose one with config.store:
:memory(default) β a process-localConcurrent::Map. Fine for a single-process migration.:fileβ JSON file, shared across rake invocations in the same run.:redisβ shared across processes; use this for parallel / multi-tenant migrations.
config.store = :redis
[!IMPORTANT] Redis: always pass a pooled or memoized client β never a proc that builds a new connection per call.
config.redisis read on every collected SQL statement. If it returns a freshRedis.neweach time (e.g.config.redis = -> { Redis.new(...) }), every statement opens a new, unpooled connection. Under parallel or multi-tenant migrations this exhausts the Redis server connection limit (Connection refused/max number of clients reached).Recommended β a thread-safe
ConnectionPool(the store usespool.with):config.store = :redis config.redis = ConnectionPool.new(size: 2, timeout: 5) { Redis.new(url: ENV["REDIS_URL"]) }A memoized single client also works for sequential migrations:
config.redis = -> { @after_migrate_redis ||= Redis.new(url: ENV["REDIS_URL"]) }
π’ Releasing
Use the guarded helper to make sure git is pushed before RubyGems publish:
bin/release
What it does:
- requires a clean git worktree
- ensures you are on the default branch (
origin/HEADfallback) - checks branch sync status vs
origin - runs
bundle exec rspecandbundle exec rubocop - pushes the branch first
- runs
bundle exec rake release(build/tag/push/publish)
Optional:
bin/release --skip-checks
π€ Contributing
Bug reports, feature requests, and pull requests are very welcome!
https://github.com/moskvin/after_migrate
π License
This project is available under the MIT License.