Skip to content

Instantly share code, notes, and snippets.

@jstanley0
Last active August 10, 2021 15:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jstanley0/a80181985a099573671eb69785a3d4a8 to your computer and use it in GitHub Desktop.
Save jstanley0/a80181985a099573671eb69785a3d4a8 to your computer and use it in GitHub Desktop.
# 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