Creating and Testing Custom Validations
In this demonstration, I will show how to create a custom model validation to cover conditions that are beyond what the Rails validation helpers can handle. We will continue to build upon the QuizMe project from the previous demos.
In particular, we will create a custom validation to enforce the condition that all the possible answer values for a McQuestion
object (i.e., answer
, distractor_1
, and distractor_2
) should be different from each other. In the case of multiple choice questions, all the choices should be unique for a single question. The uniqueness
validation won’t help here, because it checks that an attribute’s value is unique over all the records in the database, not uniqueness of attribute values within an individual model object. Thus, we will create a custom validation that checks for three possible cases (answer == distractor_1
, distractor_1 == distractor_2
and answer == distractor_2
) and adds appropriate validation-error messages if they any of the cases are true.
1. Creating a Custom Validation for the McQuestion
Model Class
As a first step, add a validate
(singular) declaration for a new custom validation method, choices_cannot_be_duplicate
, to the McQuestion
model class, like this:
validate :choices_cannot_be_duplicate
We will now need to implement the choices_cannot_be_duplicate
method, which provides our custom validation logic.
Add a new choices_cannot_be_duplicate
method to the McQuestion
class, like this:
def choices_cannot_be_duplicate
# TODO: check cases
end
There are three cases that this method must check for.
First, check that the answer
is not the same as distractor_1
, like this:
if answer == distractor_1
errors.add(:distractor_1, "can't be the same as any other choice")
end
Note that if the answer
and distractor_1
attributes have the same value, then the choices_cannot_be_duplicate
should set the McQuestion
model object as invalid. To accomplish this, the method adds an error message to the model object’s errors
hash. In particular, the error message specifies the attribute that it the subject of the error (distractor_1
) and the human-readable description of the error ("can't be the same as any other choice"
).
Second, similarly to how we handled distractor_1
, check that the answer
is not the same as distractor_2
, like this:
if answer == distractor_2
errors.add(:distractor_2, "can't be the same as any other choice")
end
Third, check that the distractors are not the same, like this:
if distractor_1 == distractor_2
errors.add(:distractor_2, "can't be the same as any other choice")
end
Verify that we didn’t accidentally introduce a syntax error into the model class by running the valid McQuestion
fixture tests, like this:
rails test
If no syntax errors were made, the tests should run with no failures and no errors.
2. Testing the Custom Validation in the McQuestion
Model Class
Add a test to verify that we implemented the custom validation correctly. In particular, for each duplication case, the test will retrieve a fixture object, set the object’s attributes to create the duplication, and assert that the object is not valid, like this:
test "choices cannot be duplicate not valid" do
q = mc_questions(:one)
q.distractor_1 = q.answer
assert_not q.valid?
q = mc_questions(:one)
q.distractor_2 = q.answer
assert_not q.valid?
q = mc_questions(:one)
q.distractor_1 = q.distractor_2
assert_not q.valid?
end
Note that the fixture needs to be retrieved anew for each case to reset its attributes.
Check that the test runs as expected by entering the following command:
rails test
If the custom validation works correctly, the tests should run with no failures and no errors.
In the last few demos, we have introduced a few common validation scenarios. For a complete list of validation helpers and more, see the Rails Guide on Active Record Validations.