unsort_db_schema_columns

Restore the natural (database ordinal) column order in Rails schema.rb dumps.

Why this exists

In rails/rails#53281, Rails 8 changed ActiveRecord::SchemaDumper to sort table columns alphabetically. The intent was to reduce merge conflicts when two developers add migrations to the same table on parallel branches.

The downside is that schema.rb no longer reflects the actual column layout of the database. That breaks several real workflows:

  • db:schema:load for production parity. Since Rails 8 (#52830), a fresh db:migrate loads schema.rb instead of replaying migrations. With sorting on, freshly-loaded databases have a different column order than production databases built incrementally from migrations.
  • Postgres column-alignment padding. Postgres pads columns to alignment boundaries; deliberate column ordering (e.g. 8-byte → 4-byte → variable-length) can meaningfully reduce table size. Alphabetical sorting destroys any layout chosen for this reason.
  • SELECT * and bulk-import pipelines. Tools like pg_dump/mysqldump round-trips, PG::Connection#put_copy_data, and CSV import/export depend on ordinal column position. Mismatched dev/prod column order breaks them.
  • add_column :after/:before. Rails supports positional column options in migrations. With alphabetical sorting in the dump, those options have no effect on schema-loaded databases.
  • Gems that read columns_hash order. The Rails change affects in-memory ordering after schema reload, not just the file (e.g. it broke a label-picking heuristic in Bullet Train).

For full discussion, see PR #55414 (opt-in restoration), PR #56842 (full revert), and the original PR #53281.

Who should use this

You probably want this gem if any of these are true:

  • You use db:schema:load (or Rails 8's db:migrate on fresh DBs) to provision new environments and need it to match production column order.
  • You hand-tune column order in migrations for Postgres alignment-padding reasons.
  • You have CSV import/export, pg_dump round-trip, or put_copy_data pipelines.
  • You use add_column :after / :before and expect it to stick.

You probably don't need it if you're a small team where merge conflicts in schema.rb were the bigger pain than any of the above.

Install

# Gemfile
gem "unsort_db_schema_columns"

That's it. A Railtie auto-applies the patch when ActiveRecord loads.

To regenerate schema.rb in DB-natural order:

bin/rails db:schema:dump

Note: the gem only affects future dumps. The schema.rb already on disk is whatever was last committed. To get a correct natural-order schema.rb, run db:schema:dump against a database whose column order matches production (typically one built by replaying migrations, not loaded from a sorted schema).

How it works

Prepends ActiveRecord::SchemaDumper and overrides #table with a copy of the upstream method that omits the .sort_by(&:name) call introduced in PR #53281. A boot-time warning fires if loaded against an untested ActiveRecord major version, since the copied method body could drift.

Why only columns?

SchemaDumper also sorts tables, indexes, foreign keys, check constraints, and unique constraints. Those sorts have been in Rails for years (some since 2009) and this gem leaves them alone — only column ordering inside a table has functional consequences in the database engine (Postgres alignment padding, SELECT * ordinal position, pg_dump/put_copy_data round-trips, add_column :after/:before). Tables, indexes, and constraints are independent named objects; the order they're created has no effect on engine behavior, so sorting them in schema.rb is pure diff-stability with no downside.

Caveats

  • Coupled to ActiveRecord 8.x's SchemaDumper#table method body. When you upgrade Rails, re-diff against activerecord/lib/active_record/schema_dumper.rb and bump the version constraint here.
  • The merge-conflict problem #53281 was solving is back. That's the intended trade.

License

MIT.