Maintenance::Atom::ImportReasonsTask

Source code
# frozen_string_literal: true

# Imports Atom reason codes as Reason rows under the already-imported ReasonGroup
# referenced by reason_group_id. Code is normalized (strip/upcase) by the Reason model's normalizer.

class Maintenance::Atom::ImportReasonsTask < MaintenanceTasks::Task
  include DatadogTrace
  include Maintenance::Atom::BlankIdSkippable
  include Maintenance::Atom::CsvTimestampParsable

  DESCRIPTION_LIMIT = 255

  csv_collection
  report_on(StandardError)

  def process(row)
    return if skip_blank_id?(row, label: row['fcm_code'])

    reason = Reason.find_or_initialize_by(id: row['id'])
    reason.assign_attributes(reason_attributes(row))
    reason.save!
  end

  private

  def reason_attributes(row)
    {
      reason_group_id: ReasonGroup.find(row['reason_group_id']).id,
      code: row['fcm_code'],
      description: truncated_description(row)
    }.merge(timestamp_attributes(row))
  end

  # Only assign timestamps that are present so a blank CSV cell doesn't null out
  # an existing Reason's created_at/updated_at on re-import.
  def timestamp_attributes(row)
    {
      created_at: parse_timestamp(row['created_at']),
      updated_at: parse_timestamp(row['updated_at'])
    }.compact
  end

  # The Reason model normalizes code (strip/upcase) and description (strip), so
  # only the length has to be handled here. Truncation is logged rather than
  # silent so the data loss is visible in the import report.
  def truncated_description(row)
    description = row['description'].to_s
    return description if description.length <= DESCRIPTION_LIMIT

    Rails.logger.warn(
      "Truncating description for reason '#{row['fcm_code']}' (#{row['id']}) " \
      "from #{description.length} to #{DESCRIPTION_LIMIT} chars"
    )
    description.first(DESCRIPTION_LIMIT)
  end
end