SpeciesFileGroup/taxonworks

View on GitHub
app/models/dataset_record.rb

Summary

Maintainability
A
55 mins
Test Coverage
# A DatasetRecord is the unit of data (typically a table row) from an ImportDataset
#
# @!attribute metadata
#   @return [Hash]
#      data about the record. No particular structure is enforced, any subclass may store metadata (typically to aid the import process).
#
# @!attribute status
#   @return [String]
#    current import status (e.g. Pending, Imported, Deleted, etc.)
#
class DatasetRecord < ApplicationRecord
  include Housekeeping
  include Shared::IsData

  belongs_to :import_dataset

  validates :type, presence: true
  validates :status, presence: true

  after_create :create_fields
  after_update :update_fields
  before_destroy :destroy_fields

  # has_many has serious performance consequences when deleting an import dataset, so using class method instead
  def dataset_record_fields
    DatasetRecordField.where(dataset_record: self)
  end

  # Sets up internal representation of data fields with field data, skipping those fields that are blank for performance and space-efficiency reasons.
  # @param field_data [Array] ordered list of values, each value representing a cell/field of the record.
  def initialize_data_fields(field_data)
    @data_fields = []
    field_data.each_with_index do |value, index|
      @data_fields[index] = (value.blank? ? nil : value)
    end
    @data_field_changed = Array.new(@data_fields.size)
  end

  def data_fields
    unless @data_fields
      @data_fields = self.dataset_record_fields
        .pluck(:position, :value)
        .inject([]) { |a, f| a[f[0]] = f[1]; a }
      @data_field_changed = Array.new(@data_fields.size)
    end

    @data_fields
  end

  def get_data_field(index)
    self.data_fields[index]
  end

  def set_data_field(index, value)
    unless frozen_fields?
      old = self.data_fields[index]
      self.data_fields[index] = value

      begin
        data_field_changed(index, value)
        @data_field_changed[index] = true
      rescue
        self.data_fields[index] = old
        raise
      end
    end
  end

  def frozen_fields?
    self.status == 'Imported'
  end

  private

  def data_field_changed(index, value)
    # Subclasses may re-implement to perform actions when field change
  end

  def field_db_attributes(position, value)
    {
      position: position,
      value: value,
      dataset_record_id: id,
      project_id: project_id,
      import_dataset_id: import_dataset_id,
      encoded_dataset_record_type: DatasetRecordField.encode_record_type(self.class)
    } if value
  end

  def fields_db_attributes
    data_fields.filter_map.with_index { |v, p| field_db_attributes(p, v) }
  end

  def create_fields
    attributes = fields_db_attributes
    DatasetRecordField.insert_all(attributes) if attributes.any?
  end

  def update_fields
    upsert_fields = @data_field_changed
      &.filter_map&.with_index { |c, i| field_db_attributes(i, data_fields[i]) if c } || []
    delete_fields = @data_field_changed
      &.filter_map&.with_index { |c, i| i if c && data_fields[i].blank? } || []

    DatasetRecordField.upsert_all(upsert_fields, unique_by: [:dataset_record_id, :position]) if upsert_fields.any?
    dataset_record_fields.where(position: delete_fields).delete_all if delete_fields.any?
  end

  def destroy_fields
    dataset_record_fields.delete_all
  end
end