sanger/sequencescape

View on GitHub
app/models/phi_x/spiked_buffer.rb

Summary

Maintainability
A
0 mins
Test Coverage
A
97%
# frozen_string_literal: true

# {include:PhiXesController}
#
# PhiX::SpikedBuffer acts as a factory to generate the required {SpikedBuffer spiked buffer}
class PhiX::SpikedBuffer
  include ActiveModel::Model

  # @return [String] the base name for the created {LibraryTube library tubes}
  #                  Will be appended with #n to distinguish multiple tubes.
  #                  eg. ('Tube name #1', 'Tube name #2')
  attr_accessor :name

  # @return [String] The barcode of the LibraryTube from which the SpikedBuffer
  # has been created
  attr_accessor :parent_barcode

  # @return [Float] The concentration of the created library in nM
  attr_accessor :concentration

  # @return [Float] The volume of the created library in ul
  attr_accessor :volume

  # @return [Integer] The number of {SpikedBuffer library tubes} to create
  attr_accessor :number

  # @return [Tube] PhiX containing parent tube. If not provided will look up via the parent_barcode
  attr_writer :parent

  # @return [Integer] The id of the {Study} to associate with the {Aliquot}
  attr_accessor :study_id

  validates :name, presence: true
  validates :concentration, numericality: { greater_than: 0, only_integer: false }
  validates :volume, numericality: { greater_than: 0, only_integer: false }
  validates :number, numericality: { greater_than: 0, only_integer: true }
  validates :parent, presence: { message: 'could not be found with that barcode' }, if: :parent_barcode
  validate :parent_contains_phi_x, if: :parent

  #
  # Generates spiked buffers if the factory is valid, otherwise returns false and does nothing
  #
  # @return [Boolean] true if the operation completed successfully, false otherwise
  def save
    return false unless valid?

    @created_spiked_buffers = generate_spiked_buffers
    true
  end

  #
  # Returns the spiked_buffers that were create as part of {#save}
  # Will be an empty array if called before the #save method
  #
  # @return [Array] Array of the spiked_buffers created by the factory.
  def created_spiked_buffers
    @created_spiked_buffers || []
  end

  #
  # Returns the provided parent, or the matching tube found via the parent_barcode
  #
  # @return [Tube] The parent tube
  def parent
    @parent ||= Tube.includes(aliquots: %i[sample tag tag2]).find_by_barcode(parent_barcode)
  end

  # Validates the contents of the parent
  # The parent MUST contain one aliquot, and it MUST be a PhiX sample
  # This MAY be a stock, or a previously created spiked buffer
  def parent_contains_phi_x
    return true if parent.aliquots.one? && parent.aliquots.all? { |aliquot| aliquot.sample == PhiX.sample }

    errors.add(:parent_barcode, 'does not contain PhiX')
  end

  def tags
    i7_oligo, i5_oligo = parent.aliquots.first.tags_combination
    PhiX.tag_option_for(i7_oligo: i7_oligo, i5_oligo: i5_oligo)
  end

  private

  def phi_x_sample
    @phi_x_sample ||= PhiX.sample
  end

  # Setting the study for a SpikedBuffer tube is not currently exposed as an option through the /phi_x page
  # due to concerns that it will be set accidentally
  # But the option is here in the model to set it via study_id if needed in future
  def aliquot_attributes
    study_id.present? ? { study_id: study_id } : {}
  end

  # Generates .number PhiX.stock_purpose tubes names
  # with name, followed by '#n' where n is the tube number (starting with 1)
  # Creates a qc_result to set the concentration (uses molarity as we're in nM not ng/ul)
  # Creates a qc_result to set the volume
  # Transfers aliquots from the parent
  def generate_spiked_buffers # rubocop:todo Metrics/AbcSize, Metrics/MethodLength
    Array.new(number.to_i) do |index|
      spiked_buffer =
        PhiX
          .spiked_buffer_purpose
          .create!(name: "#{name} ##{index + 1}") do |tube|
            receptacle = tube.receptacle
            receptacle.qc_results.build(key: 'molarity', value: concentration, units: 'nM')
            receptacle.qc_results.build(key: 'volume', value: volume, units: 'ul')
            receptacle.transfer_requests_as_target.build(
              asset: parent.receptacle,
              target_asset: receptacle,
              aliquot_attributes: aliquot_attributes
            )
          end
      parent.children << spiked_buffer
      spiked_buffer
    end
  end
end