Skip to content

Commit

Permalink
Merge pull request #1479 from sanger/y24-411-batch-library-creation
Browse files Browse the repository at this point in the history
Y24-411 - PacBio batch library creation
  • Loading branch information
BenTopping authored Nov 8, 2024
2 parents 89b388c + b7dfa5f commit 79f1fa1
Show file tree
Hide file tree
Showing 14 changed files with 324 additions and 1 deletion.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,13 @@ See Confluence for further information

We've used Yard as the documentation tool to document the service. This can be accessed via https://sanger.github.io/traction-service/.

To view the Yard docs locally, use the following command:

```shell
bundle exec yard
```
Open up the doc/index.html file in a browser.

To query all the `@todo` items in Yard, the following command can be used:

```shell
Expand Down
9 changes: 9 additions & 0 deletions app/controllers/v1/pacbio/library_batches_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true

module V1
module Pacbio
# LibraryBatchesController
class LibraryBatchesController < ApplicationController
end
end
end
2 changes: 2 additions & 0 deletions app/models/pacbio/library.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ class Library < ApplicationRecord
delegate :sample_name, :cost_code, :external_study_id, :library_type, to: :request
belongs_to :tag, optional: true
belongs_to :tube, default: -> { Tube.new }
belongs_to :library_batch, optional: true, class_name: 'Pacbio::LibraryBatch',
foreign_key: :pacbio_library_batch_id, inverse_of: :libraries

has_one :sample, through: :request
has_one :tag_set, through: :tag
Expand Down
14 changes: 14 additions & 0 deletions app/models/pacbio/library_batch.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# frozen_string_literal: true

module Pacbio
# Pacbio::LibraryBatch is a audit record created to keep track of libraries created in batches
class LibraryBatch < ApplicationRecord
validates :libraries, presence: true

has_many :libraries, class_name: 'Pacbio::Library', dependent: :nullify,
foreign_key: :pacbio_library_batch_id, inverse_of: :library_batch

# This allows the creation of libraries when creating a library batch
accepts_nested_attributes_for :libraries
end
end
74 changes: 74 additions & 0 deletions app/resources/v1/pacbio/library_batch_resource.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# frozen_string_literal: true

module V1
module Pacbio
# This resource is used to create batches of libraries in one go.
# This resource can only be accessed via a `POST` request to the library_batches endpoint.
#
# @example
# {
# "data": {
# "type": "library_batches",
# "attributes": {
# "libraries_attributes": [
# {
# "volume": 50.2,
# "concentration": 2.222,
# "template_prep_kit_box_barcode": "LK1234567",
# "insert_size": 100,
# "pacbio_request_id": 1,
# "tag_id": 1,
# "primary_aliquot_attributes": {
# "volume": 50.2,
# "concentration": 2.222,
# "template_prep_kit_box_barcode": "LK1234567",
# "insert_size": 100,
# "tag_id": 1
# },
# }
# ]
# }
# }
#
# @note
# Access this resource via the `/v1/pacbio/library_batches/` endpoint.
# To return the libraries created, add `libraries` to the include.
# To return the created libraries tubes add `libraries.tube` to the include.
# For example `/v1/pacbio/library_batches?include=libraries.tube`
#
#
# Provides a JSON:API representation of {Pacbio::LibraryBatch}.
#
# For more information about JSON:API see the [JSON:API Specifications](https://jsonapi.org/format/)
# or look at the [JSONAPI::Resources](http://jsonapi-resources.com/) package
# for the service implementation of the JSON:API standard.
class LibraryBatchResource < JSONAPI::Resource
model_name 'Pacbio::LibraryBatch'

# @!attribute [w] created_at
# @return [String] the creation time of the library
# @!attribute [w] libraries_attributes
# @return [Array] the attributes of the libraries
attributes :created_at, :libraries_attributes

has_many :libraries, always_include_optional_linkage_data: true

def libraries_attributes=(libraries_attributes_parameters)
@model.libraries_attributes = libraries_attributes_parameters.map do |library|
library.permit(
:volume, :template_prep_kit_box_barcode,
:concentration, :insert_size, :tag_id,
:pacbio_request_id,
primary_aliquot_attributes: %i[
volume concentration template_prep_kit_box_barcode insert_size tag_id
]
)
end
end

def fetchable_fields
super - [:libraries_attributes]
end
end
end
end
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
jsonapi_resources :pools, except: %i[destroy]
jsonapi_resources :smrt_link_versions, only: %i[index show]
jsonapi_resources :smrt_link_options, only: %i[index show]
jsonapi_resources :library_batches, only: %i[create]
end

namespace :ont do
Expand Down
9 changes: 9 additions & 0 deletions db/migrate/20241106153044_create_pacbio_library_batch.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class CreatePacbioLibraryBatch < ActiveRecord::Migration[7.2]
def change
create_table :pacbio_library_batches do |t|
t.timestamps
end

add_reference :pacbio_libraries, :pacbio_library_batch, index: true, foreign_key: true, null: true
end
end
10 changes: 9 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[7.2].define(version: 2024_09_27_074531) do
ActiveRecord::Schema[7.2].define(version: 2024_11_06_153044) do
create_table "aliquots", charset: "utf8mb3", force: :cascade do |t|
t.float "volume"
t.float "concentration"
Expand Down Expand Up @@ -190,11 +190,18 @@
t.bigint "pacbio_request_id", null: false
t.bigint "tag_id"
t.bigint "tube_id"
t.bigint "pacbio_library_batch_id"
t.index ["pacbio_library_batch_id"], name: "index_pacbio_libraries_on_pacbio_library_batch_id"
t.index ["pacbio_request_id"], name: "index_pacbio_libraries_on_pacbio_request_id"
t.index ["tag_id"], name: "index_pacbio_libraries_on_tag_id"
t.index ["tube_id"], name: "index_pacbio_libraries_on_tube_id"
end

create_table "pacbio_library_batches", charset: "utf8mb3", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end

create_table "pacbio_plates", charset: "utf8mb3", force: :cascade do |t|
t.bigint "pacbio_run_id"
t.string "uuid"
Expand Down Expand Up @@ -512,6 +519,7 @@
add_foreign_key "ont_requests", "library_types"
add_foreign_key "ont_runs", "ont_instruments"
add_foreign_key "ont_runs", "ont_min_know_versions"
add_foreign_key "pacbio_libraries", "pacbio_library_batches"
add_foreign_key "pacbio_libraries", "pacbio_requests"
add_foreign_key "pacbio_libraries", "tubes"
add_foreign_key "pacbio_pools", "tubes"
Expand Down
Binary file modified erd-pacbio.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified erd.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions spec/factories/pacbio/library_batches.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

FactoryBot.define do
factory :pacbio_library_batch, class: 'Pacbio::LibraryBatch' do
libraries { build_list(:pacbio_library, 1) }
end
end
51 changes: 51 additions & 0 deletions spec/models/pacbio/library_batch_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe Pacbio::LibraryBatch, :pacbio do
it 'is valid with valid attributes' do
library_batch = build(:pacbio_library_batch)
expect(library_batch).to be_valid
end

it 'is not valid without libraries' do
library_batch = build(:pacbio_library_batch, libraries: [])
expect(library_batch).not_to be_valid
expect(library_batch.errors[:libraries]).to include("can't be blank")
end

it 'nullifies associated libraries on destroy' do
library_batch = create(:pacbio_library_batch)
library = create(:pacbio_library, library_batch: library_batch)
library_batch.destroy
expect(library.reload.library_batch).to be_nil
end

describe 'creating nested libraries' do
define_negated_matcher :not_change, :change

let(:pacbio_requests_enum) { create_list(:pacbio_request, 8).cycle }
let(:pacbio_libraries) { build_list(:pacbio_library, 8, pacbio_request_id: pacbio_requests_enum.next.id) }
# Creates valid attributes for the libraries - including the primary aliquot attributes and a pacbio_request_id
let(:libraries_attributes) do
pacbio_libraries.map do |lib|
{ **lib.attributes, primary_aliquot_attributes: lib.primary_aliquot.attributes, pacbio_request_id: lib.request.id }
end
end

it 'accepts nested attributes for libraries' do
library_batch = described_class.new(libraries_attributes: libraries_attributes)
expect { library_batch.save! }.to change(Pacbio::Library, :count).by(8)
expect(library_batch.libraries.count).to eq(8)
end

it 'does not save the library batch if a library is invalid' do
# Make the last library invalid
libraries_attributes.last[:pacbio_request_id] = nil

library_batch = described_class.new(libraries_attributes: libraries_attributes)
expect { library_batch.save! }.to raise_error(ActiveRecord::RecordInvalid).and not_change(Pacbio::Library, :count)
expect(library_batch.errors['libraries.request']).to include('must exist')
end
end
end
5 changes: 5 additions & 0 deletions spec/models/pacbio/library_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,11 @@
expect(build(:pacbio_library, tag:).tag).to eq(tag)
end

it 'can have a library batch' do
library_batch = build(:pacbio_library_batch)
expect(build(:pacbio_library, library_batch:).library_batch).to eq(library_batch)
end

it 'can have a primary aliquot' do
expect(create(:pacbio_library).primary_aliquot).to be_present
end
Expand Down
136 changes: 136 additions & 0 deletions spec/requests/v1/pacbio/library_batches_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe 'LibraryBatchesController', :pacbio do
describe '#create' do
context 'when creating a library batch' do
let!(:pacbio_requests) { create_list(:pacbio_request, 2) }
let!(:tag) { create(:tag) }

context 'on success' do
let(:body) do
{
data: {
type: 'library_batches',
attributes: {
libraries_attributes: [
{
volume: 1.11,
template_prep_kit_box_barcode: 'LK1234567',
concentration: 2.22,
insert_size: 100,
pacbio_request_id: pacbio_requests.first.id,
tag_id: tag.id,
primary_aliquot_attributes: {
volume: 1.11,
template_prep_kit_box_barcode: 'LK1234567',
concentration: 2.22,
insert_size: 100,
state: 'created',
tag_id: tag.id
}
},
volume: 1.11,
template_prep_kit_box_barcode: 'LK1234567',
concentration: 2.22,
insert_size: 100,
pacbio_request_id: pacbio_requests.second.id,
tag_id: tag.id,
primary_aliquot_attributes: {
volume: 1.11,
template_prep_kit_box_barcode: 'LK1234567',
concentration: 2.22,
insert_size: 100,
state: 'created',
tag_id: tag.id
}
]
}
}
}.to_json
end

it 'has a created status' do
post v1_pacbio_library_batches_path, params: body, headers: json_api_headers
expect(response).to have_http_status(:created), response.body
end

it 'creates a library and aliquots' do
expect { post v1_pacbio_library_batches_path, params: body, headers: json_api_headers }
.to change(Pacbio::Library, :count).by(2)
.and change(Aliquot, :count).by(4) # We create a primary aliquot and a used_by aliquot for each library
end

it 'returns the id' do
post v1_pacbio_library_batches_path, params: body, headers: json_api_headers
expect(json.dig('data', 'id').to_i).to eq(Pacbio::LibraryBatch.first.id)
end

it 'includes the libraries and their tubes' do
post "#{v1_pacbio_library_batches_path}?include=libraries.tube", params: body, headers: json_api_headers
expect(json['included'].length).to eq(4)
expect(json['included'].filter { |record| record['type'] == 'tubes' }.length).to eq(2)
expect(json['included'].filter { |record| record['type'] == 'libraries' }.length).to eq(2)
end
end

context 'on failure - when a library is invalid' do
let(:body) do
{
data: {
type: 'library_batches',
attributes: {
libraries_attributes: [
{
volume: 1.11,
template_prep_kit_box_barcode: 'LK1234567',
concentration: 2.22,
insert_size: 'invalid insert size',
pacbio_request_id: pacbio_requests.first.id,
tag_id: tag.id,
primary_aliquot_attributes: {
volume: 1.11,
template_prep_kit_box_barcode: 'LK1234567',
concentration: 2.22,
insert_size: 100,
state: 'created',
tag_id: tag.id
}
},
volume: 1.11,
template_prep_kit_box_barcode: 'LK1234567',
concentration: 2.22,
insert_size: 100,
pacbio_request_id: pacbio_requests.second.id,
tag_id: tag.id,
primary_aliquot_attributes: {
volume: 1.11,
template_prep_kit_box_barcode: 'LK1234567',
concentration: 2.22,
insert_size: 100,
state: 'created',
tag_id: tag.id
}
]
}
}
}.to_json
end

it 'returns unprocessable entity status' do
post v1_pacbio_library_batches_path, params: body, headers: json_api_headers
expect(response).to have_http_status(:unprocessable_entity)
expect(response.body).to include('libraries.insert_size - is not a number')
end

it 'cannot create a library' do
expect { post v1_pacbio_library_batches_path, params: body, headers: json_api_headers }.not_to(
change(Pacbio::Library, :count) &&
change(Aliquot, :count)
)
end
end
end
end
end

0 comments on commit 79f1fa1

Please sign in to comment.