Skip to content

Commit

Permalink
Merge pull request #4010 from sanger/develop
Browse files Browse the repository at this point in the history
Develop into master
  • Loading branch information
yoldas authored Jan 31, 2024
2 parents f8a9796 + e3ea916 commit b189ed8
Show file tree
Hide file tree
Showing 37 changed files with 1,352 additions and 17 deletions.
2 changes: 1 addition & 1 deletion .release-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
14.30.0
14.31.0
4 changes: 2 additions & 2 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ GEM
net-smtp (0.4.0)
net-protocol
netrc (0.11.0)
nio4r (2.5.9)
nio4r (2.7.0)
nokogiri (1.15.4)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
Expand All @@ -298,7 +298,7 @@ GEM
pry-rails (0.3.9)
pry (>= 0.10.4)
public_suffix (5.0.4)
puma (6.4.0)
puma (6.4.2)
nio4r (~> 2.0)
racc (1.7.3)
rack (2.2.8)
Expand Down
12 changes: 12 additions & 0 deletions app/controllers/api/v2/plate_purposes_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# frozen_string_literal: true

module Api
module V2
# Provides a JSON API controller for receptacle
# See: http://jsonapi-resources.com/ for JSONAPI::Resource documentation
class PlatePurposesController < JSONAPI::ResourceController
# By default JSONAPI::ResourceController provides most the standard
# behaviour, and in many cases this file may be left empty.
end
end
end
12 changes: 12 additions & 0 deletions app/controllers/api/v2/poly_metadata_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# frozen_string_literal: true

module Api
module V2
# Provides a JSON API controller for PolyMetadatum
# See: http://jsonapi-resources.com/ for JSONAPI::Resource documentation
class PolyMetadataController < JSONAPI::ResourceController
# By default JSONAPI::ResourceController provides most the standard
# behaviour, and in many cases this file may be left empty.
end
end
end
2 changes: 1 addition & 1 deletion app/models/location_report.rb
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def generate!

ActiveRecord::Base.transaction do
Tempfile.open(filename) do |tempfile|
generate_report_rows { |fields| tempfile << CSV.generate_line(fields, csv_options) }
generate_report_rows { |fields| tempfile << CSV.generate_line(fields, **csv_options) }
tempfile.rewind
update!(report: tempfile)
end
Expand Down
15 changes: 14 additions & 1 deletion app/models/map.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,19 @@ module Coordinate
# TODO: These methods are only valid for standard plates. Moved them here to make that more explicit
# (even if its not strictly appropriate) They could do with refactoring/removing.

PLATE_DIMENSIONS = Hash.new { |_h, _k| [] }.merge(96 => [12, 8], 384 => [24, 16])
# A hash representing the dimensions of different types of plates.
# The keys are the total number of wells in the plate, and the values are
# arrays, where the first element is the number of columns and the second
# element is the number of rows.
#
# @note
# - 96 represents a 96-well plate, arranged in 12 columns and 8 rows.
# - 384 represents a 384-well plate, arranged in 24 columns and 16 rows.
# - 16 represents a 16-well Chromium Chip, which has 8 columns and 2 rows.
# Although a 16-well Chromium Chip does not have 3:2 ratio to be a
# standard plate, i.e. it has 4:1 ratio, the methods here still apply.
# @return [Hash{Integer => Array<Integer>}] the dimensions of the plates
PLATE_DIMENSIONS = Hash.new { |_h, _k| [] }.merge(96 => [12, 8], 384 => [24, 16], 16 => [8, 2])

# Seems to expect row to be zero-indexed but column to be 1 indexed
def self.location_from_row_and_column(row, column, _ = nil, __ = nil)
Expand Down Expand Up @@ -269,5 +281,6 @@ def walk_plate_in_row_major_order(size, asset_shape = nil)
.order(:row_order)
.each { |position| yield(position, position.row_order) }
end
alias walk_plate_horizontally walk_plate_in_row_major_order
end
end
37 changes: 37 additions & 0 deletions app/models/poly_metadatum.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# frozen_string_literal: true

#
# A polymetadatum is a key value pair store. It is set up such that it can be
# associated with multiple different models (ie. a polymorphic relationship).
#
# It can be linked to any model that has the reverse association set up.
# i.e. add this association line to the model:
# has_many :poly_metadata, as: :metadatable, dependent: :destroy
# and this line to the v2 api resource (if api access needed):
# has_many :poly_metadata, as: :metadatable, class_name: 'PolyMetadatum'
#
# See Request model and associated api v2 resource for an example of how to use this.
#
class PolyMetadatum < ApplicationRecord
# Associations
belongs_to :metadatable, polymorphic: true, optional: false

# Validations
validates :key, presence: true # otherwise nil is a valid key
validates :value, presence: true

# Currently we allow the same key to be used for different metadatable objects,
# but it has to be unique for each metadatable object and is case insensitive.
# This is to allow the same key to be used for different models, e.g. a request
# and a sample might both have metadata called 'somename', but the same model cannot
# have two metadata called 'somename' and 'SOMENAME'.
# A metadatable has both a type and an id, so the combination key is unique.
# metadatable_type is the class name of the model, e.g. 'Request'
# metadatable_id is the database id of the model instance
validates :key, uniqueness: { scope: %i[metadatable_type metadatable_id], case_sensitive: false }

# Methods
def to_h
{ key => value }
end
end
3 changes: 3 additions & 0 deletions app/models/request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ def date_for_state(state)
# reflect the final product of a request.
has_many :related_aliquots, class_name: 'Aliquot', inverse_of: :request

# Can have many key value pairs of metadata
has_many :poly_metadata, as: :metadatable, dependent: :destroy

delegate :flowcell, to: :batch, allow_nil: true
delegate :for_multiplexing?, to: :request_type

Expand Down
85 changes: 85 additions & 0 deletions app/resources/api/v2/plate_purpose_resource.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# frozen_string_literal: true

module Api
module V2
# Provides a JSON API representation of PlatePurpose
# See: http://jsonapi-resources.com/ for JSONAPI::Resource documentation
class PlatePurposeResource < BaseResource
model_name 'PlatePurpose'

# This resource is similar to PurposeResource but it was created to
# migrate the registration of plate purposes done by the Limber rake
# task config:generate from API version 1 to API version 2.

# The following attributes are sent by Limber for a new plate purpose.

# @!attribute name
# @return [String] the name of the plate purpose
attribute :name

# @!attribute stock_plate
# @return [Boolean] whether the plates of this purpose are stock plates
attribute :stock_plate

# @!attribute cherrypickable_target
# @return [Boolean] whether the plates of this purpose are cherrypickable
attribute :cherrypickable_target

# @!attribute input_plate
# @return [Boolean] whether the plates of this purpose are input plates
attribute :input_plate

# @!attribute size
# @return [Integer] the size of the plates of this purpose
attribute :size

# @!attribute asset_shape
# @return [String] the name of the shape of the plates of this purpose
attribute :asset_shape

# The following attribute is required by Limber to store purposes.

# @!attribute [r] uuid
# @return [String] the UUID of the plate purpose
attribute :uuid, readonly: true

# Sets the asset shape of the plate purpose by name if given.
# 'asset_shape' can be given via the Limber purpose configuration and
# defaults to 'Standard' if not provided. If the name is given and not
# found, an error is raised. Note that the name is case-sensitive.
#
# @param name [String] the name of the asset shape
# @return [void]
def asset_shape=(name)
@model.asset_shape = (AssetShape.find_by!(name: name) if name.present?) || AssetShape.default
end

# Returns the name of the asset shape of the plate purpose.
# The asset_shape association is not utilized in Limber. This method
# returns the name of the asset shape associated with the plate purpose.
#
# @return [String] the name of the asset shape
def asset_shape
@model.asset_shape.name
end

# Returns the input_plate attribute from the type of the plate purpose.
# This method is the counterpart to the model's attribute writer for
# input_plate. It performs the inverse operation, determining the value
# of input_plate attribute based on the model's type.
#
# @return [Boolean] whether the plate purpose is an input plate
def input_plate
@model.type == 'PlatePurpose::Input'
end

# Prevents updating existing plate purposes.
#
# @param _context [JSONAPI::Resource::Context] not used
# @return [Array<Symbol>] empty array
def updatable_fields(_context)
[]
end
end
end
end
32 changes: 32 additions & 0 deletions app/resources/api/v2/poly_metadatum_resource.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# frozen_string_literal: true

module Api
module V2
# Provides a JSON API representation of PolyMetadatum
# See: http://jsonapi-resources.com/ for JSONAPI::Resource documentation
class PolyMetadatumResource < BaseResource
# Constants...

# immutable # uncomment to make the resource immutable

# model_name / model_hint if required

# Associations:
has_one :metadatable, polymorphic: true

# Attributes
attribute :key
attribute :value
attribute :created_at, readonly: true
attribute :updated_at, readonly: true

# Filters

# Custom methods
# These shouldn't be used for business logic, and a more about
# I/O and isolating implementation details.

# Class method overrides
end
end
end
1 change: 1 addition & 0 deletions app/resources/api/v2/request_resource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class RequestResource < BaseResource
has_one :request_type, always_include_linkage_data: true
has_one :primer_panel
has_one :pre_capture_pool
has_many :poly_metadata, as: :metadatable, class_name: 'PolyMetadatum'

# Attributes
attribute :uuid, readonly: true
Expand Down
65 changes: 65 additions & 0 deletions config/default_records/asset_shapes/default_records.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# This file contains the AssetShape and associated Map records to be created in
# deployment environments by RecordLoader. The loading is triggered by the
# "post_deploy" task. The records are checked against the database by their
# names. If the records already exist, they are not created. AssetShape records
# are created in the "asset_shapes" table, and Map records are created in the
# "maps" table. Each section in this file starts with the name of the AssetShape
# record. The "horizontal_ratio" and "vertical_ratio" options are used to define
# the plate's shape. These options represent the simplest form of the ratio
# between the number of columns and rows (i.e. width / height). Specifically,
# "horizontal_ratio" corresponds to the number of columns (numerator), and
# "vertical_ratio" corresponds to the number of rows (denominator). For
# instance, a "Standard" 96-well plate has a horizontal ratio of 3 and a
# vertical ratio of 2 because it has 12 columns (1 to 12) and 8 rows (A to H).
# The "sizes" option defines different plate sizes with that shape, for example,
# 96 and 384. Each size determines the number of Map records to be created for
# that shape and size. The "description_strategy" option defines the nested
# module in the Map model that is used for handling positions, rows, columns,
# and wells in plate geometry. AssetShapeLoader uses PlateMapGeneration to
# create records in the database. Each "Well" on a "Plate" is associated with a
# "Map".
#
# When configuring a "Purpose", both "asset_shape" and "size" need to be
# specified in that configuration in order to use the correct labware. If not
# specified, they will default to Standard and 96 respectively. For example,
# LRC HT 5p Chip:
# :asset_shape: ChromiumChip
# :size: 16
#
# The information in this file is duplicated in a couple of places in the
# codebase. When the local development environment is "setup" or "reset", a
# database "seed" is executed. This results in using the maps hash in
# PlateMapGeneration. The same hash is used when the RSpec before suite hook
# calls PlateMapGeneration to create records in the local test environment.
# Plate sizes and number of rows and columns are also defined separately in
# a hash in the Map model, which is used by the nested Coordinate module.
---
Standard:
horizontal_ratio: 3
vertical_ratio: 2
description_strategy: Coordinate
sizes: [96, 384]

Fluidigm96:
horizontal_ratio: 3
vertical_ratio: 8
description_strategy: Sequential
sizes: [96]

Fluidigm192:
horizontal_ratio: 3
vertical_ratio: 4
description_strategy: Sequential
sizes: [192]

StripTubeColumn:
horizontal_ratio: 1,
vertical_ratio: 8,
description_strategy: Sequential
sizes: [8]

ChromiumChip:
horizontal_ratio: 4
vertical_ratio: 1
description_strategy: Coordinate
sizes: [16]
3 changes: 3 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@
jsonapi_resources :lot_types
jsonapi_resources :lots
jsonapi_resources :orders
jsonapi_resources :plate_purposes
jsonapi_resources :plate_templates
jsonapi_resources :plates
jsonapi_resources :poly_metadata
jsonapi_resources :pre_capture_pools
jsonapi_resources :primer_panels
jsonapi_resources :projects
Expand Down Expand Up @@ -284,6 +286,7 @@

resources :requests do
resources :comments, controller: 'requests/comments'
resources :poly_metadata, controller: 'requests/poly_metadata'

member do
get :history
Expand Down
14 changes: 14 additions & 0 deletions db/migrate/20231204163029_create_poly_metadata.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# frozen_string_literal: true
#
# Add polymorphic metadata table to flexibly hold key value pairs
class CreatePolyMetadata < ActiveRecord::Migration[6.0]
def change
create_table :poly_metadata do |t|
t.string :key, null: false
t.string :value, null: false
t.references :metadatable, polymorphic: true, null: false

t.timestamps
end
end
end
12 changes: 11 additions & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 2023_05_12_102847) do
ActiveRecord::Schema.define(version: 2023_12_04_163029) do

create_table "aliquot_indices", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC", force: :cascade do |t|
t.integer "aliquot_id", null: false
Expand Down Expand Up @@ -865,6 +865,16 @@
t.index ["uploaded_file_name"], name: "index_plate_volumes_on_uploaded_file_name"
end

create_table "poly_metadata", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci", force: :cascade do |t|
t.string "key", null: false
t.string "value", null: false
t.string "metadatable_type", null: false
t.bigint "metadatable_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["metadatable_type", "metadatable_id"], name: "index_poly_metadata_on_metadatable_type_and_metadatable_id"
end

create_table "pooling_methods", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC", force: :cascade do |t|
t.string "pooling_behaviour", limit: 50, null: false
t.text "pooling_options", size: :medium
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@
Given(/^I have a plate "([^"]*)" with the following wells:$/) do |plate_barcode, well_details|
plate = FactoryBot.create :plate, barcode: plate_barcode
well_details.hashes.each do |well_detail|
well = Well.create!(map: Map.find_by(description: well_detail[:well_location], asset_size: 96), plate: plate)
well =
Well.create!(map: Map.find_by(description: well_detail[:well_location], asset_size: plate.size), plate: plate)
well.well_attribute.update!(
concentration: well_detail[:measured_concentration],
measured_volume: well_detail[:measured_volume]
Expand Down
Loading

0 comments on commit b189ed8

Please sign in to comment.