sanger/sequencescape

View on GitHub
lib/limber/helper.rb

Summary

Maintainability
A
55 mins
Test Coverage
F
0%
# frozen_string_literal: true

# Helper templates and methods used in limber.rake
module Limber::Helper
  PIPELINE = 'Limber-Htp'
  PIPELINE_REGEX = /Illumina-[A-z]+ /
  PRODUCTLINE = 'Illumina-Htp'

  # Construct submission templates for the Limber pipeline
  class TemplateConstructor
    include ActiveModel::Model

    # Required:
    # @attr [String] prefix The prefix for the given limber pipeline (eg. WGS)
    # @attr [ProductCatalogue] catalogue The product catalogue that matches the submission.

    # @note Most limber stuff will use a simple SingleProduct catalogue with a product names after the prefix.

    # The following parameters are optional, and usually get calculated from the prefix.
    # @attr_writer [String] name Optional: The library creation portion of the submission template name
    #                            defaults to the prefix.
    # @attr_writer [String] type Optional: The library creation request key (eg. limber_wgs) for the templates.
    #                           Calculated from the prefix by default.
    # @attr_writer [String] role Optional: A string matching the desired order role. Defaults to the prefix.

    # The following are optional and change the range of submission templates constructed.
    # @attr_writer [Boolean] cherrypicked  Set to true to generate submission templates with in built cherrypicking.
    # @attr_writer [Array] sequencing_keys Array of sequencing request type keys to build templates for.
    #                                      Defaults to all appropriate request types.

    attr_accessor :prefix, :catalogue
    attr_writer :name, :type, :role, :cherrypicked, :sequencing_keys, :pipeline, :product_line

    #
    # Finds all submission templates matching the provided name.
    # If sequencing is not specified will find *all* submission templates.
    # @param name [String] The library creation portion of the {SubmissionTemplate} name
    # @param sequencing [Array<String>] Array of sequencing {RequestType#key} to find the templates for.
    #
    # @return [Array<SubmissionTemplate>] An array of all matching submission templates.
    def self.find_for(name, sequencing = nil)
      tc = TemplateConstructor.new(name: name, sequencing: sequencing)
      [true, false].map do |cherrypick|
        tc.sequencing.map do |sequencing_request_type|
          SubmissionTemplate.find_by!(name: tc.name_for(cherrypick, sequencing_request_type))
        end
      end.flatten
    end

    validates :name, presence: { message: 'must be specified, or prefix should be provided' }
    validates :role, presence: { message: 'must be specified, or prefix should be provided' }
    validates :type, presence: { message: 'must be specified, or prefix should be provided' }
    validates :catalogue, presence: true

    def name
      @name || prefix
    end

    # The name or the {OrderRole} associated with the submission template. If {#role} is not specified
    # falls back to {#prefix}
    #
    # @return [String] The name of the order role used for the submission templates
    def role
      @role || prefix
    end

    #
    # Prefix before submission template names.
    # Defaults to {Limber::Helper::PIPELINE}
    #
    # @return [String] Prefix before submission template names.
    def pipeline
      @pipeline || PIPELINE
    end

    # The name of the {ProductLine} associated with the submission template.
    #
    # {include:ProductLine}
    #
    # If {#product_line} is not specified defaults to {PRODUCTLINE}
    #
    # @return [String] The name of the product line
    def product_line
      @product_line || PRODUCTLINE
    end

    # The {RequestType#key} of the {RequestType} that forms the library creation
    # part of the generated {SubmissionTemplate submission templates}.
    #
    # If {#type} is not specified, defaults to 'limber_' followed by {#prefix}
    #
    # @return [String] The key of the library creation {RequestType}
    def type
      @type || "limber_#{prefix.downcase.tr(' ', '_')}"
    end

    # The {RequestType#key} of the {RequestType request types} that forms the sequencing
    # part of the generated {SubmissionTemplate submission templates}.
    #
    # If {#sequencing_keys= sequencing_keys} is not specified, defaults to 'limber_' followed by {#prefix}
    #
    # @return [Array] All Sequencing RequestTypes for which a SubmissionTemplate will be generated
    def sequencing_request_types
      @sequencing_request_types ||= @sequencing_keys.map { |request| RequestType.find_by!(key: request) }
    end

    #
    # The name of the {SubmissionTemplate} for the given options.
    # @param cherrypick [Boolean] Whether there is a cherrypick component
    # @param sequencing_request_type [RequestType] The sequencing request type
    #
    # @return [String] A name for the request type
    def name_for(cherrypick, sequencing_request_type)
      "#{pipeline} - #{cherrypick ? 'Cherrypicked - ' : ''}#{name} - #{
        sequencing_request_type.name.gsub(PIPELINE_REGEX, '')
      }"
    end

    # Construct a series of {SubmissionTemplate submission templates} according to the specified options.
    # @see file:lib/tasks/limber.rake
    #
    # @example Generating PCR Free submission templates
    #   Limber::Helper::RequestTypeConstructor.new(
    #    'PCR Free',
    #    library_types: ['HiSeqX PCR free', 'PCR Free 384', 'Chromium single cell CNV', 'DAFT-seq'],
    #    default_purposes: ['PF Cherrypicked']
    #  ).build!
    def build!
      validate!
      each_submission_template { |config| SubmissionTemplate.create!(config) }
    end

    private

    def product_line_id
      @product_line_id ||= ProductLine.find_or_create_by(name: product_line).id
    end

    def library_request_type
      @library_request_type ||= RequestType.find_by!(key: type)
    end

    def cherrypick_request_type
      RequestType.find_by!(key: 'cherrypick_for_limber')
    end

    def multiplexing_request_type
      RequestType.find_by!(key: 'limber_multiplexing')
    end

    def request_type_ids(cherrypick, sequencing)
      ids = []
      ids << [cherrypick_request_type.id] if cherrypick
      ids << [library_request_type.id]
      ids << [multiplexing_request_type.id] unless library_request_type.for_multiplexing?
      ids << [sequencing.id]
    end

    def cherrypick_options
      @cherrypicked ? [true, false] : [false]
    end

    def each_submission_template
      cherrypick_options.each do |cherrypick|
        sequencing_request_types.each do |sequencing_request_type|
          next if SubmissionTemplate.exists?(name: name_for(cherrypick, sequencing_request_type))

          yield(
            {
              name: name_for(cherrypick, sequencing_request_type),
              submission_class_name: 'LinearSubmission',
              submission_parameters: submission_parameters(cherrypick, sequencing_request_type),
              product_line_id: product_line_id,
              product_catalogue: catalogue
            }
          )
        end
      end
    end

    def submission_parameters(cherrypick, sequencing)
      {
        request_type_ids_list: request_type_ids(cherrypick, sequencing),
        order_role_id: OrderRole.find_or_create_by(role: role).id
      }
    end
  end

  #
  # Class LibraryOnlyTemplateConstructor provides a template constructor
  # which JUST build the library portion of the submission template.
  # No multiplexing or sequencing requests are added.
  #
  class LibraryOnlyTemplateConstructor < TemplateConstructor
    def name_for(cherrypick, _sequencing_request_type)
      "#{pipeline} - #{cherrypick ? 'Cherrypicked - ' : ''}#{name}"
    end

    def sequencing_request_types
      [nil]
    end

    def request_type_ids(cherrypick, _sequencing)
      ids = []
      ids << [cherrypick_request_type.id] if cherrypick
      ids << [library_request_type.id]
    end
  end

  #
  # Class LibraryAndMultiplexingTemplateConstructor provides a template
  # constructor which build the library portion of the submission
  # template with the multiplexing request. No sequencing requests are added.
  #
  class LibraryAndMultiplexingTemplateConstructor < TemplateConstructor
    def name_for(cherrypick, _sequencing_request_type)
      "#{pipeline} - #{cherrypick ? 'Cherrypicked - ' : ''}#{name} - Pool"
    end

    def sequencing_request_types
      [nil]
    end

    def request_type_ids(cherrypick, _sequencing)
      ids = []
      ids << [cherrypick_request_type.id] if cherrypick
      ids << [library_request_type.id]
      ids << [multiplexing_request_type.id] unless library_request_type.for_multiplexing?
    end
  end

  def self.find_project(name)
    if Rails.env.production?
      Project.find_by!(name: name)
    else
      # In development mode or UAT we don't care so much
      Project.find_by(name: name) || UatActions::StaticRecords.project
    end
  end
end