Iteration B1: Validating!

While playing with the results of iteration A1, our client noticed something. If she entered an invalid price or forgot to set up a product description, the application happily accepted the form and added a line to the database. A missing description is embarrassing, and a price of $0.00 costs her actual money, so she asked that we add validation to the application. No product should be allowed in the database if it has an empty title or description field, an invalid URL for the image, or an invalid price.

So, where do we put the validation? The model layer is the gatekeeper between the world of code and the database. Nothing to do with our application comes out of the database or gets stored into the database that doesn’t first go through the model. This makes models an ideal place to put validations; it doesn’t matter whether the data comes from a form or from some programmatic manipulation in our application. If a model checks it before writing to the database, the database will be protected from bad data.

Let’s look at the source code of the model class (in app/models/product.rb):

 class​ Product < ApplicationRecord
 end

Adding our validation should be fairly clean. Let’s start by validating that the text fields all contain something before a row is written to the database. We do this by adding some code to the existing model:

 validates ​:title​, ​:description​, ​:image_url​, ​presence: ​​true

The validates method is the standard Rails validator. It checks one or more model fields against one or more conditions.

presence: true tells the validator to check that each of the named fields is present and that its contents aren’t empty. The following screenshot shows what happens if we try to submit a new product with none of the fields filled in. Try it by visiting http://localhost:3000/products/new and submitting the form without entering any data. It’s pretty impressive: the fields with errors are highlighted, and the errors are summarized in a nice list at the top of the form. That’s not bad for one line of code. You might also have noticed that after editing and saving the product.rb file, you didn’t have to restart the application to test your changes. The same reloading that caused Rails to notice the earlier change to our schema also means it’ll always use the latest version of our code.

images/b_1_validation_errors.png

We’d also like to validate that the price is a valid, positive number. We’ll use the delightfully named numericality option to verify that the price is a valid number. We also pass the rather verbosely named :greater_than_or_equal_to option a value of 0.01:

 validates ​:price​, ​numericality: ​{ ​greater_than_or_equal_to: ​0.01 }

Now, if we add a product with an invalid price, the appropriate message will appear, as shown in the following screenshot.

images/b_2_price_validation_errors.png

Why test against one cent, rather than zero? Well, it’s possible to enter a number such as 0.001 into this field. Because the database stores just two digits after the decimal point, this would end up being zero in the database, even though it would pass the validation if we compared against zero. Checking that the number is at least one cent ensures that only correct values end up being stored.

We have two more items to validate. First, we want to make sure that each product has a unique title. One more line in the Product model will do this. The uniqueness validation will perform a check to ensure that no other row in the products table has the same title as the row we’re about to save:

 validates ​:title​, ​uniqueness: ​​true

Lastly, we need to validate that the URL entered for the image is valid. We’ll do this by using the format option, which matches a field against a regular expression. For now, let’s just check that the URL ends with one of gif, jpg, or png:

 validates ​:image_url​, ​allow_blank: ​​true​, ​format: ​{
 with: ​​%r{​​.​​(gif|jpg|png)​​z​​}i​,
 message: ​​'must be a URL for GIF, JPG or PNG image.'
 }

The regular expression matches the string against a literal dot, followed by one of three choices, followed by the end of the string. Be sure to use vertical bars to separate options, and backslashes before the dot and the uppercase Z. If you need a refresher on regular expression syntax, see Regular Expressions.

Note that we use the allow_blank option to avoid getting multiple error messages when the field is blank.

Later, we’d probably want to change this form to let the user select from a list of available images, but we’d still want to keep the validation to prevent malicious folks from submitting bad data directly.

So, in a couple of minutes we’ve added validations that check the following:

  • The title, description, and image URL fields aren’t empty.
  • The price is a valid number not less than $0.01.
  • The title is unique among all products.
  • The image URL looks reasonable.

Your updated Product model should look like this:

 class​ Product < ApplicationRecord
  validates ​:title​, ​:description​, ​:image_url​, ​presence: ​​true
  validates ​:title​, ​uniqueness: ​​true
  validates ​:image_url​, ​allow_blank: ​​true​, ​format: ​{
 with: ​​%r{​​.​​(gif|jpg|png)​​z​​}i​,
 message: ​​'must be a URL for GIF, JPG or PNG image.'
  }
  validates ​:price​, ​numericality: ​{ ​greater_than_or_equal_to: ​0.01 }
 
 end

Nearing the end of this cycle, we ask our customer to play with the application, and she’s a lot happier. It took only a few minutes, but the simple act of adding validation has made the product maintenance pages seem a lot more solid.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.141.200.180