Files
nairobi/lib/tasks/migrate_milestones_and_statuses.rake
Javi Martín d3882df437 Fix milestones migration not updating ID sequence
When we insert a record in PostgreSQL and we specify the ID, the
internal ID sequence for that table isn't updated.

In order to keep the original IDs so we didn't break any foreign keys,
we specified the IDs when copying the table, resulting in a table having
its ID sequence with a value of an existing record. When trying to
insert a new record, we got a `PG::UniqueViolation` exception.

Updating the sequence after the data migration might not be the most
elegant solution, but it's easy to do and it's already been tested on a
production environment.
2018-11-30 14:15:21 +01:00

107 lines
4.2 KiB
Ruby

namespace :milestones do
def generate_table_migration_sql(new_table:, old_table:, columns:)
from_cols = ['id', *columns.keys]
to_cols = ['id', *columns.values]
<<~SQL
INSERT INTO #{new_table} (#{to_cols.join(', ')})
SELECT #{from_cols.join(', ')} FROM #{old_table};
SQL
end
def migrate_table!(new_table:, old_table:, columns:)
puts "Migrating data from '#{old_table}' to '#{new_table}'..."
result = ActiveRecord::Base.connection.execute(
generate_table_migration_sql(old_table: old_table,
new_table: new_table,
columns: columns)
)
puts "#{result.cmd_tuples} rows affected"
end
def populate_column!(table:, column:, value:)
puts "Populating column '#{column}' from table '#{table}' with '#{value}'..."
result = ActiveRecord::Base.connection.execute(
"UPDATE #{table} SET #{column} = '#{value}';"
)
puts "#{result.cmd_tuples} rows affected"
end
def count_rows(table)
ActiveRecord::Base.connection.query("SELECT COUNT(*) FROM #{table};")[0][0].to_i
end
desc "Migrate milestones and milestone status data after making the model polymorphic"
task migrate: :environment do
# This script copies all milestone-related data from the old tables to
# the new ones (preserving all primary keys). All 3 of the new tables
# must be empty.
#
# To clear the new tables to test this script:
#
# DELETE FROM milestone_statuses;
# DELETE FROM milestones;
# DELETE FROM milestone_translations;
#
start = Time.now
ActiveRecord::Base.transaction do
migrate_table! old_table: 'budget_investment_statuses',
new_table: 'milestone_statuses',
columns: {'name' => 'name',
'description' => 'description',
'hidden_at' => 'hidden_at',
'created_at' => 'created_at',
'updated_at' => 'updated_at'}
migrate_table! old_table: 'budget_investment_milestones',
new_table: 'milestones',
columns: {'investment_id' => 'milestoneable_id',
'title' => 'title',
'description' => 'description',
'created_at' => 'created_at',
'updated_at' => 'updated_at',
'publication_date' => 'publication_date',
'status_id' => 'status_id'}
populate_column! table: 'milestones',
column: 'milestoneable_type',
value: 'Budget::Investment'
migrate_table! old_table: 'budget_investment_milestone_translations',
new_table: 'milestone_translations',
columns: {'budget_investment_milestone_id' => 'milestone_id',
'locale' => 'locale',
'created_at' => 'created_at',
'updated_at' => 'updated_at',
'title' => 'title',
'description' => 'description'}
Image.where(imageable_type: "Budget::Investment::Milestone").
update_all(imageable_type: "Milestone")
Document.where(documentable_type: "Budget::Investment::Milestone").
update_all(documentable_type: "Milestone")
puts "Verifying that all rows were copied..."
{
"budget_investment_milestones" => "milestones",
"budget_investment_statuses" => "milestone_statuses",
"budget_investment_milestone_translations" => "milestone_translations"
}.each do |original_table, migrated_table|
ActiveRecord::Base.connection.execute(
"select setval('#{migrated_table}_id_seq', (select max(id) from #{migrated_table}));"
)
unless count_rows(original_table) == count_rows(migrated_table)
raise "Number of rows of old and new tables do not match! Rolling back transaction..."
end
end
end
puts "Finished in %.3f seconds" % (Time.now - start)
end
end