Validation HelpersFurther information on pre-defined helpers to validate data for model attributes
In this deets, I will further explain the different types of Validation Helpers (previously explained in the Validation Helpers Demo) and how to use them. Model Validations are used to ensure that only valid data are saved to the database. We can use validation helpers that are provided by rails to easily validate data, or we can build our own custom validations.
To explain the different types of validation helpers to a model class, we will be adding a validation to the existing Review model class (depicted in Figure 1) from the movie-review base app branch.
Figure 1. The Review model class.
Length Validation
To add a length validation for the body attribute of the Review model class, we add a validates declaration to the class definition (found in app/models/review.rb), like this:
1 | # == Schema Information |
2 | # |
3 | # Table name: reviews |
4 | # |
5 | # id :bigint not null, primary key |
6 | # body :text |
7 | # genre :string |
8 | # link :string |
9 | # release_date :date |
10 | # review_date :date |
11 | # score :decimal(, ) |
12 | # title :string |
13 | # created_at :datetime not null |
14 | # updated_at :datetime not null |
15 | # |
16 | class Review < ApplicationRecord |
17 | |
18 | validates :body, length: { minimum: 50 } |
19 | |
20 | end |
Breaking down this line of code, the call to validates adds the specified validation to the model class. The :body argument specifies that the validation will apply to the body attribute of the class. The length: { minimum: 50 } argument specifies that the length validation helper will be applied to the attribute. For a string attribute, like body, this validation ensures that the attribute is at least 50 characters long.
Numericality Validation
To add a numericality validation for the score attribute of the Review model class, we add a validates declaration to the class definition (found in app/models/review.rb), like this:
1 | # == Schema Information |
2 | # |
3 | # Table name: reviews |
4 | # |
5 | # id :bigint not null, primary key |
6 | # body :text |
7 | # genre :string |
8 | # link :string |
9 | # release_date :date |
10 | # review_date :date |
11 | # score :decimal(, ) |
12 | # title :string |
13 | # created_at :datetime not null |
14 | # updated_at :datetime not null |
15 | # |
16 | class Review < ApplicationRecord |
17 | |
18 | validates :score, numericality: true |
19 | |
20 | end |
Breaking down this line of code, the call to validates adds the specified validation to the model class. The :score argument specifies that the validation will apply to the score attribute of the class. The numericality: true argument specifies that the numericality validation helper will be applied to the attribute. This validation ensures that the attribute is a numeral, like 7 and not an alphabetic string such as seven.
Inclusion Validation
To add a inclusion validation for the genre attribute of the Review model class, we add a validates declaration to the class definition (found in app/models/review.rb), like this:
1 | # == Schema Information |
2 | # |
3 | # Table name: reviews |
4 | # |
5 | # id :bigint not null, primary key |
6 | # body :text |
7 | # genre :string |
8 | # link :string |
9 | # release_date :date |
10 | # review_date :date |
11 | # score :decimal(, ) |
12 | # title :string |
13 | # created_at :datetime not null |
14 | # updated_at :datetime not null |
15 | # |
16 | class Review < ApplicationRecord |
17 | |
18 | validates :genre, inclusion: { in: ['Action', 'Science Fiction', 'Drama', 'Horror', 'Comedy', 'Musical'] } |
19 | |
20 | end |
Breaking down this line of code, the call to validates adds the specified validation to the model class. The :genre argument specifies that the validation will apply to the genre attribute of the class. The inclusion: { in: ['Action', 'Science Fiction', 'Drama', 'Horror', 'Comedy', 'Musical'] } argument specifies that the inclusion validation helper will be applied to the attribute. This validation uses in: to specify an array of allowed values and ensures that the attribute is only one of those values.
Custom Validation
To add a custom validation to the Review model class, we follow these steps to add a validate declaration and custom validation method to the class definition (found in app/models/review.rb).
Create custom method. In app/models/review.rb, add a custom method to hold the validation logic with an appropriate name, like so:
1 | # == Schema Information |
2 | # |
3 | # Table name: reviews |
4 | # |
5 | # id :bigint not null, primary key |
6 | # body :text |
7 | # genre :string |
8 | # link :string |
9 | # release_date :date |
10 | # review_date :date |
11 | # score :decimal(, ) |
12 | # title :string |
13 | # created_at :datetime not null |
14 | # updated_at :datetime not null |
15 | # |
16 | class Review < ApplicationRecord |
17 | |
18 | def review_date_must_be_after_release_date |
19 | |
20 | end |
21 | |
22 | end |
Add statement to apply error to attribute unless value passes constraint. In this case, the constraint is that the object’s review_date must come after the release_date. Unless the constraint check passes, we want to add a statement that applies an Active Record error to the object with an appropriate message, like so:
1 | # == Schema Information |
2 | # |
3 | # Table name: reviews |
4 | # |
5 | # id :bigint not null, primary key |
6 | # body :text |
7 | # genre :string |
8 | # link :string |
9 | # release_date :date |
10 | # review_date :date |
11 | # score :decimal(, ) |
12 | # title :string |
13 | # created_at :datetime not null |
14 | # updated_at :datetime not null |
15 | # |
16 | class Review < ApplicationRecord |
17 | |
18 | def review_date_must_be_after_release_date |
19 | errors.add(:review_date, "must come after release date") unless review_date.after?(release_date) |
20 | end |
21 | |
22 | end |
Breaking down this line of code, we are using the add method to add an error message for an attribute to the object’s errors collection. In the call to add, the :review_date parameter indicates which attribute the error pertains to, and the "must come after release date" parameter is the custom error message we set on that attribute. The unless keyword is used as a modifier such that the preceding argument is executed if the following argument is not true. In this case, the error will be added if an object’s review_date is NOT after the release_date. We use the after method to compare the two Date values.
Add validation declaration to model class. Although we have written the validation function, we still need to tell Rails to run it when validating an object. To do this, we need to add a validate declaration for the custom method, like so:
1 | # == Schema Information |
2 | # |
3 | # Table name: reviews |
4 | # |
5 | # id :bigint not null, primary key |
6 | # body :text |
7 | # genre :string |
8 | # link :string |
9 | # release_date :date |
10 | # review_date :date |
11 | # score :decimal(, ) |
12 | # title :string |
13 | # created_at :datetime not null |
14 | # updated_at :datetime not null |
15 | # |
16 | class Review < ApplicationRecord |
17 | |
18 | validate :review_date_must_be_after_release_date |
19 | |
20 | def review_date_must_be_after_release_date |
21 | errors.add(:review_date, "must come after release date") unless review_date.after?(release_date) |
22 | end |
23 | |
24 | end |
Further Reading
For more info on model validations and validation helpers, see the Rails Guides.