Feature idea: make it easy to extend schema.rb without monkeypatching

Given Rails issues like https://github.com/rails/rails/issues/36921 and projects like https://github.com/SchemaPlus/schema_plus_enums, it seems like there’s a real desire to use Postgres custom types in Rails without having to resort to structure.sql. (structure.sql support has gotten a lot better, but it’s still vulnerable to e.g. nonsense churn caused by folks running different DB versions locally.)

Other Postgres-specific features, such as foreign data wrappers, are near-unusable within Rails when you’re committing your schema – schema.rb ignores them, and structure.sql encodes information that should never be committed, like database passwords.

I don’t think that Rails maintainers should be obligated to maintain schema.rb dumpers for a lot of different, esoteric database-specific features. But right now, if you want to extend schema.rb's functionality, you need to monkeypatch Rails internals; it’s fragile when upgrading.

I’d like there to be a stable, external-facing interface allowing folks to extend schema.rb dumpers with database-specific features, without monkeypatching. I’m happy to take on the work of implementing this, but I wanted to raise this as an issue first so that if the core team isn’t interested in this as a feature no work is wasted.

I recently did kind of an insane thing and wrote a gem that monkeypatches Rails to support PostgreSQL enums: https://github.com/alassek/activerecord-pg_enum

It’s similar to schema_plus_enums but substantially more comprehensive.

One of the things I did was support dumping enum definitions to schema.rb not just for the latest Rails, not just for the supported Rails, but for every minor version since 4.1. So I have some perspective on what this would take.

Adding new things to schema.rb is simple. SchemaDumper#dump calls a handful of methods in order to produce a stream object that gets written to the filesystem.

A simple callback scheme using ActiveSupport Callbacks would be sufficient to make this extensible.

The catch is that anything that gets dumped has to be executable when you load it, and ActiveRecord::Schema.define is just a fancy instance_eval against ActiveRecord::Migration::Current.

In order to support arbitrary extension of schema.rb, that would require creating an entirely new system for defining a user-provided migration class.

Supporting migration commands in schema.rb will make people expect to be able to use them in their own migrations, so this will create additional edge-cases. Like CommandRecorder.

If you want to alter how tables or columns are dumped, now you need to worry about TableDefinition, Table, and prepare_column_options. By the way, none of these things live in the same place and they have a tendency to move around between minor versions.

I think you’re underestimating the amount of things you’d need to touch to make this work.

Perhaps focusing more on making structure.sql a first-class citizen would have better trade-offs. It’s simply farmed out to pg_dump as I recall, so adding a new Ruby system for producing SQL dumps would be a lot easier than adapting the migration system. This would also be pretty trivial to write this as a gem that overrides db:structure:dump.