Class: Request

Inherits:
ApplicationRecord show all
Extended by:
EventfulRecord, Metadata, Statistics
Includes:
AASM, AASM::Extensions, Aliquot::DeprecatedBehaviours::Request, Api::RequestIo::Extensions, Batch::RequestBehaviour, Commentable, ModelExtensions::Request, CustomerResponsibility, Statemachine, StandardNamedScopes, Uuid::Uuidable
Defined in:
app/models/request.rb,
app/models/request/traction.rb,
app/models/request/traction/grid_ion.rb

Overview

A Request represents work which needs to be done, either to fulfil a customers needs CustomerRequest or for internal reasons SystemRequest. The progress of a request is tracked through its state machine.

Direct Known Subclasses

CustomerRequest, SystemRequest

Defined Under Namespace

Modules: AccessioningRequired, CustomerResponsibility, GroupingHelpers, HasNoTargetAsset, HasPrimerPanel, LibraryManufacture, SampleCompoundAliquotTransfer, Statemachine, Statistics, Traction Classes: AutoMultiplexing, ChangeDecision, LibraryCreation, Multiplexing, None

Constant Summary

Constants included from Metadata

Metadata::SECTION_FIELDS

Constants included from Statemachine

Statemachine::ACTIVE, Statemachine::COMPLETED_STATE, Statemachine::INACTIVE, Statemachine::OPENED_STATE, Statemachine::SORT_ORDER

Constants included from StandardNamedScopes

StandardNamedScopes::SORT_FIELDS, StandardNamedScopes::SORT_ORDERS

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Statistics

asset_statistics, progress_statistics, sample_statistics_new

Methods included from EventfulRecord

has_many_events, has_many_lab_events, has_one_event_with_family

Methods included from Metadata

has_metadata

Methods included from CustomerResponsibility

included

Methods included from Batch::RequestBehaviour

included, #recycle_from_batch!, #return_for_inbox!, #with_batch_id

Methods included from Statemachine

#cancellable?, #change_decision!, #closed?, #failed_downstream!, #failed_upstream!, #finished?, #on_blocked, #on_cancelled, #on_failed, #on_hold, #on_passed, #on_started, #open?, #terminated?, #transfer_aliquots

Methods included from StandardNamedScopes

included

Methods included from Commentable

#after_comment_addition

Methods included from AASM::Extensions

#transition_to

Methods included from Uuid::Uuidable

included, #unsaved_uuid!, #uuid

Methods included from Api::RequestIo::Extensions

included, #json_root

Methods included from Aliquot::DeprecatedBehaviours::Request

#tag, #tag_number

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

extended

Class Method Details

.accessioning_required?Boolean

Returns:

  • (Boolean)


348
349
350
# File 'app/models/request.rb', line 348

def self.accessioning_required?
  false
end

.delegate_validatorObject



336
337
338
# File 'app/models/request.rb', line 336

def self.delegate_validator
  DelegateValidation::AlwaysValidValidator
end

.for_study(study) ⇒ Object



340
341
342
# File 'app/models/request.rb', line 340

def self.for_study(study)
  Request.for_study_id(study.id)
end

.get_all_comments(request) ⇒ Object



566
567
568
569
# File 'app/models/request.rb', line 566

def self.get_all_comments(request)
  counts = Comment.counts_for_requests([request])
  counts[request.id]
end

.number_expected_for_submission_id_and_request_type_id(submission_id, request_type_id) ⇒ Object



344
345
346
# File 'app/models/request.rb', line 344

def self.number_expected_for_submission_id_and_request_type_id(submission_id, request_type_id)
  Request.where(submission_id:, request_type_id:)
end

Instance Method Details

#add_comment(comment, user, title = nil) ⇒ Object



482
483
484
485
486
# File 'app/models/request.rb', line 482

def add_comment(comment, user, title = nil)
  # Unscope comments to fix Rails 6 deprecation warnings. But I *think* this
  # essentially models the new behaviour in 6.1 So should be removable then
  Comment.unscoped { comments.create(description: comment, user: user, title: title) }
end

#aliquot_attributesHash

Passed into cloned aliquots at the beginning of a pipeline to set appropriate options

Returns:

  • (Hash)

    A hash of aliquot attributes



415
416
417
# File 'app/models/request.rb', line 415

def aliquot_attributes
  { study_id: initial_study_id, project_id: initial_project_id, request_id: id }
end

#associated_studiesObject



400
401
402
403
404
405
406
# File 'app/models/request.rb', line 400

def associated_studies
  return [initial_study] if initial_study.present?
  return asset.studies.uniq if asset.present?
  return submission.studies if submission.present?

  []
end

#cancelable?Boolean

Returns:

  • (Boolean)


514
515
516
# File 'app/models/request.rb', line 514

def cancelable?
  batch_request.nil? && (pending? || blocked?)
end

#copyObject



510
511
512
# File 'app/models/request.rb', line 510

def copy
  RequestFactory.copy_request(self)
end

#current_request_eventObject



364
365
366
# File 'app/models/request.rb', line 364

def current_request_event
  request_events.loaded? ? request_events.detect(&:current?) : request_events.current.last
end

#customer_accepts_responsibility!Object



531
532
533
# File 'app/models/request.rb', line 531

def customer_accepts_responsibility!
  # Do nothing
end

#detect_descriptor(name, descriptor_batch: batch) ⇒ Object



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

def detect_descriptor(name, descriptor_batch: batch)
  # Sort in lab_events_for_batch goes by id ascending, so we use a reverse each, in order to find the most recent
  # descriptor with the passed in 'name'
  # Lazy ensures we stop searching as soon as we find a value
  lab_events_for_batch(descriptor_batch).lazy.reverse_each.map { |e| e.descriptor_value_for(name) }.detect(&:present?)
end

#eventful_studiesObject



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

def eventful_studies
  initial_study.present? ? [initial_study] : asset_studies
end

#format_qc_informationObject



492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
# File 'app/models/request.rb', line 492

def format_qc_information
  return [] if lab_events.empty?

  events
    .filter_map do |event|
      next if event.family.nil? || %w[pass fail].exclude?(event.family.downcase)

      message = event.message.presence || '(No message was specified)'
      {
        'event_id' => event.id,
        'status' => event.family.downcase,
        'message' => message,
        'created_at' => event.created_at
      }
    end
    .compact
end

#has_passed(batch, task) ⇒ Object



426
427
428
# File 'app/models/request.rb', line 426

def has_passed(batch, task)
  lab_events_for_batch(batch).any? { |event| event.description == task.name }
end

#lab_events_for_batch(batch) ⇒ Array<LabEvent>, LabEvent::ActiveRecord_Associations_CollectionProxy

Returns the lab_events associated with batch While for the most-part each request only belongs to a single batch at any one time, they may have belonged to other batches historically.

Parameters:

  • batch (Batch)

    The batch to filter events by

Returns:

  • (Array<LabEvent>, LabEvent::ActiveRecord_Associations_CollectionProxy)

    Events associated with batch



438
439
440
441
442
443
444
# File 'app/models/request.rb', line 438

def lab_events_for_batch(batch)
  if lab_events.loaded?
    lab_events.select { |le| le.batch_id == batch&.id }.sort
  else
    lab_events.where(batch_id: batch).order(:created_at, :id)
  end
end

#manifest_processed!Object



563
564
# File 'app/models/request.rb', line 563

def manifest_processed!
end

#most_recent_event_named(name) ⇒ Object



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

def most_recent_event_named(name)
  lab_events_for_batch(batch).reverse.detect { |e| e.description == name }
end

#next_request_type_idObject



456
457
458
459
460
461
# File 'app/models/request.rb', line 456

def next_request_type_id
  # May be nil, so can't use lazy assignment
  return @next_request_type_id if instance_variable_defined?(:@next_request_type_id)

  @next_request_type_id = calculate_next_request_type_id
end

#next_requestsObject



450
451
452
453
454
# File 'app/models/request.rb', line 450

def next_requests
  return [] if submission.nil? || next_request_type_id.nil?

  next_requests_via_asset || next_requests_via_submission
end

#next_requests_via_assetObject

CAUTION!: This may not behaves as expected. I’ll be deprecating this soon.



464
465
466
467
468
# File 'app/models/request.rb', line 464

def next_requests_via_asset
  if target_asset.present?
    target_asset.requests.where(submission_id: submission_id, request_type_id: next_request_type_id)
  end
end

#next_requests_via_submissionObject



470
471
472
# File 'app/models/request.rb', line 470

def next_requests_via_submission
  submission.next_requests_via_submission(self)
end

#previous_failed_requests?Boolean

Returns:

  • (Boolean)


478
479
480
# File 'app/models/request.rb', line 478

def previous_failed_requests?
  asset.requests.any?(&:failed?)
end

#priorityObject



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

def priority
  submission.try(:priority) || 0
end

#product_lineObject



559
560
561
# File 'app/models/request.rb', line 559

def product_line
  _product_line&.name
end

#project=(project) ⇒ Object



382
383
384
385
386
# File 'app/models/request.rb', line 382

def project=(project)
  return unless project

  self.project_id = project.id
end

#project_id=(project_id) ⇒ Object



368
369
370
371
372
# File 'app/models/request.rb', line 368

def project_id=(project_id)
  raise 'Initial project already set' if initial_project_id

  self.initial_project_id = project_id
end

#ready?Boolean

Returns:

  • (Boolean)


551
552
553
# File 'app/models/request.rb', line 551

def ready?
  true
end

#request_type_updatable?(_new_request_type) ⇒ Boolean

Returns:

  • (Boolean)


527
528
529
# File 'app/models/request.rb', line 527

def request_type_updatable?(_new_request_type)
  pending?
end

#return_pending_to_inbox!Object

Raises:

  • (StandardError)


488
489
490
# File 'app/models/request.rb', line 488

def return_pending_to_inbox!
  raise StandardError, "Can only return pending requests, request is #{state}" unless pending?
end

#run_eventsObject



352
353
354
# File 'app/models/request.rb', line 352

def run_events
  events.loaded? ? events.select { |e| e.message.starts_with?('Run') } : where('message LIKE "Run%"')
end

#source_labwareObject



356
357
358
# File 'app/models/request.rb', line 356

def source_labware
  asset.labware
end

#study=(study) ⇒ Object



394
395
396
397
398
# File 'app/models/request.rb', line 394

def study=(study)
  return unless study

  self.study_id = study.id
end

#study_id=(study_id) ⇒ Object



388
389
390
391
392
# File 'app/models/request.rb', line 388

def study_id=(study_id)
  raise 'Initial study already set' if initial_study_id

  self.initial_study_id = study_id
end

#submission_plate_countObject



374
375
376
# File 'app/models/request.rb', line 374

def submission_plate_count
  submission.requests.where(request_type_id:).joins(:source_labware).distinct.count('labware.id')
end

#submitted_atObject

The date at which the submission was made. In most cases this will be similar to the request’s created_at timestamp. We go via submission to ensure that copied requests bear the original timestamp.



543
544
545
546
547
548
549
# File 'app/models/request.rb', line 543

def 
  # Hopefully we shouldn't get any requests that don't have a submission. But validation is turned off, so
  # we should assume it it possible.
  return '' if submission.nil?

  submission.created_at.strftime('%Y-%m-%d')
end

#target_purposeObject



555
556
557
# File 'app/models/request.rb', line 555

def target_purpose
  nil
end

#target_tubeObject



474
475
476
# File 'app/models/request.rb', line 474

def target_tube
  target_asset if target_asset.is_a?(Tube)
end

#update_pool_information(pool_information) ⇒ Object

Adds any pool information to the structure so that it can be reported to client applications



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

def update_pool_information(pool_information)
  pool_information[:request_type] = request_type.key
  pool_information[:for_multiplexing] = request_type.for_multiplexing?
end

#update_priorityObject



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

def update_priority
  priority = (self.priority + 1) % 4
  submission.update!(priority:)
end

#update_responsibilities!Object



378
379
380
# File 'app/models/request.rb', line 378

def update_responsibilities!
  # Do nothing
end