Class: Sample
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- Sample
- Extended by:
- EventfulRecord, IncludeTag, Metadata, ValidationStateGuard
- Includes:
- Aliquot::Aliquotable, Api::SampleIo::Extensions, Commentable, ModelExtensions::Sample, Role::Authorized, SharedBehaviour::Named, StandardNamedScopes, Uuid::Uuidable
- Defined in:
- app/models/sample.rb
Overview
A Sample is an abstract concept, with represents the life of a sample of DNA/RNA as it moves through our processes. As a result, a sample may exist in multiple receptacles at the same time, in the form of an Aliquot. As a result Sample is mainly concerned with dealing with aspects which are always true, such as tracking where it originally came from.
An individual sample may be subject to library creation and sequencing multiple different times. These processes may be different each time.
Sample Creation
Samples can enter Sequencescape via a number of different routes. Such as: - SampleManifest: Large spreadsheets of sample information are generated. When uploaded samples are created in the corresponding Receptacle. - Heron: Heron samples get registered via the Api::V2::Heron::PlatesController - Special samples: Samples such as PhiX are generated internally
Defined Under Namespace
Constant Summary collapse
- GC_CONTENTS =
['Neutral', 'High AT', 'High GC'].freeze
- GENDERS =
['Male', 'Female', 'Mixed', 'Hermaphrodite', 'Unknown', 'Not Applicable'].freeze
- DNA_SOURCES =
[ 'Genomic', 'Whole Genome Amplified', 'Blood', 'Cell Line', 'Saliva', 'Brain', 'FFPE', 'Amniocentesis Uncultured', 'Amniocentesis Cultured', 'CVS Uncultured', 'CVS Cultured', 'Fetal Blood', 'Tissue' ].freeze
- SRA_HOLD_VALUES =
%w[Hold Public Protect].freeze
- AGE_REGEXP =
'\d+(?:\.\d+|\-\d+|\.\d+\-\d+\.\d+|\.\d+\-\d+\.\d+)?\s+(?:second|minute|day|week|month|year)s?|Not Applicable|N/A|To be provided'- DOSE_REGEXP =
rubocop:enable Layout/LineLength
'\d+(?:\.\d+)?\s+\w+(?:\/\w+)?|Not Applicable|N/A|To be provided'- REMAPPED_ATTRIBUTES =
The spreadsheets that people upload contain various fields that could be mistyped. Here we ensure that the capitalisation of these is correct.
{ gc_content: GC_CONTENTS, gender: GENDERS, dna_source: DNA_SOURCES, sample_sra_hold: SRA_HOLD_VALUES }.transform_values { |v| v.index_by { |b| b.downcase } }
Constants included from Metadata
Constants included from StandardNamedScopes
StandardNamedScopes::SORT_FIELDS, StandardNamedScopes::SORT_ORDERS
Instance Attribute Summary collapse
-
#current_user ⇒ Object
For attributing accessioning changes recorded in the SS events table.
-
#empty_supplier_sample_name ⇒ Boolean
deprecated
Deprecated.
Only set on older samples where samples were created at manifest generation, rather than upload
Class Method Summary collapse
Instance Method Summary collapse
-
#accession_and_handle_validation_errors ⇒ Object
NOTE: this does not check whether the current user is permitted to accession the sample, nor if accessioning is enabled, as these belong in a controller or library, rather than the model.
- #accession_number? ⇒ Boolean
-
#can_be_included_in_submission? ⇒ Boolean
if sample is registered through sample manifest it should have supplier sample name (without it the row is considered empty) if sample was registered directly, only sample name is a required field, so supplier sample name can be empty but it is reasonably safe to assume that required metadata was provided.
- #control_formatted ⇒ Object
- #current_accession_status ⇒ Object
- #ebi_accession_number ⇒ Object
- #ena_study ⇒ Object
- #error ⇒ Object
- #friendly_name ⇒ Object
- #handle_update_event(user) ⇒ Object
- #name_unchanged ⇒ Object
-
#registered_through_manifest? ⇒ Boolean
sample can either be registered through sample manifest, historically through studies/:id/sample_registration or via external services like Heron.
-
#rename_to!(new_name) ⇒ Object
this method has to be before validation_guarded_by.
- #sample_empty?(supplier_sample_name = name) ⇒ Boolean
- #sample_reference_genome ⇒ Object
- #sample_supplier_name_empty?(supplier_sample_name) ⇒ Boolean
-
#shorten_sanger_sample_id ⇒ Object
Truncates the sanger_sample_id for display on labels - Returns the sanger_sample_id AS IS if it is nil or less than 10 characters - Tries to truncate it to the last 7 digits, and returns that - If it cannot extract 7 digits, the full sanger_sample_id is returned Earlier implementations were supposed to fall back to the name in the absence of a sanger_sample_id, but the feature was incorrectly implemented, and would have thrown an exception.
-
#should_be_accessioned? ⇒ Boolean
Criteria for whether a sample should be accessioned.
-
#studies_for_accessioning ⇒ Array<Study>
Returns an array of studies linked to this sample that are eligible for accessioning A study is eligible for accessioning if: - it is active - it is set to open or managed - it is not set to never release - it requires accessioning - it has an accession number.
- #study_for_accessioning ⇒ Object
- #subject_type ⇒ Object
-
#validate_sample_for_accessioning! ⇒ Object
Validates that the sample and it's study are valid for ALL accessioning services accessioning.
Methods included from EventfulRecord
has_many_events, has_many_lab_events, has_one_event_with_family
Methods included from ValidationStateGuard
validation_guard, validation_guarded_by
Methods included from Metadata
Methods included from IncludeTag
Methods included from Commentable
Methods included from Aliquot::Aliquotable
Methods included from SharedBehaviour::Named
Methods included from StandardNamedScopes
Methods included from Uuid::Uuidable
included, #unsaved_uuid!, #uuid
Methods included from Api::SampleIo::Extensions
Methods inherited from ApplicationRecord
alias_association, convert_labware_to_receptacle_for, find_by_id_or_name, find_by_id_or_name!
Methods included from Squishify
Instance Attribute Details
#current_user ⇒ Object
For attributing accessioning changes recorded in the SS events table
289 290 291 |
# File 'app/models/sample.rb', line 289 def current_user @current_user end |
#empty_supplier_sample_name ⇒ Boolean
Only set on older samples where samples were created at manifest generation, rather than upload
Returns true if the customer didn't fill in the supplier_sample_name. Indicating that there is actually no sample in the well.
|
|
# File 'app/models/sample.rb', line 77
|
Class Method Details
.tags ⇒ Object
217 218 219 |
# File 'app/models/sample.rb', line 217 def self. @tags ||= [] end |
Instance Method Details
#accession_and_handle_validation_errors ⇒ Object
NOTE: this does not check whether the current user is permitted to accession the sample, nor if accessioning is enabled, as these belong in a controller or library, rather than the model.
553 554 555 556 557 558 559 560 561 562 563 |
# File 'app/models/sample.rb', line 553 def accession_and_handle_validation_errors event_user = current_user # the event_user for this sample must be set from the calling controller Accession.accession_sample(self, event_user, perform_now: true) # Save error messages for later feedback to the user in a flash message rescue Accession::InternalValidationError # validation errors have already been added to the sample in Accession::Sample.validate! rescue Accession::Error, Faraday::Error => e = Accession.(e) errors.add(:base, ) end |
#accession_number? ⇒ Boolean
496 497 498 |
# File 'app/models/sample.rb', line 496 def accession_number? ebi_accession_number.present? end |
#can_be_included_in_submission? ⇒ Boolean
if sample is registered through sample manifest it should have supplier sample name (without it the row is considered empty) if sample was registered directly, only sample name is a required field, so supplier sample name can be empty but it is reasonably safe to assume that required metadata was provided
628 629 630 |
# File 'app/models/sample.rb', line 628 def can_be_included_in_submission? registered_through_manifest? ? .supplier_name.present? : true end |
#control_formatted ⇒ Object
632 633 634 635 636 637 638 639 |
# File 'app/models/sample.rb', line 632 def control_formatted return nil if control.nil? return 'No' if control == false type_text = control_type || 'type unspecified' "Yes (#{type_text})" end |
#current_accession_status ⇒ Object
587 588 589 |
# File 'app/models/sample.rb', line 587 def current_accession_status accession_sample_statuses.last end |
#ebi_accession_number ⇒ Object
492 493 494 |
# File 'app/models/sample.rb', line 492 def ebi_accession_number .sample_ebi_accession_number end |
#ena_study ⇒ Object
569 570 571 |
# File 'app/models/sample.rb', line 569 def ena_study studies.first end |
#error ⇒ Object
500 501 502 |
# File 'app/models/sample.rb', line 500 def error 'Default error message' end |
#friendly_name ⇒ Object
608 609 610 |
# File 'app/models/sample.rb', line 608 def friendly_name sanger_sample_id || name end |
#handle_update_event(user) ⇒ Object
565 566 567 |
# File 'app/models/sample.rb', line 565 def handle_update_event(user) events.updated_using_sample_manifest!(user) end |
#name_unchanged ⇒ Object
612 613 614 615 |
# File 'app/models/sample.rb', line 612 def name_unchanged errors.add(:name, 'cannot be changed') unless can_rename_sample can_rename_sample end |
#registered_through_manifest? ⇒ Boolean
sample can either be registered through sample manifest, historically through studies/:id/sample_registration or via external services like Heron
620 621 622 |
# File 'app/models/sample.rb', line 620 def registered_through_manifest? sample_manifest.present? end |
#rename_to!(new_name) ⇒ Object
this method has to be before validation_guarded_by
385 386 387 |
# File 'app/models/sample.rb', line 385 def rename_to!(new_name) update!(name: new_name) end |
#sample_empty?(supplier_sample_name = name) ⇒ Boolean
504 505 506 507 508 |
# File 'app/models/sample.rb', line 504 def sample_empty?(supplier_sample_name = name) return true if empty_supplier_sample_name sample_supplier_name_empty?(supplier_sample_name) end |
#sample_reference_genome ⇒ Object
591 592 593 594 595 596 |
# File 'app/models/sample.rb', line 591 def sample_reference_genome return .reference_genome if .reference_genome.try(:name).present? return study_reference_genome if study_reference_genome.try(:name).present? nil end |
#sample_supplier_name_empty?(supplier_sample_name) ⇒ Boolean
510 511 512 513 |
# File 'app/models/sample.rb', line 510 def sample_supplier_name_empty?(supplier_sample_name) supplier_sample_name.blank? || ['empty', 'blank', 'water', 'no supplier name available', 'none'].include?(supplier_sample_name.downcase) end |
#shorten_sanger_sample_id ⇒ Object
This appears to be set up to handle legacy data. All currently generated Sanger sample ids will be meet criteria 1 or 2.
Truncates the sanger_sample_id for display on labels - Returns the sanger_sample_id AS IS if it is nil or less than 10 characters - Tries to truncate it to the last 7 digits, and returns that - If it cannot extract 7 digits, the full sanger_sample_id is returned Earlier implementations were supposed to fall back to the name in the absence of a sanger_sample_id, but the feature was incorrectly implemented, and would have thrown an exception.
479 480 481 482 483 484 485 486 487 488 489 490 |
# File 'app/models/sample.rb', line 479 def shorten_sanger_sample_id case sanger_sample_id when nil sanger_sample_id when sanger_sample_id.size < 10 sanger_sample_id when /(\d{7})$/ Regexp.last_match(1) else sanger_sample_id end end |
#should_be_accessioned? ⇒ Boolean
Criteria for whether a sample should be accessioned. A sample should be accessioned if: - it is part of a single accessionable study - that study is active - that study is set to open or managed - that study is set to be released - that study requires accessioning - that study has an accession number
536 537 538 539 540 541 542 543 544 545 546 547 548 549 |
# File 'app/models/sample.rb', line 536 def should_be_accessioned? # If updating this method, please also update app/views/samples/_studies.html.erb accessioning_criteria = [ studies_for_accessioning.size == 1 ] return true if accessioning_criteria.all? Rails.logger.debug do "Sample '#{name}' should not be accessioned as it " \ "belongs to #{studies_for_accessioning.size} accessionable studies." end false end |
#studies_for_accessioning ⇒ Array<Study>
Returns an array of studies linked to this sample that are eligible for accessioning A study is eligible for accessioning if: - it is active - it is set to open or managed - it is not set to never release - it requires accessioning - it has an accession number
523 524 525 |
# File 'app/models/sample.rb', line 523 def studies_for_accessioning studies.select(&:samples_accessionable?) end |
#study_for_accessioning ⇒ Object
573 574 575 576 |
# File 'app/models/sample.rb', line 573 def study_for_accessioning # There should only be one study for accessioning, if we want to accession, so we return the only one studies_for_accessioning.first end |
#subject_type ⇒ Object
598 599 600 |
# File 'app/models/sample.rb', line 598 def subject_type 'sample' end |
#validate_sample_for_accessioning! ⇒ Object
Validates that the sample and it's study are valid for ALL accessioning services accessioning
579 580 581 582 583 584 585 |
# File 'app/models/sample.rb', line 579 def validate_sample_for_accessioning! accession_service = AccessionService.select_for_sample(self) (valid?(:accession) && valid?(accession_service.provider)) || raise(ActiveRecord::RecordInvalid, self) rescue ActiveRecord::RecordInvalid => e ena_study.errors..each { || errors.add(:base, "#{} on study") } unless ena_study.nil? raise e end |