From d2183b1250be0259dc35b4b98026bc9a7e56804e Mon Sep 17 00:00:00 2001 From: Kevin Cooper Date: Wed, 11 Oct 2023 22:52:59 -0400 Subject: [PATCH] Add form_rank_helpers tasks --- app/models/form_item.rb | 19 --------- lib/tasks/form_rank_helpers.rake | 69 ++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 19 deletions(-) create mode 100644 lib/tasks/form_rank_helpers.rake diff --git a/app/models/form_item.rb b/app/models/form_item.rb index fc7f5d5d3a..efcb1300fd 100644 --- a/app/models/form_item.rb +++ b/app/models/form_item.rb @@ -105,25 +105,6 @@ class FormItem < ApplicationRecord accepts_nested_attributes_for :skip_rules, allow_destroy: true accepts_nested_attributes_for :constraints, allow_destroy: true - def self.rank_gaps? - SqlRunner.instance.run(" - SELECT id FROM form_items fi1 - WHERE fi1.rank > 1 AND NOT EXISTS ( - SELECT id FROM form_items fi2 - WHERE fi2.ancestry = fi1.ancestry AND fi2.rank = fi1.rank - 1) - ").any? - end - - def self.duplicate_ranks? - SqlRunner.instance.run(" - SELECT ancestry, rank - FROM form_items - WHERE ancestry is NOT NULL AND ancestry != '' - GROUP BY ancestry, rank - HAVING COUNT(id) > 1 - ").any? - end - def self.terminate_sub_relationships(form_item_ids) Form.where(root_id: form_item_ids).update_all(root_id: nil) SkipRule.where(source_item_id: form_item_ids).delete_all diff --git a/lib/tasks/form_rank_helpers.rake b/lib/tasks/form_rank_helpers.rake new file mode 100644 index 0000000000..ab099f72e9 --- /dev/null +++ b/lib/tasks/form_rank_helpers.rake @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +namespace :rank do + # be rails rank:audit + desc "Audits form rank data globally." + task :audit, [:this_is_required_to_load_sql_for_some_reason] => :environment do + puts rank_gaps.any? ? + "Uh oh, there are rank gaps before each of these FormItems:" : + "Good, there are no rank gaps." + rank_gaps.map do |row| + id = row["id"] + form_item = FormItem.includes(:mission, :form).find(id) + puts "#{form_item.code}\tRank #{form_item.rank}\tMission #{form_item.mission.name}\tForm #{form_item.form.name}" + end + puts + + puts duplicate_ranks.any? ? + "Uh oh, there are duplicate ranks for each of these FormItems:" : + "Good, there are no duplicate ranks." + duplicate_ranks.map do |row| + ancestry = row["ancestry"] + form_item = FormItem.includes(:mission, :form).find_by(ancestry: ancestry) + puts "#{form_item.code}\tRank #{form_item.rank}\tMission #{form_item.mission.name}\tForm #{form_item.form.name}" + end + puts + end + + # be rails rank:redo[FORM_ID] + desc "Re-ranks all form items in a given form." + task :redo, [:form_id] => :environment do |_, args| + form_id = args[:form_id] + form = Form.find_by(id: form_id) + unless form + puts "Form ID not found: '#{form_id}'." + next + end + + puts "Re-ranking #{form.descendants.count} descendants..." + rank_tree = [] + FormItem.where(form: form).ordered_by_ancestry_and(:rank).each do |form_item| + depth = form_item.depth + next if depth.zero? # This is the root node. + rank_tree += [0] if depth > rank_tree.count + rank_tree.pop if depth < rank_tree.count + rank_tree[-1] += 1 + puts "#{form_item.full_dotted_rank}: #{form_item.rank} => #{rank_tree.last}" + form_item.update!(rank: rank_tree.last) + end + end +end + +def rank_gaps + SqlRunner.instance.run(" + SELECT id FROM form_items fi1 + WHERE fi1.rank > 1 AND NOT EXISTS ( + SELECT id FROM form_items fi2 + WHERE fi2.ancestry = fi1.ancestry AND fi2.rank = fi1.rank - 1) + ") +end + +def duplicate_ranks + SqlRunner.instance.run(" + SELECT ancestry, rank + FROM form_items + WHERE ancestry is NOT NULL AND ancestry != '' + GROUP BY ancestry, rank + HAVING COUNT(id) > 1 + ") +end