Creating and Testing Presence Validations

In this demonstration, I will show how to use the Rails model validation helper, presence, to enforce that specified model attribute values are present before a record can be saved to the database. We will continue to build upon the QuizMe project from the previous demos.

Rails model validations aim to prevent invalid data from being saved to the database. For example, recall the McQuestion model class we created for multiple-choice questions, shown in Figure 1. There are a number of requirements that we would like to enforce regarding what attribute values are and are not valid. For instance, the question, answer, and distractor_1 attributes should all have a value. If a McQuestion object was missing values for any of those attributes, we would consider that McQuestion object to be invalid.

A UML class diagram depicting the McQuestion model class

Figure 1. The McQuestion model class.

To prevent such invalid records from being saved to the database, we will customize our McQuestion model class by adding presence model validations. The purpose of the presence validation helper is to make it so that a specified model class attribute must have a non-nil, non-blank value in order for the model valid? method to return true. If the specified attribute is assigned a value of nil, an empty string (""), or a blank string, comprised only of whitespace characters, then valid? will return false. This validation helps to prevent invalid records from being saved to the database, because Rails automatically rejects saving any model object whose valid? method returns false. Note that, in addition to presence, Rails provides a number of other useful validation helpers.

1. Adding presence Validations to the McQuestion Model Class

In the body of the McQuestion class (in app/models/mc_question.rb), insert a presence validation for each of the attributes, question, answer, and distractor_1, like this:

validates :question, presence: true
validates :answer, presence: true
validates :distractor_1, presence: true

Note that we did not add a presence validation to distractor_2. Such a validation would inadvertently flag all true/false questions as invalid, which is not what we want.

Verify that we didn’t accidentally introduce a syntax error into the model class by running the valid McQuestion fixture tests from last demo, like this:

rails test

If no syntax errors were made, the test should produce the same output as last time.

Code changeset for this part

2. Testing the presence Validations in the McQuestion Model Class

Although running our previously created valid fixture tests will catch syntax errors, we also need to add tests to verify that we didn’t make any logic errors in writing our presence validations.

It is considered a best practice for each model test to cover at most one validation for a single attribute. Thus, we will next write a test to verify the presence validation for the question attribute. Since the presence validation catches both nil and empty string values, we can check both in the same test.

Test the presence validation on the question attribute by inserting a new test into the McQuestionTest class (in test/models/mc_question_test.rb), like this:

test "question presence not valid" do
    q = mc_questions(:one)
    q.question = nil
    assert_not q.valid?
    q.question = ""
    assert_not q.valid?
end

Note that this test follows the three basic steps for writing model tests mentioned in the previous demo: (1) it retrieves a valid fixture; (2) it does some setting of fixture attributes (in this case, to make the question attribute invalid); and (3) it makes some assertions about the state of the model object (in this case, it asserts that the model object is invalid).

Check that the test runs as expected by entering the following command:

rails test

If everything is correct, the test should produce output like this:

Finished in 0.211335s, 7.0493 runs/s, 14.3192 assertions/s.
2 runs, 4 assertions, 0 failures, 0 errors, 0 skips

If the test fails, then there is a bug, most likely in either the model validation code, the fixture code, or the test code, and you must fix it.

Following the same approach that was used to write the above test for the presence validation on the question attribute, add two more tests to McQuestionTest for the remaining presence validations, one for the answer attribute and one for the distractor_1 attribute. After you add each test, run rails test to confirm that the test works.

Code changeset for this part