If you worked with a large database you know that you can't just run an
alter table to add a column with a default value, maybe you tried it and it failed -- like me awhile ago 🤕 -- but what if you really need to do that? That's what I'm about to tell you in these four simple steps:
class AddCurrencyToAccounts < ActiveRecord::Migration[5.2] def change add_column :accounts, :currency, :string end end
This is going to run this SQL:
ALTER TABLE "accounts" ADD "currency" character varying
There are tons of ways of accomplishing it and it really depends on how you create your records, but this is good enough for the example:
account.currency ||= "USD"
If you have a large database you also probably already have a way of running code for all your records asynchronously to finish it really fast -- If not let me know -- but if you don't have it, a
.find_each or a
.find_in_batches would probably do the trick:
Account.where(currency: nil).find_each do |account| # .update_attributes doesn't touch updated_at account.update_attributes(currency: "USD") end
Why not just run
Account.where(currency: nil).update_all(currency: "USD") ?
Because this ☝️ is going to lock your table while your database updates all your rows, that's why we need to update individual records so the database locking only happens per row.
class AddDefaultToCurrencyOnAccounts < ActiveRecord::Migration[5.2] def change change_column :accounts, :currency, :string, default: "USD", null: false end end
This is going to run this SQL
ALTER TABLE "accounts" ALTER COLUMN "currency" TYPE character varying, ALTER COLUMN "currency" SET DEFAULT 'USD', ALTER "currency" SET NOT NULL
After that you can remove your code that sets the default value when creating your records from step #2 and it's done 🎉