Source code
class Maintenance::RecoverOrphanedCustomFieldsTask < MaintenanceTasks::Task
include ArrayHelper
collection_batch_size(1_000)
attribute :emails, :string
validates :emails, presence: true, fcm_email_format: true
REPORT_COLUMNS = %w[
custom_field_id
custom_field_name
legacy_tree_node_id
tree_node_status
recoverability
action
mbo_profile_ids
status
errors
].freeze
after_start :prepare_csv_path
after_complete :send_report
after_error :send_report
def collection
return CustomField.none unless CustomField.connection.column_exists?(:custom_fields, :tree_node_id)
CustomField.where.missing(:custom_field_to_mbo_profiles)
end
def process(custom_field)
tree_node_id_loaded = false
tree_node_id = legacy_tree_node_id_for(custom_field.id)
tree_node_id_loaded = true
row = process_custom_field(custom_field, tree_node_id)
append_report(row.merge(custom_field_id: custom_field.id, custom_field_name: custom_field.name))
rescue StandardError => e
append_report(
custom_field_id: custom_field.id,
custom_field_name: custom_field.name,
tree_node_id: tree_node_id,
tree_node_status: tree_node_id_loaded ? tree_node_status_for(tree_node_id) : 'UNKNOWN',
recoverability: tree_node_id_loaded ? recoverability_for(tree_node_id) : 'UNKNOWN',
action: 'FAILED',
mbo_profile_ids: [],
status: 'FAILED',
errors: e.message
)
raise
end
private
def csv_path
@csv_path ||= Rails.root.join('tmp', "recover_orphaned_custom_fields_#{Time.now.to_i}.csv").to_s
end
def prepare_csv_path
File.write(csv_path, REPORT_COLUMNS.to_csv)
end
def send_report
return unless csv_path && File.exist?(csv_path)
CsvReportMailer.send_report(
recipients: emails_array(emails),
file_path: csv_path,
report_sender: 'Recover Orphaned Custom Fields'
).deliver_now
ensure
File.delete(csv_path) if csv_path && File.exist?(csv_path)
end
def process_custom_field(custom_field, tree_node_id)
if tree_node_id.blank?
delete_non_recoverable_custom_field(custom_field)
return {
tree_node_id: nil,
tree_node_status: 'NOT_APPLICABLE',
recoverability: 'NON_RECOVERABLE',
action: 'DELETED',
mbo_profile_ids: [],
status: 'SUCCESS',
errors: nil
}
end
process_recoverable(custom_field, tree_node_id)
end
def process_recoverable(custom_field, tree_node_id)
tree_node = TreeNode.find_by(id: tree_node_id)
unless tree_node
return {
tree_node_id: tree_node_id,
tree_node_status: 'NOT_FOUND',
recoverability: 'RECOVERABLE',
action: 'SKIPPED_TREE_NODE_NOT_FOUND',
mbo_profile_ids: [],
status: 'FAILED',
errors: 'TreeNode not found'
}
end
mbo_profiles, action = mbo_profiles_for(tree_node)
associate_custom_field(custom_field, mbo_profiles)
{
tree_node_id: tree_node_id,
tree_node_status: 'FOUND',
recoverability: 'RECOVERABLE',
action: action,
mbo_profile_ids: mbo_profiles.map(&:id),
status: 'SUCCESS',
errors: nil
}
end
def mbo_profiles_for(tree_node)
mbo_profiles = tree_node.mbo_profiles.to_a
return [mbo_profiles, 'LINKED_EXISTING_MBO_PROFILES'] if mbo_profiles.any?
[[create_default_mbo_profile(tree_node)], 'CREATED_DEFAULT_MBO_PROFILE_AND_LINKED']
end
def associate_custom_field(custom_field, mbo_profiles)
mbo_profiles.each do |mbo_profile|
CustomFieldToMboProfile.find_or_create_by!(
custom_field_id: custom_field.id,
mbo_profile_id: mbo_profile.id
)
end
end
def append_report(row)
File.open(csv_path, 'a') do |file|
file.puts(
[
row.fetch(:custom_field_id),
row.fetch(:custom_field_name),
row.fetch(:tree_node_id),
row.fetch(:tree_node_status),
row.fetch(:recoverability),
row.fetch(:action),
row.fetch(:mbo_profile_ids).join('|'),
row.fetch(:status),
row.fetch(:errors)
].to_csv
)
end
end
def recoverability_for(tree_node_id)
tree_node_id.present? ? 'RECOVERABLE' : 'NON_RECOVERABLE'
end
def tree_node_status_for(tree_node_id)
return 'NOT_APPLICABLE' if tree_node_id.blank?
TreeNode.exists?(id: tree_node_id) ? 'FOUND' : 'NOT_FOUND'
end
def legacy_tree_node_id_for(custom_field_id)
return nil unless CustomField.connection.column_exists?(:custom_fields, :tree_node_id)
CustomField.connection.select_value(
CustomField.sanitize_sql_array(
['SELECT tree_node_id FROM custom_fields WHERE id = ? LIMIT 1', custom_field_id]
)
)
end
def delete_non_recoverable_custom_field(custom_field)
ActiveRecord::Base.transaction do
Condition.where(governing_custom_field_id: custom_field.id).find_each do |condition|
condition.update!(governing_custom_field_id: nil)
end
custom_field.destroy!
end
end
def create_default_mbo_profile(tree_node)
MboProfiles::CreateDefaultMboProfileService.new(tree_node: tree_node).call
end
end