ruby-on-railsdatabase-migration

Rails migration bulk updates for change_column_default


I created a migration that does two default value updates. It looks something like this:

class ChangeTableNameDefaultValues < ActiveRecord::Migration[7.2]
  def change
    change_column_default(:table_name, :column_1, from: 'old_default_value', to: 'new_default_value')
    change_column_default(:table_name, :column_2, from: 'old_default_value', to: 'new_default_value')
  end
end

My linter suggested I wrap this with a change_table bulk: true statement so I tried it like this:

class ChangeTableNameDefaultValues < ActiveRecord::Migration[7.2]
  def change
    change_table :table_name, bulk: true do |t|
      # Notice here that I don't use the t variable being passed
      # to the block. I'm wondering if I need to call a method on t?
      change_column_default(:table_name, :column_1, from: 'old_default_value', to: 'new_default_value')
      change_column_default(:table_name, :column_2, from: 'old_default_value', to: 'new_default_value')
    end
  end
end

However, after running the migration, I got the following output:

Migrating to ChangeTableNameDefaultValues (20250221054551)
== 20250221054551 ChangeTableNameDefaultValues: migrating ======================
-- change_table(:table_name, {:bulk=>true})
-- change_column_default(:table_name, :column_1, {:from=>"old_default_value", :to=>"new_default_value"})
  TRANSACTION (0.0ms)  BEGIN
   (2.4ms)  ALTER TABLE "table_name" ALTER COLUMN "column_1" SET DEFAULT 'new_default_value'
   -> 0.0046s
-- change_column_default(:table_name, :column_2, {:from=>"old_default_value", :to=>"new_default_value"})
   (0.5ms)  ALTER TABLE "table_name" ALTER COLUMN "column_2" SET DEFAULT 'new_default_value'
   -> 0.0017s
   -> 0.0072s
== 20250221054551 ChangeTableNameDefaultValues: migrated (0.0072s) =============

So it didn't take advantage of the bulk table updating. I'm wondering if this is possible with changing default values of columns?


Solution

  • The issue is that you need to use change_default instead of change_column_default.

    change_default uses the context of the table in the change_table block, while change_column_default takes the table as an explicit argument and generates its own statements.

    So something like...

      def change
        change_table :table_name, bulk: true do |t|
          # No table needed, already available in context
          t.change_default(:column_1, from: 'old_default_value', to: 'new_default_value')
          t.change_default(:column_2, from: 'old_default_value', to: 'new_default_value')
        end
      end
    

    Note that this is NOT reversible.

    The list of methods available in change_table are here.