sanger/sequencescape

View on GitHub
app/models/asset.rb

Summary

Maintainability
A
0 mins
Test Coverage
B
89%
# frozen_string_literal: true
require 'eventful_record'

# Asset is a very busy class which combines what should probably be two separate concepts:
# Labware: A physical item which can move round the lab, such as a {Plate} or {Tube}
#
# This class has now been split into two and should be eliminated.
#
# Key subclasses
# --------------
# - {Receptacle}: Something which can contain aliquots, such as a {Well} or {Tube}
#   Currently those these all share a table, and exhibit single table inheritance.
# - {Plate}: A piece of labware containing multiple receptacles known as {Well wells}.
#   Plates can be a variety of shapes and sizes, although the marority are 12*8 (96) or
#   24*16 (384) wells in size.
# - {Well}: A receptacle on a plate. Wells themselves do not exist independently of plates in reality,
#   although may occasionally be modelled as such.
# - {Tube}: A piece of labware with a single {Receptacle}. These behaviours are currently coupled together.
# - {Lane}: Forms part of a sequencing Flowcell. The flowcell itself is not currently modelled but can be
#   approximated by a {Batch}
# - {Fragment}: Represents an isolated segment of DNA on a Gel. Historical.
# - {Receptacle}: Abstract class inherited by any asset which can contain stuff directly
#
# Some of the above are further subclasses to handle specific behaviours.
class Asset < ApplicationRecord
  include Api::Messages::QcResultIO::AssetExtensions
  include Event::PlateEvents
  extend EventfulRecord

  self.abstract_class = true

  class_attribute :stock_message_template, instance_writer: false

  # The partial used to render the list of assets on the asset show page
  class_attribute :sample_partial, instance_writer: false

  # When set to true, allows assets of this type to be automatically moved
  # from the asset_group show page
  class_attribute :automatic_move, instance_writer: false

  # Determines if the user is presented with the request additional sequencing link
  class_attribute :sequenceable, instance_writer: false

  self.per_page = 500
  self.inheritance_column = 'sti_type'
  self.sample_partial = 'assets/samples_partials/blank'
  self.automatic_move = false
  self.sequenceable = false

  delegate :human_barcode, to: :labware, prefix: true, allow_nil: true

  has_many_events do
    event_constructor(:create_external_release!, ExternalReleaseEvent, :create_for_asset!)
    event_constructor(:create_state_update!, Event::AssetSetQcStateEvent, :create_updated!)
    event_constructor(:create_scanned_into_lab!, Event::ScannedIntoLabEvent, :create_for_asset!)
    event_constructor(:create_labware_failed!, Event::LabwareFailedEvent, :create_for_asset!)
    event_constructor(:create_plate!, Event::PlateCreationEvent, :create_for_asset!)
    event_constructor(:create_gel_qc!, Event::SampleLogisticsQcEvent, :create_gel_qc_for_asset!)
    event_constructor(:created_using_sample_manifest!, Event::SampleManifestEvent, :created_sample!)
    event_constructor(:updated_using_sample_manifest!, Event::SampleManifestEvent, :updated_sample!)
    event_constructor(:updated_fluidigm_plate!, Event::SequenomLoading, :updated_fluidigm_plate!)
    event_constructor(:update_gender_markers!, Event::SequenomLoading, :created_update_gender_makers!)
    event_constructor(:update_sequenom_count!, Event::SequenomLoading, :created_update_sequenom_count!)
  end
  has_many_lab_events

  has_one_event_with_family 'scanned_into_lab'

  delegate :last_qc_result_for, to: :qc_results

  broadcast_with_warren

  scope :include_requests_as_target, -> { includes(:requests_as_target) }
  scope :include_requests_as_source, -> { includes(:requests_as_source) }

  scope :sorted, -> { order('map_id ASC') }

  scope :recent_first, -> { order(id: :desc) }

  # Includes are mostly handled in the views themselves, which allows us to be
  # a bit more smart about what we load if necessary (Ie. different stuff for plates)
  scope :include_for_show, -> { includes(:studies) }

  # Returns the type of asset that can be considered appropriate for request types.
  def asset_type_for_request_types
    self.class
  end

  def ancestor_of_purpose(_ancestor_purpose_id)
    # If it's not a tube or a plate, defaults to stock_plate
    stock_plate
  end

  def label
    sti_type || 'Unknown'
  end

  def label=(new_type)
    self.sti_type = new_type
  end

  def request_types
    RequestType.where(asset_type: label)
  end

  def details
    nil
  end

  def original_stock_plates
    ancestors.where(plate_purpose_id: PlatePurpose.stock_plate_purpose)
  end

  def has_stock_asset?
    false
  end

  def compatible_purposes
    Purpose.none
  end

  # Most assets don't have a barcode
  def barcode_number
    nil
  end

  def prefix
    nil
  end

  # By default only barcodeable assets generate barcodes
  def generate_barcode
    nil
  end

  def contained_samples
    Sample.none
  end

  def printable?
    printable_target.present?
  end

  def printable_target
    nil
  end

  def type
    self.class.name.underscore
  end

  # Generates a message to broadcast the tube to the stock warehouse
  # tables. Raises an exception if no template is configured for a give
  # asset. In most cases this is because the asset is not a stock
  # Called when importing samples, e.g. in sample_manifest > core_behaviour, on manifest upload
  def register_stock!
    class_name = self.class.name
    if stock_message_template.nil?
      # rubocop:todo Layout/LineLength
      raise StandardError,
            "No stock template configured for #{class_name}. If #{class_name} is a stock, set stock_template on the class."
      # rubocop:enable Layout/LineLength
    end

    Messenger.create!(target: self, template: stock_message_template, root: 'stock_resource')
  end

  def update_from_qc(qc_result)
    Rails.logger.info "#{self.class.name} #{id} updated by QcResult #{qc_result.id}"
  end

  def get_qc_result_value_for(key)
    last_qc_result_for(key).pick(:value)
  end
end