sanger/sequencescape

View on GitHub
app/models/sample_manifest/plate_behaviour.rb

Summary

Maintainability
A
0 mins
Test Coverage
A
94%
# frozen_string_literal: true
module SampleManifest::PlateBehaviour
  class Base
    include SampleManifest::CoreBehaviour::Shared
    include SampleManifest::CoreBehaviour::NoSpecializedValidation

    attr_reader :plates

    def initialize(manifest)
      @manifest = manifest
      @plates = []
    end

    def generate
      @plates = generate_plates(purpose)

      sanger_sample_ids = insert_sanger_sample_ids
      well_data = build_well_data(sanger_sample_ids)

      build_wells_async(well_data)

      @details_array = build_details_array(well_data)

      @manifest.update!(barcodes: @plates.map(&:human_barcode))
    end

    def acceptable_purposes
      PlatePurpose.for_submissions
    end

    def default_purpose
      PlatePurpose.stock_plate_purpose
    end

    def included_resources
      [{ sample: :sample_metadata, asset: { plate: :barcodes } }]
    end

    def io_samples
      samples.map do |sample|
        container = sample.primary_receptacle
        {
          sample: sample,
          container: {
            barcode: container.plate.human_barcode,
            position: container.map.description.sub(/^([^\d]+)(\d)$/, '\10\2')
          }
        }
      end
    end

    def updated_by!(user, samples)
      # It's more efficient to look for the wells with the samples than to look for the assets from the samples
      # themselves as the former can use named_scopes where as the latter is an array that needs iterating over.
      Plate.with_sample(samples).each { |plate| plate.events.updated_using_sample_manifest!(user) }
    end

    def details_array
      @details_array ||=
        sample_manifest_assets
          .includes(asset: [:map, :aliquots, { plate: :barcodes }])
          .map do |sample_manifest_asset|
            {
              barcode: sample_manifest_asset.asset.plate.human_barcode,
              position: sample_manifest_asset.asset.map_description,
              sample_id: sample_manifest_asset.sanger_sample_id
            }
          end
    end

    def labware=(labware)
      @plates = labware
    end

    # We use the barcodes here as we may need to reference the plates before the delayed job has passed
    def labware
      plates | Labware.with_barcode(barcodes)
    end
    alias printables labware

    private

    def generate_plates(purpose)
      Array.new(count) { purpose.create!(:without_wells) }.sort_by(&:human_barcode)
    end

    def insert_sanger_sample_ids
      sanger_sample_ids = generate_sanger_ids(@plates.sum(&:size) * @manifest.rows_per_well)
      sanger_sample_ids.map do |sanger_sample_id|
        SangerSampleId.generate_sanger_sample_id!(study.abbreviation, sanger_sample_id)
      end
    end

    # output:
    # plate_id => { map_id => [sanger_sample_id, sanger_sample_id, ...] }
    def build_well_data(sanger_sample_ids)
      @plates.each_with_object({}) do |plate, well_data|
        well_data[plate.id] = {}

        plate.maps.in_column_major_order.each do |well_map|
          well_data[plate.id][well_map.id] = sanger_sample_ids.shift(@manifest.rows_per_well)
        end
      end
    end

    # Each of the plates is handled by an individual job.
    # If it doesn't do this we run the risk that the 'handler' column in the database
    # for the delayed job will not be large enough and will truncate the data.
    def build_wells_async(well_data)
      @plates.each do |plate|
        Delayed::Job.enqueue SampleManifest::GenerateWellsJob.new(@manifest.id, well_data[plate.id], plate.id)
      end
    end

    # output:
    # [{barcode, position, sanger_sample_id}, {barcode, position, sanger_sample_id}, ...]
    def build_details_array(well_data)
      @details_array =
        @plates.flat_map do |plate|
          well_data[plate.id].flat_map do |map_id, sanger_sample_ids|
            sanger_sample_ids.map do |sanger_sample_id|
              {
                barcode: plate.human_barcode,
                position: plate.maps.find(map_id).description,
                sample_id: sanger_sample_id
              }
            end
          end
        end
    end
  end

  class Core < Base
    include SampleManifest::CoreBehaviour::StockAssets
  end
end