Last active
August 10, 2021 15:35
-
-
Save jstanley0/a80181985a099573671eb69785a3d4a8 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# this function repairs a blueprint course and its associations when they are broken by a shard split. | |
# since migration ids depend on the shard number, they will all change after a split. fortunately, | |
# MasterContentTag remembers the original migration_id so we can use it to build a mapping from old | |
# to new migration_id, and then go through and replace ids in the blueprint and its associations. | |
# symptoms of a blueprint that is broken by a shard split include: | |
# 1. content that is updated in the blueprint gets duplicated instead of updated in | |
# the associated courses | |
# 2. content that was locked in the blueprint remains locked in associated courses | |
# but does not appear locked in the blueprint (and toggling the lock doesn't appear to work) | |
def repair_master_template!(template_id, dry_run: true) | |
template = MasterCourses::MasterTemplate.find(template_id) | |
template.shard.activate do | |
# find master content tags with a mismatched shard number | |
tags_to_repair = template.master_content_tags.where.not("migration_id LIKE 'mastercourse_#{template.shard.id}_%'").preload(:content).to_a | |
tags_to_repair.reject! { |tag| tag.content.nil? } | |
return unless tags_to_repair.any? | |
# build a mapping from broken migration id to new migration id | |
migration_id_map = {} | |
tags_to_repair.each do |tag| | |
migration_id_map[tag.migration_id] = template.migration_id_for(tag.content) | |
end | |
# repair sync history | |
# (do this first for the sake of idempotence, because once master content tags are repaired, we | |
# lose the information we need to create the mapping) | |
template.master_migrations.each do |mm| | |
next unless bad_export_results?(mm) | |
mm.transaction do | |
# repair sync exceptions | |
mm.migration_results.where.not(results: {}).find_each do |result| | |
if result.results[:skipped].present? | |
result.results[:skipped].map! { |mig_id| migration_id_map[mig_id] || mig_id } | |
result.results_will_change! | |
result.save! | |
end | |
end | |
# repair sync history | |
process_result_hash!(mm.export_results[:selective][:deleted], migration_id_map) | |
process_result_hash!(mm.export_results[:selective][:created], migration_id_map) | |
process_result_hash!(mm.export_results[:selective][:updated], migration_id_map) | |
mm.export_results_will_change! | |
mm.save! | |
raise ActiveRecord::Rollback, "dry run" if dry_run | |
end | |
end | |
# repair master tags and child course information | |
child_subscription_ids = template.child_subscriptions.pluck(:id) | |
child_course_ids = template.child_subscriptions.pluck(:child_course_id) | |
tags_to_repair.each do |tag| | |
old_migration_id = tag.migration_id | |
new_migration_id = migration_id_map[old_migration_id] | |
tag.transaction do | |
# repair child objects | |
child_objects = tag.content_type.constantize.where(context_type: 'Course', context_id: child_course_ids, migration_id: old_migration_id) | |
child_objects.update_all(migration_id: new_migration_id) | |
# repair child tags | |
child_tags = MasterCourses::ChildContentTag.where(child_subscription_id: child_subscription_ids, migration_id: old_migration_id) | |
child_tags.update_all(migration_id: new_migration_id) | |
# repair the master tag | |
tag.migration_id = new_migration_id | |
begin | |
tag.save! | |
rescue | |
tag.destroy | |
end | |
raise ActiveRecord::Rollback, "dry run" if dry_run | |
end | |
end | |
end | |
end | |
def bad_export_results?(master_migration) | |
return false unless master_migration.export_results[:selective].present? | |
mig_ids = master_migration.export_results[:selective][:created].values.flatten | |
mig_ids += master_migration.export_results[:selective][:updated].values.flatten | |
mig_ids += master_migration.export_results[:selective][:deleted].values.flatten | |
mig_ids.any? do |mig_id| | |
mig_id =~ /\Amastercourse_(\d+)_/ && $1.to_i != Shard.current.id | |
end | |
end | |
def process_result_hash!(result_hash, migration_id_map) | |
result_hash.transform_values! do |mig_id_array| | |
if mig_id_array.is_a?(Array) # it might be { "syllabus": true } | |
mig_id_array.map! { |mig_id| migration_id_map[mig_id] || mig_id } | |
end | |
end | |
end | |
def repair_all_master_templates! | |
MasterCourses::MasterTemplate.active.where( | |
id: MasterCourses::MasterContentTag.where("migration_id NOT LIKE 'mastercourse_#{Shard.current.id}_%'").distinct.select(:master_template_id) | |
).pluck(:id).each do |template_id| | |
repair_master_template!(template_id, dry_run: false) | |
end | |
end | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment