Commit 95aad49c authored by Dallas Reedy's avatar Dallas Reedy

Validate that there is exactly one subject present

- Update the database constraint
- Adjust the existing customer validator on ExperimentSubject :base
- Add private non_nil_subjects helper method to model
parent 35cfaf5e
......@@ -10,15 +10,19 @@ class ExperimentSubject < ApplicationRecord
validates :experiment, presence: true
validates :variant, presence: true
validate :must_have_at_least_one_subject
validate :must_have_one_subject_present
enum variant: { GROUP_CONTROL => 0, GROUP_EXPERIMENTAL => 1 }
private
def must_have_at_least_one_subject
if [user, group, project].all?(&:blank?)
errors.add(:base, s_("ExperimentSubject|Must have at least one of User, Group, or Project."))
def must_have_one_subject_present
if non_nil_subjects.length != 1
errors.add(:base, s_("ExperimentSubject|Must have exactly one of User, Group, or Project."))
end
end
def non_nil_subjects
@non_nil_subjects ||= [user, group, project].reject(&:blank?)
end
end
......@@ -13,9 +13,9 @@ class CreateExperimentSubjects < ActiveRecord::Migration[6.0]
t.timestamps_with_timezone null: false
end
# Require at least one of user_id, group_id, or project_id to be NOT NULL
# Require exactly one of user_id, group_id, or project_id to be NOT NULL
execute <<-SQL
ALTER TABLE experiment_subjects ADD CONSTRAINT chk_at_least_one_subject CHECK (NOT ROW(user_id, group_id, project_id) IS NULL);
ALTER TABLE experiment_subjects ADD CONSTRAINT chk_has_one_subject CHECK (num_nonnulls(user_id, group_id, project_id) = 1);
SQL
end
......
......@@ -12147,7 +12147,7 @@ CREATE TABLE experiment_subjects (
variant smallint DEFAULT 0 NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
CONSTRAINT chk_at_least_one_subject CHECK ((NOT (ROW(user_id, group_id, project_id) IS NULL)))
CONSTRAINT chk_has_one_subject CHECK ((num_nonnulls(user_id, group_id, project_id) = 1))
);
CREATE SEQUENCE experiment_subjects_id_seq
......
......@@ -11307,7 +11307,7 @@ msgstr ""
msgid "Experienced"
msgstr ""
msgid "ExperimentSubject|Must have at least one of User, Group, or Project."
msgid "ExperimentSubject|Must have exactly one of User, Group, or Project."
msgstr ""
msgid "Expiration"
......
......@@ -12,36 +12,43 @@ RSpec.describe ExperimentSubject, type: :model do
describe 'validations' do
it { is_expected.to validate_presence_of(:experiment) }
end
describe 'validate must_have_at_least_one_subject' do
let(:experiment_subject) { build(:experiment_subject, user: nil, group: nil, project: nil) }
it 'fails if user, group, & project are blank' do
expect(experiment_subject).not_to be_valid
expect(experiment_subject.errors[:base]).to include("Must have at least one of User, Group, or Project.")
end
it 'passes when user is present' do
experiment_subject.user = build(:user)
expect(experiment_subject).to be_valid
end
it 'passes when group is present' do
experiment_subject.group = build(:group)
expect(experiment_subject).to be_valid
end
it 'passes when project is present' do
experiment_subject.project = build(:project)
expect(experiment_subject).to be_valid
end
it 'passes when multiple subjects are present' do
experiment_subject.user = build(:user)
experiment_subject.group = build(:group)
experiment_subject.project = build(:project)
expect(experiment_subject).to be_valid
describe 'must_have_one_subject_present' do
let(:experiment_subject) { build(:experiment_subject, user: nil, group: nil, project: nil) }
let(:error_message) { 'Must have exactly one of User, Group, or Project.' }
it 'fails when no subject is present' do
expect(experiment_subject).not_to be_valid
expect(experiment_subject.errors[:base]).to include(error_message)
end
it 'passes when user subject is present' do
experiment_subject.user = build(:user)
expect(experiment_subject).to be_valid
end
it 'passes when group subject is present' do
experiment_subject.group = build(:group)
expect(experiment_subject).to be_valid
end
it 'passes when project subject is present' do
experiment_subject.project = build(:project)
expect(experiment_subject).to be_valid
end
it 'fails when more than one subject is present', :aggregate_failures do
# two subjects
experiment_subject.user = build(:user)
experiment_subject.group = build(:group)
expect(experiment_subject).not_to be_valid
expect(experiment_subject.errors[:base]).to include(error_message)
# three subjects
experiment_subject.project = build(:project)
expect(experiment_subject).not_to be_valid
expect(experiment_subject.errors[:base]).to include(error_message)
end
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment