From c11fecc380d0f41d7057090d301f59d009b10a86 Mon Sep 17 00:00:00 2001 From: Vasili Kachalko Date: Mon, 6 Nov 2023 12:13:42 +0100 Subject: [PATCH] fix migration error --- ...most_recent_for_form_answer_transitions.rb | 2 +- lib/tasks/statesman.rake | 62 +++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 lib/tasks/statesman.rake diff --git a/db/migrate/20181102125923_add_constraints_to_most_recent_for_form_answer_transitions.rb b/db/migrate/20181102125923_add_constraints_to_most_recent_for_form_answer_transitions.rb index 42dc49396..444b4101b 100644 --- a/db/migrate/20181102125923_add_constraints_to_most_recent_for_form_answer_transitions.rb +++ b/db/migrate/20181102125923_add_constraints_to_most_recent_for_form_answer_transitions.rb @@ -2,7 +2,7 @@ class AddConstraintsToMostRecentForFormAnswerTransitions < ActiveRecord::Migrati disable_ddl_transaction! def up - Rake::Task['statesman:backfill_most_recent'].invoke("FormAnswer") + Rake::Task['statesman:backfill_most_recent_fixed'].invoke("FormAnswer") FormAnswerTransition.where(most_recent: nil).update_all(most_recent: false) add_index :form_answer_transitions, [:form_answer_id, :most_recent], unique: true, where: "most_recent", name: "index_form_answer_transitions_parent_most_recent", algorithm: :concurrently rescue "" change_column_null :form_answer_transitions, :most_recent, false diff --git a/lib/tasks/statesman.rake b/lib/tasks/statesman.rake new file mode 100644 index 000000000..5dcde0713 --- /dev/null +++ b/lib/tasks/statesman.rake @@ -0,0 +1,62 @@ +namespace :statesman do + desc "Set most_recent to false for old transitions and to true for the " \ + "latest one. Safe to re-run" + task :backfill_most_recent_fixed, [:parent_model_name] => :environment do |_, args| + parent_model_name = args.parent_model_name + abort("Parent model name must be specified") unless parent_model_name + + parent_class = parent_model_name.constantize + transition_class = parent_class.transition_class + parent_fk = "#{parent_model_name.demodulize.underscore}_id" + updated_at = if ActiveRecord.default_timezone == :utc + Time.now.utc + else + Time.now + end + + total_models = parent_class.count + done_models = 0 + batch_size = 500 + + parent_class.find_in_batches(batch_size: batch_size) do |models| + ActiveRecord::Base.transaction(requires_new: true) do + if Statesman::Adapters::ActiveRecord.database_supports_partial_indexes? + # Set all transitions' most_recent to FALSE + transition_class.where(parent_fk => models.map(&:id)). + update_all(most_recent: false, updated_at: updated_at) + else + transition_class.where(parent_fk => models.map(&:id)). + update_all(most_recent: nil, updated_at: updated_at) + end + + # Set current transition's most_recent to TRUE + initial_t = transition_class.arel_table + subsequent_t = initial_t.alias + + later_row_for_same_parent = initial_t[parent_fk]. + eq(subsequent_t[parent_fk]). + and(initial_t[:sort_key]. + lt(subsequent_t[:sort_key])) + + no_later_row = subsequent_t[:id].eq(nil) + in_current_parent_batch = initial_t[parent_fk].in(models.map(&:id)) + + latest_ids_query = initial_t.join(subsequent_t, Arel::Nodes::OuterJoin). + on(later_row_for_same_parent). + where(no_later_row.and(in_current_parent_batch)). + project(initial_t[:id]).to_sql + + latest_ids = transition_class.find_by_sql(latest_ids_query). + to_a.collect(&:id) + + transition_class.where(id: latest_ids). + update_all(most_recent: true, updated_at: updated_at) + end + + done_models += batch_size + puts "Updated #{transition_class.name.pluralize} for " \ + "#{[done_models, total_models].min}/#{total_models} " \ + "#{parent_model_name.pluralize}" + end + end +end