Class: Study

Inherits:
ApplicationRecord show all
Extended by:
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,
app/models/study/metadata.rb

Overview

Study itself will also ensure that this file gets loaded, to make sure the metadata class contains these methods

Defined Under Namespace

Classes: Metadata

Constant Summary collapse

STOCK_PLATE_PURPOSES =

Constants

['Stock Plate', 'Stock RNA Plate'].freeze
YES =
'Yes'.freeze
NO =
'No'.freeze
YES_OR_NO =
[YES, NO].freeze
Other_type =
'Other'.freeze
STUDY_SRA_HOLDS =
%w[Hold Public].freeze
DATA_RELEASE_STRATEGY_OPEN =
'open'.freeze
DATA_RELEASE_STRATEGY_MANAGED =
'managed'.freeze
DATA_RELEASE_STRATEGY_NOT_APPLICABLE =
'not applicable'.freeze
DATA_RELEASE_STRATEGIES =
[DATA_RELEASE_STRATEGY_OPEN, DATA_RELEASE_STRATEGY_MANAGED,
DATA_RELEASE_STRATEGY_NOT_APPLICABLE].freeze
DATA_RELEASE_TIMING_STANDARD =
'standard'.freeze
DATA_RELEASE_TIMING_NEVER =
'never'.freeze
DATA_RELEASE_TIMING_DELAYED =
'delayed'.freeze
DATA_RELEASE_TIMINGS =
[
  DATA_RELEASE_TIMING_STANDARD,
  'immediate',
  DATA_RELEASE_TIMING_DELAYED
].freeze
DATA_RELEASE_PREVENTION_REASONS =
[
  'data validity',
  'legal',
  'replication of data subset'
].freeze
DATA_RELEASE_DELAY_FOR_OTHER =
'other'.freeze
DATA_RELEASE_DELAY_REASONS_STANDARD =
[
  'phd study',
  DATA_RELEASE_DELAY_FOR_OTHER
].freeze
DATA_RELEASE_DELAY_REASONS_ASSAY =
[
  'phd study',
  'assay of no other use',
  DATA_RELEASE_DELAY_FOR_OTHER
].freeze
DATA_RELEASE_DELAY_PERIODS =
['3 months', '6 months', '9 months', '12 months', '18 months'].freeze

Constants included from Metadata

Metadata::SECTION_FIELDS

Constants included from StudyReport::StudyDetails

StudyReport::StudyDetails::BATCH_SIZE

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Metadata

has_metadata

Methods included from EventfulRecord

has_many_events, has_many_lab_events, has_one_event_with_family

Methods included from SampleManifest::Associations

included

Methods included from ReferenceGenome::Associations

included

Methods included from SharedBehaviour::Named

included

Methods included from Commentable

#after_comment_addition

Methods included from DataRelease

#ena_accession_required?, #for_array_express?, #valid_data_release_properties?

Methods included from Uuid::Uuidable

included, #unsaved_uuid!, #uuid

Methods included from Api::StudyIO::Extensions

included, #render_class

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

convert_labware_to_receptacle_for, find_by_id_or_name, find_by_id_or_name!

Methods included from Squishify

extended

Methods included from Warren::BroadcastMessages

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

Instance Attribute Details

#approvalObject

Returns the value of attribute approval


100
101
102
# File 'app/models/study.rb', line 100

def approval
  @approval
end

#run_countObject

Returns the value of attribute run_count


100
101
102
# File 'app/models/study.rb', line 100

def run_count
  @run_count
end

#total_priceObject

Returns the value of attribute total_price


100
101
102
# File 'app/models/study.rb', line 100

def total_price
  @total_price
end

Instance Method Details

#abbreviationObject


491
492
493
494
# File 'app/models/study.rb', line 491

def abbreviation
  abbreviation = .study_name_abbreviation
  abbreviation.presence || "#{id}STDY"
end

#accession_all_samplesObject


485
486
487
488
489
# File 'app/models/study.rb', line 485

def accession_all_samples
  if accession_number?
    samples.find_each(&:accession)
  end
end

#accession_number?Boolean

Returns:

  • (Boolean)

481
482
483
# File 'app/models/study.rb', line 481

def accession_number?
  ebi_accession_number.present?
end

#accession_serviceObject


511
512
513
514
515
516
517
# File 'app/models/study.rb', line 511

def accession_service
  case data_release_strategy
  when 'open' then EnaAccessionService.new
  when 'managed' then EgaAccessionService.new
  else NoAccessionService.new(self)
  end
end

#approved?Boolean

Returns:

  • (Boolean)

500
501
502
503
# File 'app/models/study.rb', line 500

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.

Yields:

  • (initial_requests.asset_statistics(wheres))

419
420
421
422
423
# File 'app/models/study.rb', line 419

def asset_progress(assets = nil)
  wheres = {}
  wheres = { asset_id: assets.map(&:id) } if assets.present?
  yield(initial_requests.asset_statistics(wheres))
end

#completedObject


400
401
402
403
404
405
406
407
408
409
410
# File 'app/models/study.rb', line 400

def completed
  counts = requests.standard.group('state').count
  total = counts.values.sum
  failed = counts['failed'] || 0
  cancelled = counts['cancelled'] || 0
  if (total - failed - cancelled) > 0
    (counts.fetch('passed', 0) * 100) / (total - failed - cancelled)
  else
    0
  end
end

#dac_accession_numberObject


473
474
475
# File 'app/models/study.rb', line 473

def dac_accession_number
  .ega_dac_accession_number
end

#dac_refnameObject


442
443
444
# File 'app/models/study.rb', line 442

def dac_refname
  "DAC for study - #{name} - ##{id}"
end

#dehumanise_abbreviated_nameObject


496
497
498
# File 'app/models/study.rb', line 496

def dehumanise_abbreviated_name
  abbreviation.downcase.gsub(/ +/, '_')
end

#each_well_for_qc_report_in_batches(exclude_existing, product_criteria, plate_purposes = nil) ⇒ Object


365
366
367
368
369
370
371
372
373
374
375
# File 'app/models/study.rb', line 365

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_numberObject


469
470
471
# File 'app/models/study.rb', line 469

def ebi_accession_number
  .study_ebi_accession_number
end

#ethical_approval_required?Boolean

Returns:

  • (Boolean)

505
506
507
508
509
# File 'app/models/study.rb', line 505

def ethical_approval_required?
  (.contains_human_dna == Study::YES &&
  .contaminated_human_dna == Study::NO &&
  .commercially_available == Study::NO)
end

#localeObject


465
466
467
# File 'app/models/study.rb', line 465

def locale
  funding_source
end

#mailing_list_of_managersObject


527
528
529
530
531
532
533
534
# File 'app/models/study.rb', line 527

def mailing_list_of_managers
  configured_managers = managers.pluck(:email).compact.uniq
  if configured_managers.empty?
    configatron.fetch(:ssr_emails, User.all_administrators_emails)
  else
    configured_managers
  end
end

#mark_activeObject


390
391
392
393
394
# File 'app/models/study.rb', line 390

def mark_active
  unless active?
    logger.warn "Study activation failed! #{errors.map(&:to_s)}"
  end
end

#mark_deactiveObject


384
385
386
387
388
# File 'app/models/study.rb', line 384

def mark_deactive
  unless inactive?
    logger.warn "Study deactivation failed! #{errors.map(&:to_s)}"
  end
end

#ownerObject

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.


461
462
463
# File 'app/models/study.rb', line 461

def owner
  owners.first
end

#policy_accession_numberObject


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

def policy_accession_number
  .ega_policy_accession_number
end

#rebroadcastObject


540
541
542
# File 'app/models/study.rb', line 540

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.

Yields:

  • (@stats_cache ||= initial_requests.progress_statistics)

414
415
416
# File 'app/models/study.rb', line 414

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.


426
427
428
429
430
431
432
433
434
435
436
# File 'app/models/study.rb', line 426

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

#send_samples_to_service?Boolean

Returns:

  • (Boolean)

519
520
521
# File 'app/models/study.rb', line 519

def send_samples_to_service?
  accession_service.no_study_accession_needed || ((!.never_release?) && accession_number?)
end

#studyObject

Used by EventfulMailer


452
453
454
# File 'app/models/study.rb', line 452

def study
  self
end

#study_statusObject


438
439
440
# File 'app/models/study.rb', line 438

def study_status
  inactive? ? 'closed' : 'open'
end

#subject_typeObject


536
537
538
# File 'app/models/study.rb', line 536

def subject_type
  'study'
end

#text_commentsObject


396
397
398
# File 'app/models/study.rb', line 396

def text_comments
  comments.each_with_object([]) { |c, array| array << c.description if c.description.present? }.join(', ')
end

#unprocessed_submissions?Boolean

Returns:

  • (Boolean)

446
447
448
449
# File 'app/models/study.rb', line 446

def unprocessed_submissions?
  # TODO[mb14] optimize if needed
  study.orders.any? { |o| o.submission.nil? || o.submission.unprocessed? }
end

#validate_ena_required_fields!Object


523
524
525
# File 'app/models/study.rb', line 523

def validate_ena_required_fields!
  valid?(:accession) or raise ActiveRecord::RecordInvalid, self
end

#validate_ethically_approvedObject

Instance methods


357
358
359
360
361
362
363
# File 'app/models/study.rb', line 357

def validate_ethically_approved
  return true if valid_ethically_approved?

  message = ethical_approval_required? ? 'should be either true or false for this study.' : 'should be not applicable (null) not false.'
  errors.add(:ethically_approved, message)
  false
end

#warningsObject


377
378
379
380
381
382
# File 'app/models/study.rb', line 377

def warnings
  # These studies are now invalid, but the warning should remain until existing studies are fixed.
  if .managed? && .data_access_group.blank?
    '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.'
  end
end