Class: Aliquot

Inherits:
ApplicationRecord show all
Includes:
DataForSubstitution, AliquotIndexer::AliquotScopes, Api::AliquotIO::Extensions, Api::Messages::FlowcellIO::AliquotExtensions, Api::Messages::QcResultIO::AliquotExtensions, Uuid::Uuidable
Defined in:
app/models/aliquot.rb

Overview

A note on tags: Aliquots can have up to two tags attached, the i7 (tag) and the i5(tag2) Tags are short DNA sequences which can be used to track samples following pooling. If two samples with the same tags are pooled together it becomes impossible to distinguish between them. To avoid this we have an index which ensures unique tags are maintained per pool. (Limitation: This restriction assumes that each oligo sequence is represented only once in the database. This is not the case, so additional slower checks are required where cross tag group pools are possible) MySQL indexes treat NULL values as non identical, so -1 (UNASSIGNED_TAG) is used to represent an untagged well. We have some performance optimizations in place to avoid trying to look up tag -1

See Also:

Defined Under Namespace

Modules: Aliquotable, DataForSubstitution, DeprecatedBehaviours, Remover Classes: InsertSize

Constant Summary collapse

TagClash =
Class.new(ActiveRecord::RecordInvalid)
TAG_COUNT_NAMES =
%w[Untagged Single Dual].freeze
UNASSIGNED_TAG =

It may have a tag but not necessarily. If it does, however, that tag needs to be unique within the receptacle. To ensure that there can only be one untagged aliquot present in a receptacle we use a special value for tag_id, rather than NULL which does not work in MySQL. It also works because the unassigned tag ID never gets matched for a Tag and so the result is nil!

-1

Class Method Summary collapse

Instance Method Summary collapse

Methods included from DataForSubstitution

#changes, #generate_substitution_hash, #original_tag2_id, #original_tag_id, #other_attributes_for_substitution, #substitute_tag2_id, #substitute_tag_id, #substitution_hash, #tag2_id_substitution, #tag_id_substitution

Methods included from Api::AliquotIO::Extensions

included

Methods included from AliquotIndexer::AliquotScopes

included

Methods included from Uuid::Uuidable

included, #unsaved_uuid!, #uuid

Methods inherited from ApplicationRecord

convert_labware_to_receptacle_for, find_by_id_or_name, find_by_id_or_name!

Methods included from Warren::BroadcastMessages

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

Class Method Details

.count_by_project_cost_codeObject

returns a hash, where keys are cost_codes and values are number of aliquots related to particular cost code {'cost_code_1' => 20, 'cost_code_2' => 3, 'cost_code_3' => 8 } this one does not work, as project is not always there: joins(project: :project_metadata).group(“project_metadata.project_cost_code”).count


91
92
93
94
95
96
# File 'app/models/aliquot.rb', line 91

def self.count_by_project_cost_code
  joins('LEFT JOIN projects ON aliquots.project_id = projects.id')
    .joins('LEFT JOIN project_metadata ON project_metadata.project_id = projects.id')
    .group('project_metadata.project_cost_code')
    .count
end

Instance Method Details

#aliquot_index_valueObject


98
99
100
# File 'app/models/aliquot.rb', line 98

def aliquot_index_value
  aliquot_index.try(:aliquot_index)
end

#created_with_request_optionsObject


102
103
104
105
106
107
108
# File 'app/models/aliquot.rb', line 102

def created_with_request_options
  {
    fragment_size_required_from: insert_size_from,
    fragment_size_required_to: insert_size_to,
    library_type: library_type
  }
end

#dup(params = {}) ⇒ Object

Cloning an aliquot should unset the receptacle ID because otherwise it won't get reassigned. We should also reset the timestamp information as this is a new aliquot really. Any options passed in as parameters will override the aliquot defaults


157
158
159
160
161
# File 'app/models/aliquot.rb', line 157

def dup(params = {})
  super().tap do |cloned_aliquot|
    cloned_aliquot.assign_attributes(params)
  end
end

#equivalent?(other) ⇒ Boolean

Unlike the above methods, which allow untagged to match with tagged, this looks for exact matches only only id, timestamps and receptacles are excluded

Returns:

  • (Boolean)

183
184
185
186
187
188
189
190
# File 'app/models/aliquot.rb', line 183

def equivalent?(other)
  %i[
    sample_id tag_id tag2_id library_id bait_library_id
    insert_size_from insert_size_to library_type project_id study_id
  ].all? do |attrib|
    send(attrib) == other.send(attrib)
  end
end

#matches?(object) ⇒ Boolean

Returns:

  • (Boolean)

168
169
170
171
172
173
174
175
176
177
178
179
# File 'app/models/aliquot.rb', line 168

def matches?(object)
  # Note: This function is directional, and assumes that the downstream aliquot
  # is checking the upstream aliquot
  case
  when sample_id != object.sample_id                                                   then false # The samples don't match
  when object.library_id.present?      && (library_id      != object.library_id)       then false # Our librarys don't match.
  when object.bait_library_id.present? && (bait_library_id != object.bait_library_id)  then false # We have different bait libraries
  when (no_tag1? && object.tag1?) || (no_tag2? && object.tag2?)                        then raise StandardError, 'Tag missing from downstream aliquot' # The downstream aliquot is untagged, but is tagged upstream. Something is wrong!
  when object.no_tags? then true # The upstream aliquot was untagged, we don't need to check tags
  else (object.no_tag1? || (tag_id == object.tag_id)) && (object.no_tag2? || (tag2_id == object.tag2_id)) # Both aliquots are tagged, we need to check if they match
  end
end

#no_tag1?Boolean

Validating the uniqueness of tags in rails was causing issues, as it was resulting the in the preform_transfer_of_contents in transfer request to fail, without any visible sign that something had gone wrong. This essentially meant that tag clashes would result in sample dropouts. (presumably because << triggers save not save!)

Returns:

  • (Boolean)

113
114
115
# File 'app/models/aliquot.rb', line 113

def no_tag1?
  tag_id == UNASSIGNED_TAG || tag.nil?
end

#no_tag2?Boolean

Returns:

  • (Boolean)

121
122
123
# File 'app/models/aliquot.rb', line 121

def no_tag2?
  tag2_id == UNASSIGNED_TAG || tag2.nil?
end

#no_tags?Boolean

Returns:

  • (Boolean)

133
134
135
# File 'app/models/aliquot.rb', line 133

def no_tags?
  no_tag1? && no_tag2?
end

#set_libraryObject


150
151
152
# File 'app/models/aliquot.rb', line 150

def set_library
  self.library = receptacle
end

#tagObject

Optimization: Avoids us hitting the database for untagged aliquots


146
147
148
# File 'app/models/aliquot.rb', line 146

def tag
  super unless tag_id == UNASSIGNED_TAG
end

#tag1?Boolean

Returns:

  • (Boolean)

117
118
119
# File 'app/models/aliquot.rb', line 117

def tag1?
  !no_tag1?
end

#tag2?Boolean

Returns:

  • (Boolean)

125
126
127
# File 'app/models/aliquot.rb', line 125

def tag2?
  !no_tag2?
end

#tag_count_nameObject


141
142
143
# File 'app/models/aliquot.rb', line 141

def tag_count_name
  TAG_COUNT_NAMES[tag_count]
end

#tags?Boolean

Returns:

  • (Boolean)

129
130
131
# File 'app/models/aliquot.rb', line 129

def tags?
  !no_tags?
end

#tags_combinationObject


137
138
139
# File 'app/models/aliquot.rb', line 137

def tags_combination
  [tag.try(:oligo), tag2.try(:oligo)]
end

#update_quality(suboptimal_quality) ⇒ Object


163
164
165
166
# File 'app/models/aliquot.rb', line 163

def update_quality(suboptimal_quality)
  self.suboptimal = suboptimal_quality
  save!
end