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.
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.
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.