sanger/sequencescape

View on GitHub
app/models/tube.rb

Summary

Maintainability
A
0 mins
Test Coverage
A
94%
# frozen_string_literal: true
# A Tube is a piece of {Labware}
class Tube < Labware
  include Barcode::Barcodeable
  include ModelExtensions::Tube
  include Tag::Associations
  include Asset::Ownership::Unowned
  include Transfer::Associations
  include Transfer::State::TubeState
  include Api::Messages::QcResultIO::TubeExtensions
  include SingleReceptacleLabware

  extend QcFile::Associations

  # Fallback for tubes without a purpose
  self.default_prefix = 'NT'
  self.automatic_move = true

  has_qc_files

  def subject_type
    'tube'
  end

  def barcode!
    self.sanger_barcode = { number: AssetBarcode.new_barcode, prefix: default_prefix } unless barcode_number
    save!
  end

  delegate :source_purpose, to: :purpose, allow_nil: true

  def comments
    @comments ||= CommentsProxy::Tube.new(self)
  end

  def submission
    submissions.first
  end

  def pool_id
    submissions.ids.first
  end

  def name_for_label
    primary_sample&.shorten_sanger_sample_id.presence || name
  end

  alias friendly_name human_barcode

  # Delegates the provided methods to purpose, passing the tube as the first argument, and the remaining arguments as-is
  def self.delegate_to_purpose(*methods)
    methods.each { |method| class_eval("def #{method}(*args, &block) ; purpose.#{method}(self, *args, &block) ; end") }
  end

  # TODO: change column name to account for purpose, not plate_purpose!
  belongs_to :purpose, class_name: 'Tube::Purpose', foreign_key: :plate_purpose_id
  has_one :racked_tube, dependent: :destroy
  has_one :tube_rack, through: :racked_tube

  scope :in_column_major_order, lambda { joins(:racked_tube).order('racked_tubes.coordinate ASC') }
  delegate :coordinate, to: :racked_tube

  # @!method stock_plate
  #   Returns the stock plate of the tube, behaviour delegated to purpose
  #   @return [Plate] The stock plate
  delegate_to_purpose(:stock_plate)

  delegate :barcode_type, to: :purpose

  def name_for_child_tube
    name
  end

  def sanger_barcode=(attributes)
    barcodes << Barcode.build_sanger_ean13(attributes)
  end

  def details
    purpose.try(:name) || 'Tube'
  end

  def after_comment_addition(comment)
    comments.add_comment_to_submissions(comment)
  end

  def self.create_with_barcode!(*args, &block)
    attributes = args.extract_options!.symbolize_keys

    barcode, prefix = extract_barcode(args, attributes)
    validate_barcode(barcode, prefix) if barcode.present?
    barcode ||= AssetBarcode.new_barcode

    # remove this so it's not passed in on creation, and set it explicitly afterwards
    # this is to control the order of barcode addition so that it gets set as the 'primary' barcode
    foreign_barcode = attributes.delete(:foreign_barcode)

    tube = create!(attributes.merge(sanger_barcode: { prefix: prefix, number: barcode }), &block)

    tube.foreign_barcode = foreign_barcode if foreign_barcode
    tube.reload
  end
end

private

# mutates 'attributes'
def extract_barcode(args, attributes)
  barcode = args.first || attributes.delete(:barcode)
  prefix = attributes.delete(:barcode_prefix)&.prefix || default_prefix
  [barcode, prefix]
end

def validate_barcode(barcode, prefix)
  human = SBCF::SangerBarcode.new(prefix: prefix, number: barcode).human_barcode
  raise "Barcode: #{barcode} already used!" if Barcode.exists?(barcode: human)
end

require_dependency 'sample_tube'
require_dependency 'library_tube'
require_dependency 'qc_tube'
require_dependency 'pulldown_multiplexed_library_tube'
require_dependency 'pac_bio_library_tube'
require_dependency 'stock_library_tube'
require_dependency 'stock_multiplexed_library_tube'