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, TagClash

Constant Summary collapse

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

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

.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



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

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

.equivalent_attributesObject

Returns a list of attributes which must be the same for two Aliquots to be considered #equivalent? Generated dynamically to avoid accidental introduction of false positives when new columns are added



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

def self.equivalent_attributes
  @equivalent_attributes ||= attribute_names - %w[id receptacle_id created_at updated_at]
end

Instance Method Details

#aliquot_index_valueObject



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

def aliquot_index_value
  aliquot_index.try(:aliquot_index)
end

#created_with_request_optionsObject



120
121
122
123
124
125
126
# File 'app/models/aliquot.rb', line 120

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



184
185
186
# File 'app/models/aliquot.rb', line 184

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

#equivalent?(other, list_of_aliquot_attributes_to_consider_a_duplicate = nil) ⇒ Boolean

Unlike the above methods, which allow untagged to match with tagged, this looks for exact matches only. By default only id, timestamps and receptacles are excluded, but this can be overridden by passing in a specific list of attributes to check against.

Returns:

  • (Boolean)


221
222
223
224
# File 'app/models/aliquot.rb', line 221

def equivalent?(other, list_of_aliquot_attributes_to_consider_a_duplicate = nil)
  attributes_to_check = list_of_aliquot_attributes_to_consider_a_duplicate || Aliquot.equivalent_attributes
  attributes_to_check.all? { |attrib| send(attrib) == other.send(attrib) }
end

#matches?(object) ⇒ Boolean

rubocop:todo Metrics/PerceivedComplexity, Metrics/MethodLength, Metrics/AbcSize

Returns:

  • (Boolean)


194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'app/models/aliquot.rb', line 194

def matches?(object) # rubocop:todo Metrics/CyclomaticComplexity
  # NOTE: This function is directional, and assumes that the downstream aliquot
  # is checking the upstream aliquot
  if sample_id != object.sample_id
    false # The samples don't match
  elsif object.library_id.present? && (library_id != object.library_id)
    false # Our libraries don't match.
  elsif object.bait_library_id.present? && (bait_library_id != object.bait_library_id)
    false # We have different bait libraries
  elsif (no_tag1? && object.tag1?) || (no_tag2? && object.tag2?)
    # rubocop:todo Layout/LineLength
    raise StandardError, 'Tag missing from downstream aliquot' # The downstream aliquot is untagged, but is tagged upstream. Something is wrong!
    # rubocop:enable Layout/LineLength
  elsif object.no_tags?
    true # The upstream aliquot was untagged, we don't need to check tags
  else
    # rubocop:todo Layout/LineLength
    (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
    # rubocop:enable Layout/LineLength
  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)


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

def no_tag1?
  tag_id == UNASSIGNED_TAG || (tag_id.nil? && tag.nil?)
end

#no_tag2?Boolean

Returns:

  • (Boolean)


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

def no_tag2?
  tag2_id == UNASSIGNED_TAG || (tag2_id.nil? && tag2.nil?)
end

#no_tags?Boolean

Returns:

  • (Boolean)


152
153
154
# File 'app/models/aliquot.rb', line 152

def no_tags?
  no_tag1? && no_tag2?
end

#poly_metadataActiveRecord::Relation

Returns a collection of PolyMetadatum records.

Returns:

  • (ActiveRecord::Relation)

    a collection of PolyMetadatum records



227
228
229
# File 'app/models/aliquot.rb', line 227

def 
  PolyMetadatum.where(metadatable_id: id, metadatable_type: self.class.name)
end

#set_library(force: false) ⇒ Object



177
178
179
# File 'app/models/aliquot.rb', line 177

def set_library(force: false)
  self.library = receptacle if library.nil? || force
end

#tagObject

Optimization: Avoids us hitting the database for untagged aliquots



169
170
171
# File 'app/models/aliquot.rb', line 169

def tag
  super unless tag_id == UNASSIGNED_TAG
end

#tag1?Boolean

Returns:

  • (Boolean)


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

def tag1?
  !no_tag1?
end

#tag2Object



173
174
175
# File 'app/models/aliquot.rb', line 173

def tag2
  super unless tag2_id == UNASSIGNED_TAG
end

#tag2?Boolean

Returns:

  • (Boolean)


144
145
146
# File 'app/models/aliquot.rb', line 144

def tag2?
  !no_tag2?
end

#tag_count_nameObject



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

def tag_count_name
  TAG_COUNT_NAMES[tag_count]
end

#tags?Boolean

Returns:

  • (Boolean)


148
149
150
# File 'app/models/aliquot.rb', line 148

def tags?
  !no_tags?
end

#tags_and_tag_depth_combinationObject



160
161
162
# File 'app/models/aliquot.rb', line 160

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

#tags_combinationObject



156
157
158
# File 'app/models/aliquot.rb', line 156

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

#update_quality(suboptimal_quality) ⇒ Object



188
189
190
191
# File 'app/models/aliquot.rb', line 188

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