Yeah, I agree that this is best attacked by documentation rather than coding it into Rails or using strong_migrations. Unfortunately even for that simple example, the best way to add columns is actually completely database-specific and version-specific.
For example, the below advice is the right thing to do on current versions of postgresql, but it is exactly the wrong thing to do on mysql and mariadb - current versions of those will add columns with defaults without blocking other transactions (they’ll rewrite the table, but they’ll buffer concurrent DML), whereas a separate update statement would block other transactions. (Older versions would block concurrent transactions, but doing an ADD COLUMN + an UPDATE is even worse as it would mean rewriting the table rows twice, doubling the blocking.) So it is not good advice for any version of mysql/mariadb (IMHO strong_migrations is only correct for postgresql).
It’s also a moving target. For example, the next version of postgresql is able to add columns with defaults without blocking as well (see https://www.depesz.com/2018/04/04/waiting-for-postgresql-11-fast-alter-table-add-column-with-a-non-null-default/).
Personally I don’t think there is any point baking this knowledge into the migration system; it’ll fall out of date too fast. All the database vendors are actively working on making the basic DDL operations safer and O(1); the remaining cases that you need to look out for are IMHO too dependent on your app and your deployment strategy.
This is because aside from the impact of the DDL statement itself, ignoring the ADD COLUMN case which is the simplest one, most of the problems are to do with explicit references plus the cached column lists in instances of the app already running.
If you’re using a flash reload strategy, the overlap in instances with the old and new column lists could be <10s; if you’re using a rolling deploy, it could be minutes; if you’re using canaries it could be hours. So depending on your strategy you might accept the risk, or you might need to do multi-stage releases.
So Xavier’s right, what is “safe” depends on your app and needs to be thought through at a higher level than the impact of running individual migration statements.
The ignored column mechanism Xavier mentioned can solve some of the problems (see also mariadb’s hidden column feature, and even the virtual column feature which can help with renames). The other half depends on the code that you have written - if your queries specifically reference a column that you want to rename or remove, you are going to have to do a multi-stage release if you don’t want to break it during your deploy window.
+1 to popularising the idea of post-deployment migrations though. We do this at our company (using a small patch to the migration code - I’ll post it if anyone wants it) and it in practice allows us to cover a lot of the major use cases such as adding a new column and starting to populate it in a new release + using an after-release migration to back-populate the data. (You can do the same thing with more individual merge-and-releases, but it’s more work.)
It would be a bit awkward to add to Rails core though, because it requires explicit support from your deployment tools. Some deployment techniques don’t even know for sure when the instances running the old versions of the app have gone away, so they can’t support this pattern.