unsort_db_schema_columns 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +62 -0
- data/lib/unsort_db_schema_columns/railtie.rb +11 -0
- data/lib/unsort_db_schema_columns/schema_dumper_patch.rb +104 -0
- data/lib/unsort_db_schema_columns/version.rb +3 -0
- data/lib/unsort_db_schema_columns.rb +3 -0
- metadata +96 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 2d5ae8c8bb1699839dfbbd549d0c82241a2d5f8625227057b8a541f04c8485cb
|
|
4
|
+
data.tar.gz: 153946257a01842d4a7957d9e36ac0ce0fcd5dc9394de43f25ec941921730dad
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 4166346b74a7139b5c00e84f12e5e3ef5fa3fce72e8f7c7b918063690e6c23186bd814db0408b85f9ecbb1f6b6d561fe88d4ed29fb3072b2c9970200aa856215
|
|
7
|
+
data.tar.gz: '096ad720d60c32088ea1a4f634455cbdf9d1c67d3782197fd034302b078fac79fab14b70a15c873959c12894ae546d11d444e33270f271aa6bdd0f7600d672af'
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jake Moffatt
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# unsort_db_schema_columns
|
|
2
|
+
|
|
3
|
+
Restore the natural (database ordinal) column order in Rails `schema.rb` dumps.
|
|
4
|
+
|
|
5
|
+
## Why this exists
|
|
6
|
+
|
|
7
|
+
In [rails/rails#53281](https://github.com/rails/rails/pull/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.
|
|
8
|
+
|
|
9
|
+
The downside is that `schema.rb` no longer reflects the actual column layout of the database. That breaks several real workflows:
|
|
10
|
+
|
|
11
|
+
- **`db:schema:load` for production parity.** Since Rails 8 ([#52830](https://github.com/rails/rails/pull/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.
|
|
12
|
+
- **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.
|
|
13
|
+
- **`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.
|
|
14
|
+
- **`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.
|
|
15
|
+
- **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).
|
|
16
|
+
|
|
17
|
+
For full discussion, see [PR #55414](https://github.com/rails/rails/pull/55414) (opt-in restoration), [PR #56842](https://github.com/rails/rails/pull/56842) (full revert), and the original [PR #53281](https://github.com/rails/rails/pull/53281).
|
|
18
|
+
|
|
19
|
+
## Who should use this
|
|
20
|
+
|
|
21
|
+
You probably want this gem if any of these are true:
|
|
22
|
+
|
|
23
|
+
- 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.
|
|
24
|
+
- You hand-tune column order in migrations for Postgres alignment-padding reasons.
|
|
25
|
+
- You have CSV import/export, `pg_dump` round-trip, or `put_copy_data` pipelines.
|
|
26
|
+
- You use `add_column :after` / `:before` and expect it to stick.
|
|
27
|
+
|
|
28
|
+
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.
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
# Gemfile
|
|
34
|
+
gem "unsort_db_schema_columns"
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
That's it. A Railtie auto-applies the patch when ActiveRecord loads.
|
|
38
|
+
|
|
39
|
+
To regenerate `schema.rb` in DB-natural order:
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
bin/rails db:schema:dump
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
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).
|
|
46
|
+
|
|
47
|
+
## How it works
|
|
48
|
+
|
|
49
|
+
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.
|
|
50
|
+
|
|
51
|
+
## Why only columns?
|
|
52
|
+
|
|
53
|
+
`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.
|
|
54
|
+
|
|
55
|
+
## Caveats
|
|
56
|
+
|
|
57
|
+
- Coupled to ActiveRecord 8.x's `SchemaDumper#table` method body. When you upgrade Rails, re-diff against [`activerecord/lib/active_record/schema_dumper.rb`](https://github.com/rails/rails/blob/main/activerecord/lib/active_record/schema_dumper.rb) and bump the version constraint here.
|
|
58
|
+
- The merge-conflict problem #53281 was solving is back. That's the intended trade.
|
|
59
|
+
|
|
60
|
+
## License
|
|
61
|
+
|
|
62
|
+
MIT.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
require "rails/railtie"
|
|
2
|
+
|
|
3
|
+
module UnsortDbSchemaColumns
|
|
4
|
+
class Railtie < ::Rails::Railtie
|
|
5
|
+
initializer "unsort_db_schema_columns.patch_schema_dumper" do
|
|
6
|
+
ActiveSupport.on_load(:active_record) do
|
|
7
|
+
require "unsort_db_schema_columns/schema_dumper_patch"
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
require "active_record"
|
|
2
|
+
require "active_record/schema_dumper"
|
|
3
|
+
|
|
4
|
+
module UnsortDbSchemaColumns
|
|
5
|
+
# Restores the pre-#53281 behavior: dump columns in the order the database
|
|
6
|
+
# returns them (ordinal position), instead of alphabetically by name.
|
|
7
|
+
#
|
|
8
|
+
# We override the entire #table method because Rails sorts inline:
|
|
9
|
+
# columns.sort_by(&:name).each do |column|
|
|
10
|
+
# The body below is copied from
|
|
11
|
+
# activerecord/lib/active_record/schema_dumper.rb at the Rails 8.x main
|
|
12
|
+
# branch, with that single `.sort_by(&:name)` removed. Verified against
|
|
13
|
+
# Rails 8.0.x. If you upgrade Rails and this gem hasn't, watch for an
|
|
14
|
+
# UnexpectedRailsVersion warning at boot.
|
|
15
|
+
module SchemaDumperPatch
|
|
16
|
+
def table(table, stream)
|
|
17
|
+
columns = @connection.columns(table)
|
|
18
|
+
begin
|
|
19
|
+
self.table_name = table
|
|
20
|
+
|
|
21
|
+
tbl = StringIO.new
|
|
22
|
+
|
|
23
|
+
# first dump primary key column
|
|
24
|
+
if @connection.respond_to?(:primary_keys)
|
|
25
|
+
pk = @connection.primary_keys(table)
|
|
26
|
+
pk = pk.first unless pk.size > 1
|
|
27
|
+
else
|
|
28
|
+
pk = @connection.primary_key(table)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
tbl.print " create_table #{remove_prefix_and_suffix(table).inspect}"
|
|
32
|
+
|
|
33
|
+
case pk
|
|
34
|
+
when String
|
|
35
|
+
tbl.print ", primary_key: #{pk.inspect}" unless pk == "id"
|
|
36
|
+
pkcol = columns.detect { |c| c.name == pk }
|
|
37
|
+
if pkcol
|
|
38
|
+
pkcolspec = column_spec_for_primary_key(pkcol)
|
|
39
|
+
if pkcolspec.present?
|
|
40
|
+
if pkcolspec != pkcolspec.slice(:id, :default)
|
|
41
|
+
pkcolspec = { id: { type: pkcolspec.delete(:id), **pkcolspec }.compact }
|
|
42
|
+
end
|
|
43
|
+
tbl.print ", #{format_colspec(pkcolspec)}"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
when Array
|
|
47
|
+
tbl.print ", primary_key: #{pk.inspect}"
|
|
48
|
+
else
|
|
49
|
+
tbl.print ", id: false"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
table_options = @connection.table_options(table)
|
|
53
|
+
if table_options.present?
|
|
54
|
+
tbl.print ", #{format_options(table_options)}"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
tbl.puts ", force: :cascade do |t|"
|
|
58
|
+
|
|
59
|
+
# then dump all non-primary key columns -- in NATURAL DB order, not sorted
|
|
60
|
+
columns.each do |column|
|
|
61
|
+
raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" unless @connection.valid_type?(column.type)
|
|
62
|
+
next if column.name == pk
|
|
63
|
+
|
|
64
|
+
type, colspec = column_spec(column)
|
|
65
|
+
if type.is_a?(Symbol)
|
|
66
|
+
tbl.print " t.#{type} #{column.name.inspect}"
|
|
67
|
+
else
|
|
68
|
+
tbl.print " t.column #{column.name.inspect}, #{type.inspect}"
|
|
69
|
+
end
|
|
70
|
+
tbl.print ", #{format_colspec(colspec)}" if colspec.present?
|
|
71
|
+
tbl.puts
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
indexes_in_create(table, tbl)
|
|
75
|
+
remaining = check_constraints_in_create(table, tbl) if @connection.supports_check_constraints?
|
|
76
|
+
exclusion_constraints_in_create(table, tbl) if @connection.supports_exclusion_constraints?
|
|
77
|
+
unique_constraints_in_create(table, tbl) if @connection.supports_unique_constraints?
|
|
78
|
+
|
|
79
|
+
tbl.puts " end"
|
|
80
|
+
|
|
81
|
+
if remaining
|
|
82
|
+
tbl.puts
|
|
83
|
+
tbl.print remaining.string
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
stream.print tbl.string
|
|
87
|
+
rescue => e
|
|
88
|
+
stream.puts "# Could not dump table #{table.inspect} because of following #{e.class}"
|
|
89
|
+
stream.puts "# #{e.message}"
|
|
90
|
+
stream.puts
|
|
91
|
+
ensure
|
|
92
|
+
self.table_name = nil
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
ActiveRecord::SchemaDumper.prepend(UnsortDbSchemaColumns::SchemaDumperPatch)
|
|
99
|
+
|
|
100
|
+
unless ActiveRecord::VERSION::MAJOR == 8
|
|
101
|
+
warn "[unsort_db_schema_columns] Tested against ActiveRecord 8.x; running on " \
|
|
102
|
+
"#{ActiveRecord::VERSION::STRING}. Verify schema dumps look right; " \
|
|
103
|
+
"the upstream SchemaDumper#table method may have drifted."
|
|
104
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: unsort_db_schema_columns
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Jake Moffatt
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 2026-05-14 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: activerecord
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '8.0'
|
|
19
|
+
- - "<"
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '9.0'
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
requirements:
|
|
26
|
+
- - ">="
|
|
27
|
+
- !ruby/object:Gem::Version
|
|
28
|
+
version: '8.0'
|
|
29
|
+
- - "<"
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: '9.0'
|
|
32
|
+
- !ruby/object:Gem::Dependency
|
|
33
|
+
name: railties
|
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
|
35
|
+
requirements:
|
|
36
|
+
- - ">="
|
|
37
|
+
- !ruby/object:Gem::Version
|
|
38
|
+
version: '8.0'
|
|
39
|
+
- - "<"
|
|
40
|
+
- !ruby/object:Gem::Version
|
|
41
|
+
version: '9.0'
|
|
42
|
+
type: :runtime
|
|
43
|
+
prerelease: false
|
|
44
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
45
|
+
requirements:
|
|
46
|
+
- - ">="
|
|
47
|
+
- !ruby/object:Gem::Version
|
|
48
|
+
version: '8.0'
|
|
49
|
+
- - "<"
|
|
50
|
+
- !ruby/object:Gem::Version
|
|
51
|
+
version: '9.0'
|
|
52
|
+
description: |
|
|
53
|
+
Rails 8 (PR #53281) sorts schema.rb columns alphabetically. That breaks
|
|
54
|
+
db:schema:load parity with production, Postgres column-alignment padding,
|
|
55
|
+
add_column :after/:before, and bulk-import pipelines that depend on
|
|
56
|
+
SELECT * column order. This gem prepends ActiveRecord::SchemaDumper to
|
|
57
|
+
dump columns in the order the database actually stores them.
|
|
58
|
+
email:
|
|
59
|
+
- jake.moffatt@gmail.com
|
|
60
|
+
executables: []
|
|
61
|
+
extensions: []
|
|
62
|
+
extra_rdoc_files: []
|
|
63
|
+
files:
|
|
64
|
+
- LICENSE.txt
|
|
65
|
+
- README.md
|
|
66
|
+
- lib/unsort_db_schema_columns.rb
|
|
67
|
+
- lib/unsort_db_schema_columns/railtie.rb
|
|
68
|
+
- lib/unsort_db_schema_columns/schema_dumper_patch.rb
|
|
69
|
+
- lib/unsort_db_schema_columns/version.rb
|
|
70
|
+
homepage: https://github.com/jakeonrails/unsort-db-schema-columns
|
|
71
|
+
licenses:
|
|
72
|
+
- MIT
|
|
73
|
+
metadata:
|
|
74
|
+
homepage_uri: https://github.com/jakeonrails/unsort-db-schema-columns
|
|
75
|
+
source_code_uri: https://github.com/jakeonrails/unsort-db-schema-columns
|
|
76
|
+
bug_tracker_uri: https://github.com/jakeonrails/unsort-db-schema-columns/issues
|
|
77
|
+
changelog_uri: https://github.com/jakeonrails/unsort-db-schema-columns/blob/main/README.md
|
|
78
|
+
rubygems_mfa_required: 'true'
|
|
79
|
+
rdoc_options: []
|
|
80
|
+
require_paths:
|
|
81
|
+
- lib
|
|
82
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
83
|
+
requirements:
|
|
84
|
+
- - ">="
|
|
85
|
+
- !ruby/object:Gem::Version
|
|
86
|
+
version: '3.1'
|
|
87
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
88
|
+
requirements:
|
|
89
|
+
- - ">="
|
|
90
|
+
- !ruby/object:Gem::Version
|
|
91
|
+
version: '0'
|
|
92
|
+
requirements: []
|
|
93
|
+
rubygems_version: 3.6.2
|
|
94
|
+
specification_version: 4
|
|
95
|
+
summary: Restore natural (database ordinal) column order in Rails schema.rb dumps.
|
|
96
|
+
test_files: []
|