Skip to content

Commit c69207f

Browse files
authored
Merge pull request #1219 from sanger/pacbio-multiplexing-epic
[EPIC] PacBio multiplexing
2 parents 6059b96 + c9a8ab3 commit c69207f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+1301
-365
lines changed

app/exchanges/run_csv/deprecated_pacbio_sample_sheet.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def csv_headers
4242
end
4343

4444
def csv_sample_rows(well)
45-
well.libraries.map do |library|
45+
well.all_libraries.map do |library|
4646
# add row under well header for each sample in the well
4747
csv_data(sample: library, well:, row_type: :sample)
4848
end

app/models/aliquot.rb

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,15 @@ class Aliquot < ApplicationRecord
1010

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

1616
# currently I have set these to be validated but not sure
1717
# as library only validates when a run is created
1818
# maybe we need to do this when the state is set to used?
1919
# requests currently dont support these fields so we skip validation on primary aliquots
20-
validates :volume, :concentration, :template_prep_kit_box_barcode, :insert_size,
20+
# we dont validate insert size as it may not be known at the time of creation
21+
validates :volume, :concentration, :template_prep_kit_box_barcode,
2122
presence: true,
2223
unless: -> { source.is_a?(Pacbio::Request) && aliquot_type == 'primary' }
2324
validates :volume, :concentration, :insert_size,

app/models/concerns/aliquotable.rb

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,13 @@ module Aliquotable
55
extend ActiveSupport::Concern
66

77
included do
8-
has_many :aliquots, as: :source, dependent: :nullify
9-
end
10-
11-
def primary_aliquot
12-
# Something fundamentally wrong if we have more than one
13-
aliquots.find_by(aliquot_type: :primary)
14-
end
15-
16-
def derived_aliquots
17-
aliquots.where(aliquot_type: :derived)
8+
has_many :aliquots, as: :source, dependent: :destroy
9+
has_many :used_aliquots, as: :used_by, dependent: :destroy, class_name: 'Aliquot'
10+
has_one :primary_aliquot, -> { where(aliquot_type: :primary) },
11+
as: :source, class_name: 'Aliquot',
12+
dependent: :destroy, inverse_of: :source
13+
has_many :derived_aliquots, -> { where(aliquot_type: :derived) },
14+
as: :source, class_name: 'Aliquot',
15+
dependent: :nullify, inverse_of: :source
1816
end
1917
end

app/models/concerns/sample_sheet.rb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,20 +35,20 @@ def barcode_set
3535

3636
# Determines rendering of a row-per sample
3737
def show_row_per_sample?
38-
sample_sheet_behaviour.show_row_per_sample?(libraries)
38+
sample_sheet_behaviour.show_row_per_sample?(all_libraries)
3939
end
4040

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

45-
libraries
45+
all_libraries
4646
end
4747

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

5454
# find the plate given the plate_number

app/models/pacbio/library.rb

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,22 @@ class Library < ApplicationRecord
1313
include Uuidable
1414
include Librarian
1515
include SampleSheet::Library
16+
include Aliquotable
1617

1718
validates :volume, :concentration,
1819
:insert_size, presence: true, on: :run_creation
1920
validates :volume, :concentration,
2021
:insert_size, numericality: { greater_than_or_equal_to: 0, allow_nil: true }
2122

23+
has_many :well_libraries, class_name: 'Pacbio::WellLibrary', foreign_key: :pacbio_library_id,
24+
dependent: :nullify, inverse_of: :library
25+
has_many :wells, class_name: 'Pacbio::Well', through: :well_libraries
26+
2227
belongs_to :request, class_name: 'Pacbio::Request', foreign_key: :pacbio_request_id,
2328
inverse_of: :libraries
2429
belongs_to :tag, optional: true
2530
belongs_to :pool, class_name: 'Pacbio::Pool', foreign_key: :pacbio_pool_id,
26-
inverse_of: :libraries
31+
inverse_of: :libraries, optional: true
2732
belongs_to :tube, optional: true
2833

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

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

43+
# TODO: remove pool constraint this when pools are updated for aliquots
44+
validates :primary_aliquot, presence: true, if: -> { pool.blank? }
45+
accepts_nested_attributes_for :primary_aliquot, allow_destroy: true
46+
47+
after_create :create_used_aliquot, :create_tube, if: -> { pool.blank? }
48+
before_destroy :check_for_associated_wells, prepend: true
49+
50+
def create_used_aliquot
51+
used_aliquots.create(
52+
source: request,
53+
aliquot_type: :derived,
54+
volume:,
55+
concentration:,
56+
template_prep_kit_box_barcode:,
57+
insert_size:,
58+
tag:
59+
)
60+
end
61+
62+
def create_tube
63+
self.tube = tube || Tube.create!
64+
save
65+
end
66+
67+
def tube
68+
pool ? pool.tube : super
69+
end
70+
3871
def collection?
3972
false
4073
end
@@ -43,6 +76,30 @@ def sample_sheet_behaviour
4376
SampleSheetBehaviour.get(tag_set&.sample_sheet_behaviour || :untagged)
4477
end
4578

46-
delegate :sequencing_plates, to: :pool
79+
# @return [Array] of Runs that the pool is used in
80+
def sequencing_runs
81+
wells&.collect(&:run)&.uniq
82+
end
83+
84+
# @return [Array] of Plates attached to a sequencing run
85+
def sequencing_plates
86+
# TODO: remove this when pools are updated for aliquots
87+
if pool
88+
pool.sequencing_plates
89+
else
90+
wells&.collect(&:plate)
91+
end
92+
end
93+
94+
private
95+
96+
def check_for_associated_wells
97+
if wells.empty?
98+
true
99+
else
100+
errors.add(:base, 'Cannot delete a library that is associated with wells')
101+
throw(:abort)
102+
end
103+
end
47104
end
48105
end

app/models/pacbio/well.rb

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,9 @@ class Well < ApplicationRecord
1414
has_many :well_pools, class_name: 'Pacbio::WellPool', foreign_key: :pacbio_well_id,
1515
dependent: :destroy, inverse_of: :well, autosave: true
1616
has_many :pools, class_name: 'Pacbio::Pool', through: :well_pools
17-
18-
has_many :aliquots, foreign_key: :pacbio_well_id, inverse_of: :well, dependent: :destroy,
19-
autosave: true
20-
has_many :libraries, through: :pools
21-
has_many :tag_sets, through: :libraries
17+
has_many :well_libraries, class_name: 'Pacbio::WellLibrary', foreign_key: :pacbio_well_id,
18+
dependent: :destroy, inverse_of: :well, autosave: true
19+
has_many :libraries, class_name: 'Pacbio::Library', through: :well_libraries
2220

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

5351
validates :row, :column, presence: true
5452

55-
validates :pools, length: {
56-
minimum: 1,
57-
message: :well_min_pools
58-
}
59-
6053
delegate :run, to: :plate, allow_nil: true
6154

6255
def tag_set
63-
tag_sets.first
56+
all_libraries.collect(&:tag_set).first
6457
end
6558

6659
def sample_sheet_behaviour
@@ -75,6 +68,11 @@ def summary
7568
"#{sample_names} #{comment}".strip
7669
end
7770

71+
# A collection of all the libraries for a well
72+
def all_libraries
73+
pools.collect(&:libraries).flatten + libraries
74+
end
75+
7876
# collection of all of the requests for a library
7977
# useful for messaging
8078
def request_libraries
@@ -84,25 +82,37 @@ def request_libraries
8482
# a collection of all the sample names for a particular well
8583
# useful for comments
8684
def sample_names(separator = ':')
87-
libraries.collect(&:request).collect(&:sample_name).join(separator)
85+
all_libraries.collect(&:request).collect(&:sample_name).join(separator)
8886
end
8987

9088
# a collection of all the tags for a well
9189
# useful to check whether they are unique
9290
def tags
93-
libraries.collect(&:tag_id)
91+
all_libraries.collect(&:tag_id)
9492
end
9593

9694
def pools?
9795
pools.present?
9896
end
9997

98+
def libraries?
99+
libraries.present?
100+
end
101+
100102
def template_prep_kit_box_barcode
101-
pools? ? pools.first.template_prep_kit_box_barcode : ''
103+
if pools?
104+
pools.first.template_prep_kit_box_barcode
105+
elsif libraries?
106+
libraries.first.template_prep_kit_box_barcode
107+
end
102108
end
103109

104110
def insert_size
105-
pools? ? pools.first.insert_size : ''
111+
if pools?
112+
pools.first.insert_size
113+
elsif libraries?
114+
libraries.first.insert_size
115+
end
106116
end
107117

108118
def collection?

app/models/pacbio/well_library.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# frozen_string_literal: true
2+
3+
module Pacbio
4+
# Pacbio::WellLibrary
5+
# A well can contain many libraries
6+
# A library can belong in many wells
7+
class WellLibrary < ApplicationRecord
8+
belongs_to :well, class_name: 'Pacbio::Well', foreign_key: :pacbio_well_id,
9+
inverse_of: :well_libraries
10+
belongs_to :library, class_name: 'Pacbio::Library', foreign_key: :pacbio_library_id,
11+
inverse_of: :well_libraries
12+
end
13+
end

app/models/tube.rb

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,21 @@ class Tube < ApplicationRecord
2020
scope :by_barcode, ->(*barcodes) { where(barcode: barcodes) }
2121
scope :by_pipeline,
2222
lambda { |pipeline|
23-
joins(:container_materials).where(
24-
'container_materials.material_type LIKE ?', "#{pipeline.capitalize}::%"
25-
)
23+
case pipeline
24+
when :pacbio
25+
left_outer_joins(:pacbio_pools, :pacbio_library, :pacbio_requests).where(
26+
'pacbio_pools.id IS NOT NULL OR pacbio_libraries.id IS NOT NULL OR
27+
pacbio_requests.id IS NOT NULL'
28+
)
29+
when :ont
30+
left_outer_joins(:ont_pools, :ont_requests).where(
31+
'ont_pools.id IS NOT NULL OR ont_requests.id IS NOT NULL'
32+
)
33+
else
34+
joins(:container_materials).where(
35+
'container_materials.material_type LIKE ?', "#{pipeline.capitalize}::%"
36+
)
37+
end
2638
}
2739

2840
def identifier
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# frozen_string_literal: true
2+
3+
module V1
4+
module Pacbio
5+
# AliquotResource
6+
class AliquotResource < JSONAPI::Resource
7+
model_name '::Aliquot'
8+
9+
attributes :aliquot_type, :source, :used_by, :state,
10+
:volume, :concentration, :insert_size, :template_prep_kit_box_barcode, :tag_id
11+
end
12+
end
13+
end
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# frozen_string_literal: true
2+
3+
module V1
4+
module Pacbio
5+
# A temporary PacBio Library resource that returns all libraries
6+
# This allows for pool libraries to be retrieved from the pools resource
7+
class LibraryPoolResource < JSONAPI::Resource
8+
include Shared::RunSuitability
9+
10+
model_name 'Pacbio::Library'
11+
12+
attributes :state, :volume, :concentration, :template_prep_kit_box_barcode,
13+
:insert_size, :created_at, :deactivated_at, :source_identifier,
14+
:pacbio_request_id, :tag_id
15+
16+
has_one :request, always_include_optional_linkage_data: true
17+
# If we don't specify the relation_name here, jsonapi-resources
18+
# attempts to use_related_resource_records_for_joins
19+
# In this case I can see it using container_associations
20+
# so seems to be linking the wrong tube relationship.
21+
has_one :tag, always_include_optional_linkage_data: true
22+
has_one :pool, always_include_optional_linkage_data: true
23+
has_one :tube, relation_name: :tube, always_include_optional_linkage_data: true
24+
has_one :source_well, relation_name: :source_well, class_name: 'Well'
25+
has_one :source_plate, relation_name: :source_plate, class_name: 'Plate'
26+
27+
has_one :primary_aliquot, always_include_optional_linkage_data: true,
28+
relation_name: :primary_aliquot, class_name: 'Aliquot'
29+
30+
def self.records_for_populate(*_args)
31+
super.preload(source_well: :plate, request: :sample,
32+
tag: :tag_set,
33+
container_material: { container: :barcode })
34+
end
35+
36+
def created_at
37+
@model.created_at.to_fs(:us)
38+
end
39+
40+
def deactivated_at
41+
@model&.deactivated_at&.to_fs(:us)
42+
end
43+
end
44+
end
45+
end

0 commit comments

Comments
 (0)