class ZeroDowntimeMigrations::Validation::AddColumn
Public Instance Methods
validate!()
click to toggle source
# File lib/zero_downtime_migrations/validation/add_column.rb 4 def validate! 5 return if options[:default].nil? # only nil is safe 6 error!(message) 7 end
Private Instance Methods
column()
click to toggle source
# File lib/zero_downtime_migrations/validation/add_column.rb 71 def column 72 args[1] 73 end
column_default()
click to toggle source
# File lib/zero_downtime_migrations/validation/add_column.rb 75 def column_default 76 options[:default].inspect 77 end
column_title()
click to toggle source
# File lib/zero_downtime_migrations/validation/add_column.rb 79 def column_title 80 column.to_s.camelize 81 end
column_type()
click to toggle source
# File lib/zero_downtime_migrations/validation/add_column.rb 83 def column_type 84 args[2] 85 end
message()
click to toggle source
# File lib/zero_downtime_migrations/validation/add_column.rb 11 def message 12 <<-MESSAGE.strip_heredoc 13 Adding a column with a default is unsafe! 14 15 This can take a long time with significant database 16 size or traffic and lock your table! 17 18 First let’s add the column without a default. When we add 19 a column with a default it has to lock the table while it 20 performs an UPDATE for ALL rows to set this new default. 21 22 class Add#{column_title}To#{table_title} < ActiveRecord::Migration 23 def change 24 add_column :#{table}, :#{column}, :#{column_type} 25 end 26 end 27 28 Then we’ll set the new column default in a separate migration. 29 Note that this does not update any existing data! This only 30 sets the default for newly inserted rows going forward. 31 32 class AddDefault#{column_title}To#{table_title} < ActiveRecord::Migration 33 def change 34 change_column_default :#{table}, :#{column}, #{column_default} 35 end 36 end 37 38 Finally we’ll backport the default value for existing data in 39 batches. This should be done in its own migration as well. 40 Updating in batches allows us to lock 1000 rows at a time 41 (or whatever batch size we prefer). 42 43 class BackportDefault#{column_title}To#{table_title} < ActiveRecord::Migration 44 def change 45 #{table_model}.select(:id).find_in_batches.with_index do |records, index| 46 puts "Processing batch \#{index + 1}\\r" 47 #{table_model}.where(id: records).update_all(#{column}: #{column_default}) 48 end 49 end 50 end 51 52 Note that in some cases it may not even be necessary to backport a default value. 53 54 class #{table_model} < ActiveRecord::Base 55 def #{column} 56 self["#{column}"] ||= #{column_default} 57 end 58 end 59 60 If you're 100% positive that this migration is already safe, then wrap the 61 call to `add_column` in a `safety_assured` block. 62 63 class Add#{column_title}To#{table_title} < ActiveRecord::Migration 64 def change 65 safety_assured { add_column :#{table}, :#{column}, :#{column_type}, default: #{column_default} } 66 end 67 end 68 MESSAGE 69 end
table()
click to toggle source
# File lib/zero_downtime_migrations/validation/add_column.rb 87 def table 88 args[0] 89 end
table_model()
click to toggle source
# File lib/zero_downtime_migrations/validation/add_column.rb 91 def table_model 92 table_title.singularize 93 end
table_title()
click to toggle source
# File lib/zero_downtime_migrations/validation/add_column.rb 95 def table_title 96 table.to_s.camelize 97 end