From 977d7850dab4160e6712b13f71839de167a080f3 Mon Sep 17 00:00:00 2001 From: John Costa Date: Sat, 24 Oct 2026 21:47:47 -0700 Subject: [PATCH] feat: add Heroku preboot context to strong_migrations destructive-operation messages Closes #2963 --- config/initializers/strong_migrations.rb | 16 +++++++++++ spec/config/strong_migrations_config_spec.rb | 30 ++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 spec/config/strong_migrations_config_spec.rb diff --git a/config/initializers/strong_migrations.rb b/config/initializers/strong_migrations.rb index 0fe1533ee..f64fc1832 100644 --- a/config/initializers/strong_migrations.rb +++ b/config/initializers/strong_migrations.rb @@ -4,3 +4,19 @@ # https://github.com/ankane/strong_migrations StrongMigrations.start_after = 20260101000000 # rubocop:disable Style/NumericLiterals StrongMigrations.target_postgresql_version = 16 + +# With preboot, the release phase runs before old web dynos stop, so old code +# serves against the new schema for ~3 minutes. Say so where the migration +# author will see it, next to the gem's `safety_assured` advice. +preboot_note = <<~TXT + Heroku preboot: this runs in the release phase before old web dynos stop, + so old code serves against the new schema for ~3 minutes after the deploy. + See AGENTS.md ("Migrations and Heroku preboot"). If the migration cannot + be made forward-compatible, disable preboot for this deploy. +TXT + +%i[change_column change_column_with_not_null change_column_default + change_column_null rename_column rename_table rename_schema + rename_enum_value execute].each do |key| + StrongMigrations.error_messages[key] = "#{StrongMigrations.error_messages.fetch(key)}\n\n#{preboot_note}" +end diff --git a/spec/config/strong_migrations_config_spec.rb b/spec/config/strong_migrations_config_spec.rb new file mode 100644 index 000000000..ba61b09dc --- /dev/null +++ b/spec/config/strong_migrations_config_spec.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'StrongMigrations configuration' do # rubocop:disable RSpec/DescribeClass + describe 'destructive-operation messages' do + preboot_keys = %i[change_column change_column_with_not_null change_column_default + change_column_null rename_column rename_table rename_schema + rename_enum_value execute] + + it 'appends the Heroku preboot note after the gem default' do + message = StrongMigrations.error_messages[:rename_column] + + expect(message).to start_with("Renaming a column that's in use will cause errors") + expect(message).to end_with("disable preboot for this deploy.\n") + end + + it 'carries the note exactly once on each destructive-operation message' do + counts = preboot_keys.index_with do |key| + StrongMigrations.error_messages.fetch(key).scan('disable preboot for this deploy').size + end + + expect(counts.values).to all(eq(1)) + end + + it 'leaves messages for other checks unchanged' do + expect(StrongMigrations.error_messages[:add_index]).not_to include('Heroku preboot') + end + end +end