Skip to content

Commit

Permalink
Merge pull request #1219 from sanger/pacbio-multiplexing-epic
Browse files Browse the repository at this point in the history
[EPIC] PacBio multiplexing
  • Loading branch information
BenTopping authored Feb 29, 2024
2 parents 6059b96 + c9a8ab3 commit c69207f
Show file tree
Hide file tree
Showing 55 changed files with 1,301 additions and 365 deletions.
2 changes: 1 addition & 1 deletion app/exchanges/run_csv/deprecated_pacbio_sample_sheet.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def csv_headers
end

def csv_sample_rows(well)
well.libraries.map do |library|
well.all_libraries.map do |library|
# add row under well header for each sample in the well
csv_data(sample: library, well:, row_type: :sample)
end
Expand Down
7 changes: 4 additions & 3 deletions app/models/aliquot.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@ class Aliquot < ApplicationRecord

belongs_to :tag, optional: true
belongs_to :source, polymorphic: true
belongs_to :well, class_name: 'Pacbio::Well', optional: true, foreign_key: :pacbio_well_id,
inverse_of: :aliquots
# Used to identify where a derived aliquot has been used
belongs_to :used_by, polymorphic: true, optional: true

# currently I have set these to be validated but not sure
# as library only validates when a run is created
# maybe we need to do this when the state is set to used?
# requests currently dont support these fields so we skip validation on primary aliquots
validates :volume, :concentration, :template_prep_kit_box_barcode, :insert_size,
# we dont validate insert size as it may not be known at the time of creation
validates :volume, :concentration, :template_prep_kit_box_barcode,
presence: true,
unless: -> { source.is_a?(Pacbio::Request) && aliquot_type == 'primary' }
validates :volume, :concentration, :insert_size,
Expand Down
18 changes: 8 additions & 10 deletions app/models/concerns/aliquotable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,13 @@ module Aliquotable
extend ActiveSupport::Concern

included do
has_many :aliquots, as: :source, dependent: :nullify
end

def primary_aliquot
# Something fundamentally wrong if we have more than one
aliquots.find_by(aliquot_type: :primary)
end

def derived_aliquots
aliquots.where(aliquot_type: :derived)
has_many :aliquots, as: :source, dependent: :destroy
has_many :used_aliquots, as: :used_by, dependent: :destroy, class_name: 'Aliquot'
has_one :primary_aliquot, -> { where(aliquot_type: :primary) },
as: :source, class_name: 'Aliquot',
dependent: :destroy, inverse_of: :source
has_many :derived_aliquots, -> { where(aliquot_type: :derived) },
as: :source, class_name: 'Aliquot',
dependent: :nullify, inverse_of: :source
end
end
10 changes: 5 additions & 5 deletions app/models/concerns/sample_sheet.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,20 +35,20 @@ def barcode_set

# Determines rendering of a row-per sample
def show_row_per_sample?
sample_sheet_behaviour.show_row_per_sample?(libraries)
sample_sheet_behaviour.show_row_per_sample?(all_libraries)
end

# Returns libraries only if they should be shown per row
def libraries_to_show_per_row
return unless show_row_per_sample?

libraries
all_libraries
end

# Sample Name field
def pool_barcode
# First pool in well's barcode as samples names are already contained in bio sample name
pools.first.tube.barcode
def tube_barcode
# Gets the firsts library barcode which will either be the pool barcode or the library barcode
all_libraries.first.tube.barcode
end

# find the plate given the plate_number
Expand Down
61 changes: 59 additions & 2 deletions app/models/pacbio/library.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,22 @@ class Library < ApplicationRecord
include Uuidable
include Librarian
include SampleSheet::Library
include Aliquotable

validates :volume, :concentration,
:insert_size, presence: true, on: :run_creation
validates :volume, :concentration,
:insert_size, numericality: { greater_than_or_equal_to: 0, allow_nil: true }

has_many :well_libraries, class_name: 'Pacbio::WellLibrary', foreign_key: :pacbio_library_id,
dependent: :nullify, inverse_of: :library
has_many :wells, class_name: 'Pacbio::Well', through: :well_libraries

belongs_to :request, class_name: 'Pacbio::Request', foreign_key: :pacbio_request_id,
inverse_of: :libraries
belongs_to :tag, optional: true
belongs_to :pool, class_name: 'Pacbio::Pool', foreign_key: :pacbio_pool_id,
inverse_of: :libraries
inverse_of: :libraries, optional: true
belongs_to :tube, optional: true

has_one :sample, through: :request
Expand All @@ -35,6 +40,34 @@ class Library < ApplicationRecord

has_one :source_plate, through: :source_well, source: :plate, class_name: '::Plate'

# TODO: remove pool constraint this when pools are updated for aliquots
validates :primary_aliquot, presence: true, if: -> { pool.blank? }
accepts_nested_attributes_for :primary_aliquot, allow_destroy: true

after_create :create_used_aliquot, :create_tube, if: -> { pool.blank? }
before_destroy :check_for_associated_wells, prepend: true

def create_used_aliquot
used_aliquots.create(
source: request,
aliquot_type: :derived,
volume:,
concentration:,
template_prep_kit_box_barcode:,
insert_size:,
tag:
)
end

def create_tube
self.tube = tube || Tube.create!
save
end

def tube
pool ? pool.tube : super
end

def collection?
false
end
Expand All @@ -43,6 +76,30 @@ def sample_sheet_behaviour
SampleSheetBehaviour.get(tag_set&.sample_sheet_behaviour || :untagged)
end

delegate :sequencing_plates, to: :pool
# @return [Array] of Runs that the pool is used in
def sequencing_runs
wells&.collect(&:run)&.uniq
end

# @return [Array] of Plates attached to a sequencing run
def sequencing_plates
# TODO: remove this when pools are updated for aliquots
if pool
pool.sequencing_plates
else
wells&.collect(&:plate)
end
end

private

def check_for_associated_wells
if wells.empty?
true
else
errors.add(:base, 'Cannot delete a library that is associated with wells')
throw(:abort)
end
end
end
end
40 changes: 25 additions & 15 deletions app/models/pacbio/well.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,9 @@ class Well < ApplicationRecord
has_many :well_pools, class_name: 'Pacbio::WellPool', foreign_key: :pacbio_well_id,
dependent: :destroy, inverse_of: :well, autosave: true
has_many :pools, class_name: 'Pacbio::Pool', through: :well_pools

has_many :aliquots, foreign_key: :pacbio_well_id, inverse_of: :well, dependent: :destroy,
autosave: true
has_many :libraries, through: :pools
has_many :tag_sets, through: :libraries
has_many :well_libraries, class_name: 'Pacbio::WellLibrary', foreign_key: :pacbio_well_id,
dependent: :destroy, inverse_of: :well, autosave: true
has_many :libraries, class_name: 'Pacbio::Library', through: :well_libraries

# pacbio smrt link options for a well are kept in store field of the well
# which is mapped to smrt_link_options column (JSON) of pacbio_wells table.
Expand Down Expand Up @@ -52,15 +50,10 @@ class Well < ApplicationRecord

validates :row, :column, presence: true

validates :pools, length: {
minimum: 1,
message: :well_min_pools
}

delegate :run, to: :plate, allow_nil: true

def tag_set
tag_sets.first
all_libraries.collect(&:tag_set).first
end

def sample_sheet_behaviour
Expand All @@ -75,6 +68,11 @@ def summary
"#{sample_names} #{comment}".strip
end

# A collection of all the libraries for a well
def all_libraries
pools.collect(&:libraries).flatten + libraries
end

# collection of all of the requests for a library
# useful for messaging
def request_libraries
Expand All @@ -84,25 +82,37 @@ def request_libraries
# a collection of all the sample names for a particular well
# useful for comments
def sample_names(separator = ':')
libraries.collect(&:request).collect(&:sample_name).join(separator)
all_libraries.collect(&:request).collect(&:sample_name).join(separator)
end

# a collection of all the tags for a well
# useful to check whether they are unique
def tags
libraries.collect(&:tag_id)
all_libraries.collect(&:tag_id)
end

def pools?
pools.present?
end

def libraries?
libraries.present?
end

def template_prep_kit_box_barcode
pools? ? pools.first.template_prep_kit_box_barcode : ''
if pools?
pools.first.template_prep_kit_box_barcode
elsif libraries?
libraries.first.template_prep_kit_box_barcode
end
end

def insert_size
pools? ? pools.first.insert_size : ''
if pools?
pools.first.insert_size
elsif libraries?
libraries.first.insert_size
end
end

def collection?
Expand Down
13 changes: 13 additions & 0 deletions app/models/pacbio/well_library.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# frozen_string_literal: true

module Pacbio
# Pacbio::WellLibrary
# A well can contain many libraries
# A library can belong in many wells
class WellLibrary < ApplicationRecord
belongs_to :well, class_name: 'Pacbio::Well', foreign_key: :pacbio_well_id,
inverse_of: :well_libraries
belongs_to :library, class_name: 'Pacbio::Library', foreign_key: :pacbio_library_id,
inverse_of: :well_libraries
end
end
18 changes: 15 additions & 3 deletions app/models/tube.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,21 @@ class Tube < ApplicationRecord
scope :by_barcode, ->(*barcodes) { where(barcode: barcodes) }
scope :by_pipeline,
lambda { |pipeline|
joins(:container_materials).where(
'container_materials.material_type LIKE ?', "#{pipeline.capitalize}::%"
)
case pipeline
when :pacbio
left_outer_joins(:pacbio_pools, :pacbio_library, :pacbio_requests).where(
'pacbio_pools.id IS NOT NULL OR pacbio_libraries.id IS NOT NULL OR
pacbio_requests.id IS NOT NULL'
)
when :ont
left_outer_joins(:ont_pools, :ont_requests).where(
'ont_pools.id IS NOT NULL OR ont_requests.id IS NOT NULL'
)
else
joins(:container_materials).where(
'container_materials.material_type LIKE ?', "#{pipeline.capitalize}::%"
)
end
}

def identifier
Expand Down
13 changes: 13 additions & 0 deletions app/resources/v1/pacbio/aliquot_resource.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# frozen_string_literal: true

module V1
module Pacbio
# AliquotResource
class AliquotResource < JSONAPI::Resource
model_name '::Aliquot'

attributes :aliquot_type, :source, :used_by, :state,
:volume, :concentration, :insert_size, :template_prep_kit_box_barcode, :tag_id
end
end
end
45 changes: 45 additions & 0 deletions app/resources/v1/pacbio/library_pool_resource.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# frozen_string_literal: true

module V1
module Pacbio
# A temporary PacBio Library resource that returns all libraries
# This allows for pool libraries to be retrieved from the pools resource
class LibraryPoolResource < JSONAPI::Resource
include Shared::RunSuitability

model_name 'Pacbio::Library'

attributes :state, :volume, :concentration, :template_prep_kit_box_barcode,
:insert_size, :created_at, :deactivated_at, :source_identifier,
:pacbio_request_id, :tag_id

has_one :request, always_include_optional_linkage_data: true
# If we don't specify the relation_name here, jsonapi-resources
# attempts to use_related_resource_records_for_joins
# In this case I can see it using container_associations
# so seems to be linking the wrong tube relationship.
has_one :tag, always_include_optional_linkage_data: true
has_one :pool, always_include_optional_linkage_data: true
has_one :tube, relation_name: :tube, always_include_optional_linkage_data: true
has_one :source_well, relation_name: :source_well, class_name: 'Well'
has_one :source_plate, relation_name: :source_plate, class_name: 'Plate'

has_one :primary_aliquot, always_include_optional_linkage_data: true,
relation_name: :primary_aliquot, class_name: 'Aliquot'

def self.records_for_populate(*_args)
super.preload(source_well: :plate, request: :sample,
tag: :tag_set,
container_material: { container: :barcode })
end

def created_at
@model.created_at.to_fs(:us)
end

def deactivated_at
@model&.deactivated_at&.to_fs(:us)
end
end
end
end
Loading

0 comments on commit c69207f

Please sign in to comment.