From 06c0348009cce05837bcd5f14e02ce15b2bd1ec0 Mon Sep 17 00:00:00 2001 From: James Glover Date: Fri, 10 Jun 2022 12:02:12 +0100 Subject: [PATCH] Add basic outline for ONT request - Validate all attributes as needed - Add associations with library type and data_type - Add pipeline validation for library_type/data_type --- app/models/ont/request.rb | 10 +++ app/validators/pipeline_validator.rb | 34 ++++++++++ config/locales/en.yml | 1 + spec/factories/ont/requests.rb | 3 + spec/models/ont/request_spec.rb | 76 +++++++++++++++++++++- spec/validators/pipeline_validator_spec.rb | 42 ++++++++++++ 6 files changed, 164 insertions(+), 2 deletions(-) create mode 100644 app/validators/pipeline_validator.rb create mode 100644 spec/validators/pipeline_validator_spec.rb diff --git a/app/models/ont/request.rb b/app/models/ont/request.rb index 098a15261..e83fe4fe3 100644 --- a/app/models/ont/request.rb +++ b/app/models/ont/request.rb @@ -4,5 +4,15 @@ module Ont # Ont::Request class Request < ApplicationRecord include Material + + belongs_to :library_type + belongs_to :data_type + + validates :cost_code, presence: true + validates :number_of_flowcells, numericality: { only_integer: true, greater_than: 0 } + validates :external_study_id, uuid: true + + validates :library_type, pipeline: :ont + validates :data_type, pipeline: :ont end end diff --git a/app/validators/pipeline_validator.rb b/app/validators/pipeline_validator.rb new file mode 100644 index 000000000..338d5daf0 --- /dev/null +++ b/app/validators/pipeline_validator.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +# Validates that attribute belongs to the configured pipeline +# @example +# validates :library_type, pipeline: :ont +# Requires that the associated attribute responds to +class PipelineValidator < ActiveModel::EachValidator + def initialize(options) + super + + # We'll give some helpful errors if we configure it wrong + raise ArgumentError, 'No target pipeline specified' if expected.nil? + raise ArgumentError, "#{expected} is not a recognised pipeline" unless valid_pipeline? + end + + def validate_each(record, attribute, value) + return if value.blank? + + actual = value.pipeline + return if actual == expected.to_s + + record.errors.add(attribute, :pipeline_invalid, { expected: expected, actual: actual }) + end + + private + + def expected + options[:with] + end + + def valid_pipeline? + Pipelines::ENUMS.key?(expected) + end +end diff --git a/config/locales/en.yml b/config/locales/en.yml index fa5d61418..7185515fd 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -57,6 +57,7 @@ en: errors: messages: uuid: is not a valid uuid + pipeline_invalid: is in %{actual} not %{expected} pipeline attributes: cost_code: blank: must be present diff --git a/spec/factories/ont/requests.rb b/spec/factories/ont/requests.rb index dd045e6ed..905cba23a 100644 --- a/spec/factories/ont/requests.rb +++ b/spec/factories/ont/requests.rb @@ -4,5 +4,8 @@ factory :ont_request, class: 'Ont::Request' do external_study_id cost_code + + association :library_type, :ont + association :data_type, :ont end end diff --git a/spec/models/ont/request_spec.rb b/spec/models/ont/request_spec.rb index c0b03af96..c4a3af5c6 100644 --- a/spec/models/ont/request_spec.rb +++ b/spec/models/ont/request_spec.rb @@ -4,8 +4,80 @@ require './spec/support/read_only' RSpec.describe Ont::Request, type: :model, ont: true do - before do - set_read_only(described_class, false) + describe '#valid?' do + subject { build :ont_request, attributes } + + context 'with all required attributes' do + let(:attributes) { {} } + + it { is_expected.to be_valid } + end + + context 'without a costcode' do + let(:attributes) { { cost_code: '' } } + + it { is_expected.not_to be_valid } + end + + context 'without an external_study_id' do + let(:attributes) { { external_study_id: '' } } + + it { is_expected.not_to be_valid } + end + + context 'with a non uuid external_study_id' do + let(:attributes) { { external_study_id: '2' } } + + it { is_expected.not_to be_valid } + end + + context 'without a number of flowcells' do + let(:attributes) { { number_of_flowcells: '' } } + + it { is_expected.not_to be_valid } + end + + context 'with a negative of flowcells' do + let(:attributes) { { number_of_flowcells: -3 } } + + it { is_expected.not_to be_valid } + end + + context 'with a non-integer of flowcells' do + let(:attributes) { { number_of_flowcells: 3.5 } } + + it { is_expected.not_to be_valid } + end + end + + describe '#library_type' do + subject(:request) { build(:ont_request, attributes) } + + context 'when set' do + let(:library_type) { build :library_type, :ont } + let(:attributes) { { library_type: library_type } } + + it { expect(request.library_type).to eq library_type } + it { is_expected.to be_valid } + end + + context 'when from a different pipeline' do + let(:library_type) { build :library_type, :pacbio } + let(:attributes) { { library_type: library_type } } + + it { is_expected.not_to be_valid } + end + end + + describe '#data_type' do + subject { build(:ont_request, attributes).data_type } + + context 'when set' do + let(:data_type) { build :data_type, :ont } + let(:attributes) { { data_type: data_type } } + + it { is_expected.to eq data_type } + end end context 'material' do diff --git a/spec/validators/pipeline_validator_spec.rb b/spec/validators/pipeline_validator_spec.rb new file mode 100644 index 000000000..94e0e7a8f --- /dev/null +++ b/spec/validators/pipeline_validator_spec.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe PipelineValidator do + describe '#validate_each' do + let(:record_class) { Ont::Request } + let(:record) { record_class.new } + let(:attribute) { :library_type } + let(:pipeline) { :ont } + + before do + described_class.new(attributes: [attribute], with: pipeline).validate_each(record, attribute, value) + end + + context 'with a nil value' do + let(:value) { nil } + + # We'll allow nil here, and let the presence validator handle that if + # required + it 'does not add an error to the record' do + expect(record.errors.full_messages).to be_empty + end + end + + context 'with the incorrect pipeline' do + let(:value) { build :library_type, :pacbio } + + it 'adds an error to the record' do + expect(record.errors.full_messages).to include('Library type is in pacbio not ont pipeline') + end + end + + context 'with the correct pipeline' do + let(:value) { build :library_type, pipeline } + + it 'does not add an error to the record' do + expect(record.errors.full_messages).to be_empty + end + end + end +end