Length Validations Ensuring That a String Attribute Is of Appropriate Length

In this demonstration, I will show how to add a length validation to an existing model class. In general, model validations are used to ensure that only valid data are saved to the database. The pre-defined length validation helper is used specifically to ensure that a specified model attribute meets a specified minimum and/or maximum length.

General Steps

In general, the steps for adding a presence validation to a model class are as follows.

Validating the Length of the Body in a Review Model Object

To demonstrate the steps for adding a length validation to a model class, we will be building upon a movie-review base app by adding a validation to the existing Review model class (depicted in Figure 1) that ensures that a review’s body attribute is at least 50 characters.

A class diagram depicting a Review model class with the following attributes: a title string, a score decimal number, body text, a genre string, a link string, a release date, and a review date

Figure 1. The Review model class.

Base App Code

Step 1 Add a Validation Declaration to the Model Class

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:

# == Schema Information
#
# Table name: reviews
#
#  id           :bigint           not null, primary key
#  body         :text
#  genre        :string
#  link         :string
#  release_date :date
#  review_date  :date
#  score        :decimal(, )
#  title        :string
#  created_at   :datetime         not null
#  updated_at   :datetime         not null
#
class Review < ApplicationRecord

  validates :body, length: { minimum: 50 }

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.

Test It!

To confirm that we made this change correctly, we test it using the Rails console.

To start the console, we run this command:

rails console

As a first test of our length model validation, we check whether we can create and save valid Review objects in the database by entering a call to the create class method, like this:

r = Review.create(
  title: 'The Matrix',
  score: 9.7,
  body: 'I love the Matrix so much that I saw it eight times in theaters and had plastic surgery to look like Keanu Reeves. Now everyone asks if I\'m Keanu. My life is actually kind of sad.',
  genre: 'Science Fiction',
  link: 'https://www.imdb.com/title/tt0133093/',
  release_date: Date.new(1999, 3, 31),
  review_date: Date.new(2020, 12, 2)
)

The console output should look like this:

  TRANSACTION (0.3ms)  BEGIN
  Review Create (1.8ms)  INSERT INTO "reviews" ("body", "genre", "link", "release_date", "review_date", "score", "title", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING "id"  [["body", "I love the Matrix so much that I saw it eight times in theaters and had plastic surgery to look like Keanu Reeves. Now everyone asks if I'm Keanu. My life is actually kind of sad."], ["genre", "Science Fiction"], ["link", "https://www.imdb.com/title/tt0133093/"], ["release_date", "1999-03-31"], ["review_date", "2020-12-02"], ["score", "9.7"], ["title", "The Matrix"], ["created_at", "2021-01-29 22:01:23.998414"], ["updated_at", "2021-01-29 22:01:23.998414"]]
  TRANSACTION (0.3ms)  COMMIT
 => #<Review id: 1, body: "I love the Matrix so much that I saw it eight time...", genre: "Science Fiction", link: "https:... 

Note that the call to create succeeded, returning a Review object.

Also, note that we stored a reference to the returned Review object in a variable, r.

Next, we test that our length validation will detect a body value with less than 50 characters.

We first set the body to 1 2 3 4 5, like this:

r.body = '1 2 3 4 5'

We then attempt to save this change to the database, like this:

r.save

The output of the command should look only like this:

 => false

The return value of false indicates that the call to save was unsuccessful. Note that no SQL commands are printed, because the change wasn’t saved to the database.

When commands that save model objects to the database fail, error messages are attached to the model object. To inspect the error messages in this case, we use the errors method, like this:

r.errors.full_messages

The call should return this value:

 => ["Body is too short (minimum is 50 characters)"] 

Note that the error message indicates that it was the short length of the body that caused the save method to fail.

If we wanted to, we could repeat the above steps, only setting the body attribute to an empty string ("") or a string with only whitespace characters (e.g., " "); however, these test should be unnecessary—if the validation works for short lengths, it should also work for these blank string values.

Satisfied that our length validation is functioning as expected, we quit the Rails console by entering this command:

exit

Step 1 Changeset

Step 2 Add a Model Test for the Validation

To add a model test for verifying that our length validation works correctly, we follow the general steps from the model tests demo.

Substep Create fixtures. In the base app, the Review model class already has this test fixture (in test/fixtures/reviews.yml):

# == Schema Information
#
# Table name: reviews
#
#  id           :bigint           not null, primary key
#  body         :text
#  genre        :string
#  link         :string
#  release_date :date
#  review_date  :date
#  score        :decimal(, )
#  title        :string
#  created_at   :datetime         not null
#  updated_at   :datetime         not null
#

one:
  title: The Matrix
  score: 9.7
  body: I love the Matrix so much that I saw it eight times in theaters and had plastic surgery to look like Keanu Reeves. Now everyone asks if I'm Keanu. My life is actually kind of sad.
  genre: Science Fiction
  link: https://www.imdb.com/title/tt0133093/
  release_date: 1999-03-31
  review_date: 2020-12-02

This fixture will be sufficient for this model test, so we can skip this substep.

Substep Add an empty model test. We add an empty model test to the ReviewTest class (in test/models/review_test.rb), like this:

# == Schema Information
#
# Table name: reviews
#
#  id           :bigint           not null, primary key
#  body         :text
#  genre        :string
#  link         :string
#  release_date :date
#  review_date  :date
#  score        :decimal(, )
#  title        :string
#  created_at   :datetime         not null
#  updated_at   :datetime         not null
#
require "test_helper"

class ReviewTest < ActiveSupport::TestCase

  test "all fixtures should be valid" do
    review_one = reviews(:one)
    assert review_one.valid?, review_one.errors.full_messages.inspect
  end

  test "body should be longer than 50 characters" do
    
  end

end

Note that the ReviewTest class contained one test already ("all fixtures should be valid") to verify that all the fixtures are valid, and we added our new test ("body should be longer than 50 characters") beneath it.

Substep Retrieve the fixture object. To retrieve the fixture object, we add a call to the fixture-retrieving method, reviews, like this:

class ReviewTest < ActiveSupport::TestCase

  …

  test "body should be longer than 50 characters" do
    review_one = reviews(:one)
  end

end

Substep Manipulate the fixture object. Because we want to ensure that a review with a short body attribute cannot be saved to the database, we set the body of the retrieved fixture object to 1 2 3 4 5, like this:

class ReviewTest < ActiveSupport::TestCase

  …

  test "body should be longer than 50 characters" do
    review_one = reviews(:one)
    review_one.body = '1 2 3 4 5'
  end

end

Substep Check for expected behavior. In this case, the test succeeds if the validation catches the error, so we want to make sure the fixture is not valid. Thus, we use the assert_not function in conjunction with the valid? method, like this:

class ReviewTest < ActiveSupport::TestCase

  …

  test "body should be longer than 50 characters" do
    review_one = reviews(:one)
    review_one.body = '1 2 3 4 5'
    assert_not review_one.valid?
  end

end

Test It!

To confirm that we made this change correctly, we run the test we created, like this:

rails test -v

Technically, this command runs all the tests written for the web app. It should produce output similar to this:

Running via Spring preloader in process 78139
Run options: -v --seed 38274

# Running:

ReviewTest#test_all_fixtures_should_be_valid = 0.06 s = .
ReviewTest#test_body_should_be_longer_than_50_characters = 0.16 s = .

Finished in 0.204849s, 9.7633 runs/s, 9.7633 assertions/s.
2 runs, 2 assertions, 0 failures, 0 errors, 0 skips

Note that the output shows that our newly created test, ReviewTest#test_body_should_be_longer_than_50_characters, ran and that there were “0 failures” and “0 errors”. Also, the output shows “2 runs”, because the ReviewTest class contains two tests, and it shows “2 assertions”, because each of those tests executed a call to one assertion.

Step 2 Changeset

Conclusion

Following the above steps, we have now added a length validation to a model class to ensure that one of its attributes has a minimum length of 50 characters.

Demo App Code