Class: Study
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- Study
- Extended by:
- Attributable::Association::Target, EventfulRecord, Metadata
- Includes:
- AASM, Api::StudyIo::Extensions, Commentable, DataRelease, EventfulRecord, ModelExtensions::Study, ReferenceGenome::Associations, Role::Authorized, SampleManifest::Associations, SharedBehaviour::Named, StudyReport::StudyDetails, Uuid::Uuidable
- Defined in:
- app/models/study.rb
Overview
This is really quite convoluted, and couples together administrative organization alongside accessioning and data-access rules. It results in samples being tied to an EGAS/ERP far too early in their lifecycle, and as a result we often need to perform 'sample moves'. Although we do need to know if samples are open(ENA) or managed(EGA) at the point of accessioning.
A Study is a collection of various samples and the work done on them. They are perhaps slightly overloaded, and provide: - A means of grouping together samples for administrative purposes - A means of generating EGAS/ERP study accession numbers at the ENA/EGA - @see Accessionable::Study - These accession numbers are used at data release to group samples together for publication - For managed/EGA studies, also ties the data to an Accessionable::Dac and Accessionable::Policy - A means of generating the aforementioned Accessionable::Dac and Accessionable::Policy @note These should DEFINITELY be separate entities - A means of tying data to internal data-release timings - A means to apply internal data access policies to released sequencing data - A means to tie interested parties to the samples and the work done on them - A way of specifying common ways of filtering/processing generated data. eg. filter human sequence - The service with which a Sample will be accessioned (eg. EGA/ENA)
When a Sample enters Sequencescape it will usually be associated with a single Study, usually determined by the Study associated with the SampleManifest. This study will be recorded on the Aliquot in the stock Receptacle, and additionally a StudySample will record this association.
When work is requested an Order will be created, specifying a list of receptacles and the Study for which this work is being performed. This will set initial study id on request and in turn will be recorded on any downstream aliquots. Critically, it is the study specified on the Aliquot in the Lane which will influence processes like data release and data access.
Defined Under Namespace
Classes: Metadata
Constant Summary collapse
- STOCK_PLATE_PURPOSES =
Constants
['Stock Plate', 'Stock RNA Plate'].freeze
- YES =
'Yes'- NO =
'No'- YES_OR_NO =
[YES, NO].freeze
- OTHER_TYPE =
'Other'- STUDY_SRA_HOLDS =
%w[Hold Public].freeze
- DATA_RELEASE_STRATEGY_OPEN =
'open'- DATA_RELEASE_STRATEGY_MANAGED =
'managed'- DATA_RELEASE_STRATEGY_NOT_APPLICABLE =
'not applicable'- DATA_RELEASE_STRATEGIES =
[ DATA_RELEASE_STRATEGY_OPEN, DATA_RELEASE_STRATEGY_MANAGED, DATA_RELEASE_STRATEGY_NOT_APPLICABLE ].freeze
- DATA_RELEASE_TIMING_STANDARD =
'standard'- DATA_RELEASE_TIMING_NEVER =
'never'- DATA_RELEASE_TIMING_DELAYED =
'delayed'- DATA_RELEASE_TIMING_IMMEDIATE =
'immediate'- DATA_RELEASE_TIMING_PUBLICATION =
'delay until publication'- ALL_DATA_RELEASE_TIMINGS =
The list of all possible data release timings
[ DATA_RELEASE_TIMING_STANDARD, DATA_RELEASE_TIMING_NEVER, DATA_RELEASE_TIMING_DELAYED, DATA_RELEASE_TIMING_IMMEDIATE, DATA_RELEASE_TIMING_PUBLICATION ].freeze
- DATA_RELEASE_TIMINGS_FOR_OPEN_RELEASE =
Release timings for open studies
[ DATA_RELEASE_TIMING_STANDARD, DATA_RELEASE_TIMING_IMMEDIATE, DATA_RELEASE_TIMING_DELAYED, DATA_RELEASE_TIMING_PUBLICATION ].freeze
- DATA_RELEASE_TIMINGS_FOR_MANAGED_RELEASE =
Release timings for managed studies
[ DATA_RELEASE_TIMING_STANDARD, DATA_RELEASE_TIMING_IMMEDIATE, DATA_RELEASE_TIMING_DELAYED ].freeze
- OLD_DATA_RELEASE_PREVENTION_REASONS =
['data validity', 'legal', 'replication of data subset'].freeze
- DATA_RELEASE_PREVENTION_REASON_OTHER =
'Other (please specify)'- DATA_RELEASE_PREVENTION_REASONS =
[ 'Pilot or validation studies - DAC approval not required', 'Collaborators will share data in a research repository - DAC approval not required', 'Prevent harm (e.g sensitive studies or biosecurity) - DAC approval required', 'Protecting IP - DAC approval required', DATA_RELEASE_PREVENTION_REASON_OTHER ].freeze
- OLD_DATA_RELEASE_DELAY_FOR_OTHER =
'other'- DATA_RELEASE_DELAY_FOR_OTHER =
'Other (please specify below)'- OLD_DATA_RELEASE_DELAY_REASONS =
['other', 'phd study'].freeze
- DATA_RELEASE_DELAY_REASONS_STANDARD =
[ 'PhD study', 'Capacity building', 'Intellectual property protection', 'Additional time to make data FAIR', DATA_RELEASE_DELAY_FOR_OTHER ].freeze
- DATA_RELEASE_DELAY_REASONS_ASSAY =
['assay of no other use'].freeze
- DATA_RELEASE_DELAY_PERIODS =
['3 months', '6 months', '9 months', '12 months', '18 months'].freeze
- EBI_LIBRARY_STRATEGY_OPTIONS =
- EBI_LIBRARY_SOURCE_OPTIONS =
- EBI_LIBRARY_SELECTION_OPTIONS =
- REMAPPED_ATTRIBUTES =
{ contaminated_human_dna: YES_OR_NO, remove_x_and_autosomes: YES_OR_NO, study_sra_hold: STUDY_SRA_HOLDS, contains_human_dna: YES_OR_NO, commercially_available: YES_OR_NO }.transform_values { |v| v.index_by { |b| b.downcase } }
Constants included from Metadata
Constants included from StudyReport::StudyDetails
StudyReport::StudyDetails::BATCH_SIZE
Instance Attribute Summary collapse
-
#approval ⇒ Object
Returns the value of attribute approval.
-
#run_count ⇒ Object
Returns the value of attribute run_count.
-
#total_price ⇒ Object
Returns the value of attribute total_price.
Instance Method Summary collapse
- #abbreviation ⇒ Object
-
#accession_all_samples(event_user) ⇒ void
Accession all samples in the study.
- #accession_number? ⇒ Boolean
- #approved? ⇒ Boolean
-
#asset_progress(assets = nil) {|initial_requests.asset_statistics(wheres)| ... } ⇒ Object
Yields information on the state of all assets in a convenient fashion for displaying in a table.
- #completed ⇒ Object
- #dac_accession_number ⇒ Object
- #dac_refname ⇒ Object
-
#data_release_delay_options(assay_option: false) ⇒ Array<String>
Helper method for edit dropdowns to support backwards compatibility with old options.
-
#data_release_prevention_options ⇒ Array<String>
Helper method for edit dropdowns to support backwards compatibility with old options.
- #dehumanise_abbreviated_name ⇒ Object
- #each_well_for_qc_report_in_batches(exclude_existing, product_criteria, plate_purposes = nil) ⇒ Object
- #ebi_accession_number ⇒ Object
- #ethical_approval_required? ⇒ Boolean
- #locale ⇒ Object
- #mailing_list_of_managers ⇒ Object
- #mark_active ⇒ Object
- #mark_deactive ⇒ Object
-
#owner ⇒ Object
Returns the study owner (user) if exists or nil TODO - Should be “owners” and return all owners or empty array - done TODO - Look into this is the person that created it really the owner? If so, then an owner should be created when a study is created.
- #policy_accession_number ⇒ Object
- #rebroadcast ⇒ Object
-
#request_progress {|@stats_cache ||= initial_requests.progress_statistics| ... } ⇒ Object
Yields information on the state of all request types in a convenient fashion for displaying in a table.
-
#sample_progress(samples = nil) ⇒ Object
Yields information on the state of all samples in a convenient fashion for displaying in a table.
-
#samples_accessionable? ⇒ Boolean
Returns true if the samples in this study are eligible for accessioning.
-
#study ⇒ Object
Used by EventfulMailer.
- #study_status ⇒ Object
- #subject_type ⇒ Object
- #text_comments ⇒ Object
- #unprocessed_submissions? ⇒ Boolean
-
#validate_ethically_approved ⇒ Object
Instance methods.
- #validate_study_for_accessioning! ⇒ Object
- #warnings ⇒ Object
Methods included from EventfulRecord
has_many_events, has_many_lab_events, has_one_event_with_family
Methods included from Metadata
Methods included from Attributable::Association::Target
default, extended, for_select_association
Methods included from SampleManifest::Associations
Methods included from ReferenceGenome::Associations
Methods included from SharedBehaviour::Named
Methods included from Commentable
Methods included from DataRelease
#accession_required?, #do_not_enforce_accessioning, #for_array_express?, #valid_data_release_properties?
Methods included from Uuid::Uuidable
included, #unsaved_uuid!, #uuid
Methods included from Api::StudyIo::Extensions
Methods included from StudyReport::StudyDetails
#each_stock_well_id_in_study_in_batches, #progress_report_header, #progress_report_on_all_assets
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
#approval ⇒ Object
Returns the value of attribute approval.
144 145 146 |
# File 'app/models/study.rb', line 144 def approval @approval end |
#run_count ⇒ Object
Returns the value of attribute run_count.
144 145 146 |
# File 'app/models/study.rb', line 144 def run_count @run_count end |
#total_price ⇒ Object
Returns the value of attribute total_price.
144 145 146 |
# File 'app/models/study.rb', line 144 def total_price @total_price end |
Instance Method Details
#abbreviation ⇒ Object
611 612 613 614 |
# File 'app/models/study.rb', line 611 def abbreviation abbreviation = .study_name_abbreviation abbreviation.presence || "#{id}STDY" end |
#accession_all_samples(event_user) ⇒ void
This method returns an undefined value.
Accession all samples in the study.
If the study does not have an accession number, adds an error to the study and returns. Otherwise, iterates through each sample in the study and attempts to accession it, unless the sample already has an accession number. If an Accession::Error occurs for a sample, adds the error message to the study's errors.
NOTE: this does not check if the current user has permission to accession samples in this study
596 597 598 599 600 601 602 603 604 605 606 607 608 609 |
# File 'app/models/study.rb', line 596 def accession_all_samples(event_user) return errors.add(:base, 'Please accession the study before accessioning samples') unless accession_number? unless samples_accessionable? return errors.add(:base, 'Study cannot accession samples, see Study Accessioning tab for details') end samples.find_each do |sample| Accession.accession_sample(sample, event_user) unless sample.accession_number? rescue Accession::Error => e errors.add(:base, e.) end end |
#accession_number? ⇒ Boolean
561 562 563 |
# File 'app/models/study.rb', line 561 def accession_number? ebi_accession_number.present? end |
#approved? ⇒ Boolean
620 621 622 623 |
# File 'app/models/study.rb', line 620 def approved? # TODO: remove true end |
#asset_progress(assets = nil) {|initial_requests.asset_statistics(wheres)| ... } ⇒ Object
Yields information on the state of all assets in a convenient fashion for displaying in a table.
499 500 501 502 503 |
# File 'app/models/study.rb', line 499 def asset_progress(assets = nil) wheres = {} wheres = { asset_id: assets.map(&:id) } if assets.present? yield(initial_requests.asset_statistics(wheres)) end |
#completed ⇒ Object
484 485 486 487 488 489 490 |
# File 'app/models/study.rb', line 484 def completed counts = requests.standard.group('state').count total = counts.values.sum failed = counts['failed'] || 0 cancelled = counts['cancelled'] || 0 (total - failed - cancelled) > 0 ? (counts.fetch('passed', 0) * 100) / (total - failed - cancelled) : 0 end |
#dac_accession_number ⇒ Object
553 554 555 |
# File 'app/models/study.rb', line 553 def dac_accession_number .ega_dac_accession_number end |
#dac_refname ⇒ Object
522 523 524 |
# File 'app/models/study.rb', line 522 def dac_refname "DAC for study - #{name} - ##{id}" end |
#data_release_delay_options(assay_option: false) ⇒ Array<String>
Helper method for edit dropdowns to support backwards compatibility with old options.
663 664 665 666 667 668 669 670 671 672 |
# File 'app/models/study.rb', line 663 def (assay_option: false) # If the current value is an old one, then we need to include it in the list of options = [] if OLD_DATA_RELEASE_DELAY_REASONS.include? .data_release_delay_reason << .data_release_delay_reason end .concat(DATA_RELEASE_DELAY_REASONS_ASSAY) if assay_option DATA_RELEASE_DELAY_REASONS_STANDARD + end |
#data_release_prevention_options ⇒ Array<String>
Helper method for edit dropdowns to support backwards compatibility with old options.
650 651 652 653 654 655 656 657 |
# File 'app/models/study.rb', line 650 def = [] if OLD_DATA_RELEASE_PREVENTION_REASONS.include? .data_release_prevention_reason << .data_release_prevention_reason end DATA_RELEASE_PREVENTION_REASONS + end |
#dehumanise_abbreviated_name ⇒ Object
616 617 618 |
# File 'app/models/study.rb', line 616 def dehumanise_abbreviated_name abbreviation.downcase.gsub(/ +/, '_') end |
#each_well_for_qc_report_in_batches(exclude_existing, product_criteria, plate_purposes = nil) ⇒ Object
449 450 451 452 453 454 455 456 457 458 459 460 461 |
# File 'app/models/study.rb', line 449 def each_well_for_qc_report_in_batches(exclude_existing, product_criteria, plate_purposes = nil) # @note We include aliquots here, despite the fact they are only needed if we have to set a poor-quality flag # as in some cases failures are not as rare as you may imagine, and it can cause major performance issues. base_scope = Well .on_plate_purpose_included(PlatePurpose.where(name: plate_purposes || STOCK_PLATE_PURPOSES)) .for_study_through_aliquot(self) .without_blank_samples .includes(:well_attribute, :aliquots, :map, samples: :sample_metadata) .readonly(true) scope = exclude_existing ? base_scope.without_report(product_criteria) : base_scope scope.find_in_batches { |wells| yield wells } end |
#ebi_accession_number ⇒ Object
549 550 551 |
# File 'app/models/study.rb', line 549 def ebi_accession_number .study_ebi_accession_number end |
#ethical_approval_required? ⇒ Boolean
625 626 627 628 |
# File 'app/models/study.rb', line 625 def ethical_approval_required? .contains_human_dna == Study::YES && .contaminated_human_dna == Study::NO && .commercially_available == Study::NO end |
#locale ⇒ Object
545 546 547 |
# File 'app/models/study.rb', line 545 def locale funding_source end |
#mailing_list_of_managers ⇒ Object
634 635 636 637 |
# File 'app/models/study.rb', line 634 def mailing_list_of_managers configured_managers = managers.pluck(:email).compact.uniq configured_managers.empty? ? configatron.fetch(:ssr_emails, User.all_administrators_emails) : configured_managers end |
#mark_active ⇒ Object
476 477 478 |
# File 'app/models/study.rb', line 476 def mark_active logger.warn "Study activation failed! #{errors.map(&:to_s)}" unless active? end |
#mark_deactive ⇒ Object
472 473 474 |
# File 'app/models/study.rb', line 472 def mark_deactive logger.warn "Study deactivation failed! #{errors.map(&:to_s)}" unless inactive? end |
#owner ⇒ Object
Returns the study owner (user) if exists or nil TODO - Should be “owners” and return all owners or empty array - done TODO - Look into this is the person that created it really the owner? If so, then an owner should be created when a study is created.
541 542 543 |
# File 'app/models/study.rb', line 541 def owner owners.first end |
#policy_accession_number ⇒ Object
557 558 559 |
# File 'app/models/study.rb', line 557 def policy_accession_number .ega_policy_accession_number end |
#rebroadcast ⇒ Object
643 644 645 |
# File 'app/models/study.rb', line 643 def rebroadcast broadcast end |
#request_progress {|@stats_cache ||= initial_requests.progress_statistics| ... } ⇒ Object
Yields information on the state of all request types in a convenient fashion for displaying in a table. Used initial requests, which won't capture cross study sequencing requests.
494 495 496 |
# File 'app/models/study.rb', line 494 def request_progress yield(@stats_cache ||= initial_requests.progress_statistics) if block_given? end |
#sample_progress(samples = nil) ⇒ Object
Yields information on the state of all samples in a convenient fashion for displaying in a table.
506 507 508 509 510 511 512 513 514 515 516 |
# File 'app/models/study.rb', line 506 def sample_progress(samples = nil) if samples.blank? requests.sample_statistics_new else # Rubocop suggests this changes as it allows MySQL to perform a single query, which is usually better # however in this case we've actually already loaded the samples. If we do try passing in the # samples themselves, then things top working as intended. (Performance tanks in some places, and # we generate invalid SQL in others) yield(requests.where(aliquots: { sample_id: samples.pluck(:id) }).sample_statistics_new) end end |
#samples_accessionable? ⇒ Boolean
Returns true if the samples in this study are eligible for accessioning
A study's samples are eligible for accessioning if: - the study is active - the study's data release strategy open or managed - the study is not set to never release - the study requires accessioning - the study has an accession number
575 576 577 578 579 580 581 582 583 584 |
# File 'app/models/study.rb', line 575 def samples_accessionable? # If updating this method, please also update app/views/studies/information/_study_accession_status.html.erb [ active?, !.strategy_not_applicable?, !.never_release?, accession_required?, accession_number? ].all? end |
#study ⇒ Object
Used by EventfulMailer
532 533 534 |
# File 'app/models/study.rb', line 532 def study self end |
#study_status ⇒ Object
518 519 520 |
# File 'app/models/study.rb', line 518 def study_status inactive? ? 'closed' : 'open' end |
#subject_type ⇒ Object
639 640 641 |
# File 'app/models/study.rb', line 639 def subject_type 'study' end |
#text_comments ⇒ Object
480 481 482 |
# File 'app/models/study.rb', line 480 def text_comments comments.each_with_object([]) { |c, array| array << c.description if c.description.present? }.join(', ') end |
#unprocessed_submissions? ⇒ Boolean
526 527 528 529 |
# File 'app/models/study.rb', line 526 def unprocessed_submissions? # TODO[mb14] optimize if needed study.orders.any? { |o| o.submission.nil? || o.submission.unprocessed? } end |
#validate_ethically_approved ⇒ Object
Instance methods
436 437 438 439 440 441 442 443 444 445 446 447 |
# File 'app/models/study.rb', line 436 def validate_ethically_approved return true if valid_ethically_approved? = if ethical_approval_required? 'should be either true or false for this study.' else 'should be not applicable (null) not false.' end errors.add(:ethically_approved, ) false end |
#validate_study_for_accessioning! ⇒ Object
630 631 632 |
# File 'app/models/study.rb', line 630 def validate_study_for_accessioning! valid?(:accession) or raise ActiveRecord::RecordInvalid, self end |
#warnings ⇒ Object
463 464 465 466 467 468 469 470 |
# File 'app/models/study.rb', line 463 def warnings # These studies are now invalid, but the warning should remain until existing studies are fixed. if .managed? && .data_access_group.blank? # rubocop:todo Layout/LineLength 'No user group specified for a managed study. Please specify a valid Unix user group to ensure study data is visible to the correct people.' # rubocop:enable Layout/LineLength end end |