Class: Plate

Overview

[plate_image]: A plate is a piece of labware made up of a number of wells. This class represents the physical piece of plastic.

plate_image
  • PlatePuprose: describes the role a plate has in the lab. In some cases a plate's purpose may change as it gets processed.

  • Well: Plates can have multiple wells (most often 96 or 384) each of which can contain multiple samples.

  • PlateType: Identifies the plates form factor, typically provided by robots to ensure tips are positioned correctly.

Defined Under Namespace

Modules: FluidigmBehaviour Classes: Creator, CreatorParameters, SampleTubeFactory

Constant Summary

Constants included from Metadata

Metadata::SECTION_FIELDS

Instance Attribute Summary

Attributes included from AssetRefactor::Labware::Methods

#storage_location_service

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Metadata

has_metadata, required_tags

Methods included from QcFile::Associations

has_qc_files

Methods included from AssetRefactor::Labware::Methods

#labware, #labwhere_location, #scanned_in_date, #storage_location

Methods included from PlateCreation::CreationChild

included

Methods included from SubmissionPool::Association::Plate

included

Methods included from FluidigmBehaviour

#apply_fluidigm_data, included, #retrieve_fluidigm_data

Methods included from Asset::Ownership::Owned

#change_owner_to, included

Methods included from Barcode::Barcodeable

#aker_barcode, #aker_barcode=, #any_barcode_matching?, #barcode_format, #barcode_number, #cgap_barcode, #cgap_barcode=, #external_barcode, #external_barcode=, #external_identifier, #fluidigm_barcode, #fluidigm_barcode=, included, #infinium_barcode, #infinium_barcode=, #prefix, #primary_barcode, #printable_target, #sanger_barcode

Methods included from Transfer::State::PlateState

included

Methods included from Transfer::Associations

included

Methods included from Api::PlateIO::Extensions

included, #json_root

Methods inherited from Labware

#display_name, #generate_name, #human_barcode

Methods inherited from Asset

#add_parent, #asset_type_for_request_types, #assign_relationships, #barcode_number, #contained_samples, #external_identifier, #get_qc_result_value_for, #has_stock_asset?, #label, #label=, #original_stock_plates, #prefix, #printable?, #printable_target, #register_stock!, #request_types, #role, #spiked_in_buffer, #type, #update_from_qc

Methods included from EventfulRecord

#has_many_events, #has_many_lab_events, #has_one_event_with_family

Methods included from Event::PlateEvents

#event_date, #fluidigm_stamp_date, #gel_qc_date, #pico_date, #qc_started_date, #sequenom_stamp_date

Methods inherited from ApplicationRecord

convert_labware_to_receptacle_for, find_by_id_or_name, find_by_id_or_name!

Methods included from Warren::BroadcastMessages

#broadcast, included, #queue_associated_for_broadcast, #queue_for_broadcast, #warren

Class Method Details

.create_with_barcode!(*args, &block) ⇒ Object


376
377
378
379
380
# File 'app/models/plate.rb', line 376

def self.create_with_barcode!(*args, &block)
  attributes = args.extract_options!
  attributes[:sanger_barcode] = safe_sanger_barcode(attributes[:sanger_barcode] || {})
  create!(attributes, &block)
end

.plate_ids_from_requests(requests) ⇒ Object


350
351
352
# File 'app/models/plate.rb', line 350

def self.plate_ids_from_requests(requests)
  with_requests(requests).pluck(:id)
end

.safe_sanger_barcode(sanger_barcode) ⇒ Object


382
383
384
385
386
387
388
# File 'app/models/plate.rb', line 382

def self.safe_sanger_barcode(sanger_barcode)
  if sanger_barcode[:number].blank? || Barcode.sanger_barcode(sanger_barcode[:prefix], sanger_barcode[:number]).exists?
    { number: PlateBarcode.create.barcode, prefix: sanger_barcode[:prefix] }
  else
    sanger_barcode
  end
end

.search_for_plates(params) ⇒ Object


286
287
288
289
290
291
292
293
# File 'app/models/plate.rb', line 286

def self.search_for_plates(params)
  with_faculty_sponsor_ids(params[:faculty_sponsor_ids] || nil)
    .with_study_id(params[:study_id] || nil)
    .with_plate_purpose_ids(params[:plate_purpose_ids] || nil)
    .created_between(params[:start_date], params[:end_date])
    .filter_by_barcode(params[:barcodes] || nil)
    .distinct
end

Instance Method Details

#after_comment_addition(comment) ⇒ Object


493
494
495
# File 'app/models/plate.rb', line 493

def after_comment_addition(comment)
  comments.add_comment_to_submissions(comment)
end

#all_submission_idsObject

Prioritised the submissions that have been made from the plate then falls back onto the ones under which the plate was made


197
198
199
# File 'app/models/plate.rb', line 197

def all_submission_ids
  submission_ids_as_source.presence || submission_ids
end

#ancestor_of_purpose(ancestor_purpose_id) ⇒ Object


364
365
366
367
368
# File 'app/models/plate.rb', line 364

def ancestor_of_purpose(ancestor_purpose_id)
  return self if plate_purpose_id == ancestor_purpose_id

  ancestors.order(created_at: :desc).find_by(plate_purpose_id: ancestor_purpose_id)
end

#ancestors_of_purpose(ancestor_purpose_id) ⇒ Object


370
371
372
373
374
# File 'app/models/plate.rb', line 370

def ancestors_of_purpose(ancestor_purpose_id)
  return [self] if plate_purpose_id == ancestor_purpose_id

  ancestors.order(created_at: :desc).where(plate_purpose_id: ancestor_purpose_id)
end

#buffer_required?Boolean


398
399
400
# File 'app/models/plate.rb', line 398

def buffer_required?
  wells.any?(&:buffer_required?)
end

#cherrypick_completedObject


155
156
157
# File 'app/models/plate.rb', line 155

def cherrypick_completed
  plate_purpose.cherrypick_completed(self)
end

#commentsObject


219
220
221
# File 'app/models/plate.rb', line 219

def comments
  @comments ||= CommentsProxy::Plate.new(self)
end

#compatible_purposesObject


445
446
447
# File 'app/models/plate.rb', line 445

def compatible_purposes
  PlatePurpose.compatible_with_purpose(purpose)
end

#convert_to(new_purpose) ⇒ Object


441
442
443
# File 'app/models/plate.rb', line 441

def convert_to(new_purpose)
  update!(plate_purpose: new_purpose)
end

#detailsObject


342
343
344
# File 'app/models/plate.rb', line 342

def details
  purpose.try(:name) || 'Unknown plate purpose'
end

#find_well_by_name(well_name) ⇒ Object Also known as: find_well_by_map_description


317
318
319
320
321
322
323
# File 'app/models/plate.rb', line 317

def find_well_by_name(well_name)
  if wells.loaded?
    wells.indexed_by_location[well_name]
  else
    wells.located_at_position(well_name).first
  end
end

#generate_barcodeObject

Plates use a different counter to tubes, and prior to the foreign barcodes update this method would have fallen back to Barcodable#generate tubes, and potentially generated an invalid plate barcode. In the future we probably want to scrap this approach entirely, and generate all barcodes in the plate style. (That is, as part of the factory on, eg. plate purpose)

Raises:

  • (StandardError)

485
486
487
# File 'app/models/plate.rb', line 485

def generate_barcode
  raise StandardError, "#generate_barcode has been called on plate, which wasn't supposed to happen, and probably indicates a bug."
end

#heightObject


418
419
420
# File 'app/models/plate.rb', line 418

def height
  asset_shape.plate_height(size)
end

#iterationObject


205
206
207
208
209
210
211
212
# File 'app/models/plate.rb', line 205

def iteration
  iter = siblings # assets sharing the same parent
         .where(plate_purpose_id: plate_purpose_id, sti_type: sti_type) # of the same purpose and type
         .where("#{self.class.table_name}.created_at <= ?", created_at) # created before or at the same time
         .count(:id) # count the siblings.

  iter.zero? ? nil : iter # Maintains compatibility with legacy version
end

#labware_typeObject


129
130
131
# File 'app/models/plate.rb', line 129

def labware_type
  super || PlateType.plate_default_type
end

#mapsObject


313
314
315
# File 'app/models/plate.rb', line 313

def maps
  Map.where_plate_size(size).where_plate_shape(asset_shape)
end

#name_for_labelObject


410
411
412
# File 'app/models/plate.rb', line 410

def name_for_label
  name
end

#number_of_blank_samplesObject


390
391
392
# File 'app/models/plate.rb', line 390

def number_of_blank_samples
  wells.with_blank_samples.count
end

#occupied_well_countObject


143
144
145
# File 'app/models/plate.rb', line 143

def occupied_well_count
  wells.with_contents.count
end

#plate_columnsObject


330
331
332
# File 'app/models/plate.rb', line 330

def plate_columns
  (1..width)
end

#plate_rowsObject


326
327
328
# File 'app/models/plate.rb', line 326

def plate_rows
  ('A'..('A'.getbyte(0) + height - 1).chr.to_s).to_a
end

#plate_typeObject


334
335
336
# File 'app/models/plate.rb', line 334

def plate_type
  labware_type.name
end

#plate_type=(plate_type) ⇒ Object


338
339
340
# File 'app/models/plate.rb', line 338

def plate_type=(plate_type)
  self.labware_type = PlateType.find_by(name: plate_type)
end

#priorityObject


223
224
225
226
227
# File 'app/models/plate.rb', line 223

def priority
  waiting_submissions.maximum(:priority) ||
    in_progress_submissions.maximum(:priority) ||
    0
end

497
498
499
# File 'app/models/plate.rb', line 497

def related_studies
  studies
end

#sanger_barcode=(attributes) ⇒ Object


489
490
491
# File 'app/models/plate.rb', line 489

def sanger_barcode=(attributes)
  barcodes << Barcode.build_sanger_code39(attributes)
end

#scored?Boolean


394
395
396
# File 'app/models/plate.rb', line 394

def scored?
  wells.any?(&:get_gel_pass)
end

#source_plateObject


159
160
161
# File 'app/models/plate.rb', line 159

def source_plate
  purpose&.source_plate(self)
end

#stateObject


133
134
135
# File 'app/models/plate.rb', line 133

def state
  plate_purpose.state_of(self)
end

#stock_plateObject


360
361
362
# File 'app/models/plate.rb', line 360

def stock_plate
  @stock_plate ||= stock_plate? ? self : lookup_stock_plate
end

#stock_plate?Boolean


354
355
356
357
358
# File 'app/models/plate.rb', line 354

def stock_plate?
  return true if plate_purpose.nil?

  plate_purpose.stock_plate? && plate_purpose.attatched?(self)
end

#stock_roleObject


346
347
348
# File 'app/models/plate.rb', line 346

def stock_role
  well_requests_as_source.first&.role
end

#stock_wellsObject

This method returns a map from the wells on the plate to their stock well.


427
428
429
430
431
432
433
434
435
436
437
438
439
# File 'app/models/plate.rb', line 427

def stock_wells
  # Optimisation: if the plate is a stock plate then it's wells are it's stock wells!]
  if stock_plate?
    wells.with_pool_id.each_with_object({}) { |w, store| store[w] = [w] }
  else
    wells.include_stock_wells.with_pool_id.each_with_object({}) do |w, store|
      storted_stock_wells = w.stock_wells.sort_by { |sw| sw.map.column_order }
      store[w] = storted_stock_wells unless storted_stock_wells.empty?
    end.tap do |stock_wells_hash|
      raise "No stock plate associated with #{id}" if stock_wells_hash.empty?
    end
  end
end

#subject_typeObject


477
478
479
# File 'app/models/plate.rb', line 477

def subject_type
  'plate'
end

#submission_idsObject


187
188
189
# File 'app/models/plate.rb', line 187

def submission_ids
  @submission_ids ||= in_progress_submissions.ids
end

#submission_ids_as_sourceObject


191
192
193
# File 'app/models/plate.rb', line 191

def submission_ids_as_source
  @submission_ids_as_source ||= waiting_submissions.ids
end

#submissionsObject


201
202
203
# File 'app/models/plate.rb', line 201

def submissions
  waiting_submissions.presence || in_progress_submissions
end

#summary_hashObject


147
148
149
150
151
152
153
# File 'app/models/plate.rb', line 147

def summary_hash
  {
    asset_id: id,
    barcode: { ean13_barcode: ean13_barcode, human_readable: human_barcode },
    occupied_wells: wells.with_aliquots.include_map.map(&:map_description)
  }
end

#teamObject


467
468
469
470
471
472
473
474
# File 'app/models/plate.rb', line 467

def team
  ProductLine.joins([
    'INNER JOIN request_types ON request_types.product_line_id = product_lines.id',
    'INNER JOIN requests ON requests.request_type_id = request_types.id',
    'INNER JOIN well_links ON well_links.source_well_id = requests.asset_id AND well_links.type = "stock"',
    'INNER JOIN container_associations AS ca ON ca.content_id = well_links.target_well_id'
  ]).find_by(['ca.container_id = ?', id]).try(:name) || 'UNKNOWN'
end

#transition_to(state, user, contents = nil, customer_accepts_responsibility = false) ⇒ Object

Delegate the change of state to our plate purpose.


215
216
217
# File 'app/models/plate.rb', line 215

def transition_to(state, user, contents = nil, customer_accepts_responsibility = false)
  purpose.transition_to(self, state, user, contents, customer_accepts_responsibility)
end

#update_qc_values_with_parser(parser) ⇒ Object


453
454
455
456
457
458
459
460
461
462
463
464
465
# File 'app/models/plate.rb', line 453

def update_qc_values_with_parser(parser)
  ActiveRecord::Base.transaction do
    qc_assay = QcAssay.new
    parser.each_well_and_parameters do |position, well_updates|
      # We might have a nil well if a plate was only partially cherrypicked
      well = well_hash[position] || next
      well_updates.each do |attribute, value|
        QcResult.create!(asset: well, key: attribute, unit_value: value, assay_type: parser.assay_type, assay_version: parser.assay_version, qc_assay: qc_assay)
      end
    end
  end
  true
end

#update_volume(volume_change) ⇒ Object


137
138
139
140
141
# File 'app/models/plate.rb', line 137

def update_volume(volume_change)
  ActiveRecord::Base.transaction do
    wells.each { |well| well.update_volume(volume_change) }
  end
end

#valid_positions?(positions) ⇒ Boolean


402
403
404
405
406
407
408
# File 'app/models/plate.rb', line 402

def valid_positions?(positions)
  unique_positions_from_caller = positions.sort.uniq
  unique_positions_on_plate = maps.where_description(unique_positions_from_caller)
                                  .distinct
                                  .pluck(:description).sort
  unique_positions_on_plate == unique_positions_from_caller
end

#well_hashObject


449
450
451
# File 'app/models/plate.rb', line 449

def well_hash
  @well_hash ||= wells.include_map.includes(:well_attribute).index_by(&:map_description)
end

#widthObject


422
423
424
# File 'app/models/plate.rb', line 422

def width
  asset_shape.plate_width(size)
end