Class: Sample
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- Sample
- Extended by:
- EventfulRecord, 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
Instance Method Summary collapse
- #accession(event_user) ⇒ Object
- #accession_and_handle_validation_errors ⇒ Object
- #accession_number? ⇒ Boolean
-
#accession_service ⇒ Object
Return the highest priority accession service.
-
#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.
- #subject_type ⇒ Object
- #validate_ena_required_fields! ⇒ Object
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 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
282 283 284 |
# File 'app/models/sample.rb', line 282 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
|
Instance Method Details
#accession(event_user) ⇒ Object
519 520 521 522 523 524 525 526 527 528 529 530 531 |
# File 'app/models/sample.rb', line 519 def accession(event_user) # Check if study is present and allowed to be accessioned return unless ena_study&.accession_required? # Flag set in the deployment project to allow per-environment enabling of accessioning unless configatron.accession_samples raise AccessionService::AccessioningDisabledError, 'Accessioning is not enabled in this environment.' end accessionable = build_accessionable validate_accessionable!(accessionable) enqueue_accessioning_job!(accessionable, event_user) end |
#accession_and_handle_validation_errors ⇒ Object
533 534 535 536 537 538 539 540 |
# File 'app/models/sample.rb', line 533 def accession_and_handle_validation_errors event_user = current_user # the event_user for this sample must be set from the calling controller accession(event_user) Rails.logger.info("Accessioning passed for sample '#{name}'") rescue AccessionService::AccessionServiceError => e # Save error messages for later feedback to the user in a flash message errors.add(:base, e.) end |
#accession_number? ⇒ Boolean
488 489 490 |
# File 'app/models/sample.rb', line 488 def accession_number? ebi_accession_number.present? end |
#accession_service ⇒ Object
Return the highest priority accession service
508 509 510 511 512 513 514 515 516 517 |
# File 'app/models/sample.rb', line 508 def accession_service services = studies.group_by { |s| s.accession_service.priority } return AccessionService::UnsuitableService.new([]) if services.empty? highest_priority = services.keys.max suitable_study = services[highest_priority].detect(&:send_samples_to_service?) return suitable_study.accession_service if suitable_study AccessionService::UnsuitableService.new(services[highest_priority]) 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
598 599 600 |
# File 'app/models/sample.rb', line 598 def can_be_included_in_submission? registered_through_manifest? ? .supplier_name.present? : true end |
#control_formatted ⇒ Object
602 603 604 605 606 607 608 609 |
# File 'app/models/sample.rb', line 602 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
557 558 559 |
# File 'app/models/sample.rb', line 557 def current_accession_status accession_sample_statuses.last end |
#ebi_accession_number ⇒ Object
484 485 486 |
# File 'app/models/sample.rb', line 484 def ebi_accession_number .sample_ebi_accession_number end |
#ena_study ⇒ Object
546 547 548 |
# File 'app/models/sample.rb', line 546 def ena_study studies.first end |
#error ⇒ Object
492 493 494 |
# File 'app/models/sample.rb', line 492 def error 'Default error message' end |
#friendly_name ⇒ Object
578 579 580 |
# File 'app/models/sample.rb', line 578 def friendly_name sanger_sample_id || name end |
#handle_update_event(user) ⇒ Object
542 543 544 |
# File 'app/models/sample.rb', line 542 def handle_update_event(user) events.updated_using_sample_manifest!(user) end |
#name_unchanged ⇒ Object
582 583 584 585 |
# File 'app/models/sample.rb', line 582 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
590 591 592 |
# File 'app/models/sample.rb', line 590 def registered_through_manifest? sample_manifest.present? end |
#rename_to!(new_name) ⇒ Object
this method has to be before validation_guarded_by
377 378 379 |
# File 'app/models/sample.rb', line 377 def rename_to!(new_name) update!(name: new_name) end |
#sample_empty?(supplier_sample_name = name) ⇒ Boolean
496 497 498 499 500 |
# File 'app/models/sample.rb', line 496 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
561 562 563 564 565 566 |
# File 'app/models/sample.rb', line 561 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
502 503 504 505 |
# File 'app/models/sample.rb', line 502 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.
471 472 473 474 475 476 477 478 479 480 481 482 |
# File 'app/models/sample.rb', line 471 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 |
#subject_type ⇒ Object
568 569 570 |
# File 'app/models/sample.rb', line 568 def subject_type 'sample' end |
#validate_ena_required_fields! ⇒ Object
550 551 552 553 554 555 |
# File 'app/models/sample.rb', line 550 def validate_ena_required_fields! (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 |