Validating the form POST

We might write perfect code but, unfortunately, our users are mere mortals and thus are prone to giving us cruddy data by mistake. If you recall, in Chapter 1, Getting Started with Luminus, we used the Luminus template to generate the hipstr application, which includes the lib-noir library for us. One of the helper namespaces provided by lib-noir is a noir.validation namespace.

The noir.validation namespace

The noir.validation namespace provides methods to validate data in a variety of ways. It includes functions to check whether or not an input is nil, is an e-mail, is of a certain minimum length, and so on. This is excellent because I hate writing validation code, and I'm sure you do, too.

However, while noir.validation has a lot of functions that can be used to validate data, its actual validation framework makes some unfortunate assumptions as to how it will be used. It is also stateful, which makes it difficult to test and, frankly, is pretty unnecessary for a validation framework.

Instead of depending on the framework of noir.validation and its way of doing things, we're going to use some of its validation functions in conjunction with Validateur.

The Validateur library

The Validateur library is an alternative validation library to noir.validation. Using Validateur, we can define a collection of validators as a single function, which you can then apply against a map of key/values to be validated. The function will then return a map of all keys in the map that failed the validation and for each invalid key, a set of messages detailing why the validation failed (which is slightly similar to noir.validation but without all the evils of maintaining the state). The validators that we'll construct with Validateur are easily reusable and composable with other validators.

Note

The website of Validateur will provide a lot of information to you. The library is well documented and available for your perusal at http://clojurevalidations.info/.

Adding the Validateur dependency

The first thing that we need to do, as is the case with any third-party library we want to use, is import the library in our project.clj file as a dependency of the app. Adjust the project's dependencies vector so it is similar to this:

(defproject...
  :dependencies [[org.clojure/clojure "1.6.0]
                          ;...snipped for brevity
                          [com.november/validateur "2.3.1"]]
  ;...rest of our project...

Creating the user validation namespace

It can sometimes be difficult to determine where exactly one should write the validation code. Because one of the strengths of Validateur is that validators can be created, reused, and composed together to create more complex validators, it makes sense for us to create the validators in appropriate namespaces.

In our case, the sign up form has three fields that require validation: :username, :email, and :password. However, if we were to further extend this application to have something like a password reset form, then the e-mail and username validators would come in handy. As such, let's create a new namespace called hipstr.validators.user and put our validators in it.

Create a new directory in our /src/hipstr directory called validators:

# mkdir validators

Create a new hipstr.validators.user-validator namespace and ensure that it uses the validateur.validation namespace:

(ns hipstr.validators.user-validator
  (:require [validateur.validation :refer :all]))

The key function in validateur.validation is the validation-set. The validation-set function can be thought of as a factory responsible for creating and returning the actual validator to be applied to a map. The validation-set function accepts a list of rules, which will ultimately be used to validate a map. The validator returns a set of error messages for any key whose value is invalid, otherwise it returns an empty map.

Validating required fields

We can create a simple validator that checks for required fields using the presence-of function rule:

 (defn validate-signup [signup]
  "Validates the incoming map of values from our signup form,
   and returns a set of error messages for any invalid key.
   Expects signup to have :username, :email, and :password."
  (let [v (validation-set                           ;#1
    (presence-of #{:username :email :password}))]   ;#2
    (v signup)))

In the preceding code, we created our own little validate-signup function, which will take the parameters map from our POST/signup up route. At #1, we construct our validator by calling validation-set, and we pass it to our first rule, presence-of (as shown on #2), which itself accepts the set of keys that must be present, and truthy, in the map to be deemed valid (in our case, the sign-up map).

Ultimately, presence-of returns a function (as do all the rules we pass to validation-set), which will be used by validation-set during validation. These functions accept a map to be validated and perform their task against that map. We can take a peek into this at the REPL:

# lein repl
hipstr.repl=> (require '[validateur.validation :refer :all])

nil

hipstr.repl => (let [presence-of-fn (presence-of
               #=>   #{:username :email :password})]
               #=>   (presence-of-fn {:username "TheDude"}))
[false {:password #{"can't be blank"}, :email #{"can't be blank"}}]

In the preceding REPL code, we load the validateur.validation namespace and assign the result of presence-of to our own function presence-of-fn. We call presence-of-fn and pass it the map to be validated. The validation fails because :password and :email are not provided.

It's easy to see how validation-set simply iterates blindly over all of its validators, forwarding the map to each validator in question. Its simplicity is beautiful and elegant.

If we were to call validate-signup from the REPL with an invalid map, we'd see the following:

hipstr.repl=> (use '[hipstr.validators.user-validator])
nil
hipstr.repl=> (validate-signup {:username "TheDude"})
{:email #{"can't be blank"}, :password #{"can't be blank"}}
hipstr.repl=> (validate-signup {:username "TheDude" :email "[email protected]" :password "12345678"})
{}

The default error message for presence-of is "can't be blank", however, we can provide our own message by passing the :message keyword argument. Adjust the presence-of's arguments to look like the following:

(presence-of #{:username :email :password}
  :message "is a required field")

If we re-execute from the REPL, we'll now see a different error message:

hipstr.repl=> (use 'hipstr.validators.user-validator :reload)
nil
hipstr.repl=> (validate-signup {:username "TheDude"})
{:email #{"is a required field."}, :password #{"is a required field."}}

Validating the format

Of course, checking for required fields isn't the only thing we need to ensure. We also need to ensure whatever values are supplied are in the correct format. I'm making the executive decision that our application will only allow a limited set of characters for usernames, because I'm old and grumbly.

We can use the format-of rule to ensure that a given value is appropriately formatted. Because the validator returned by validation-set chains all the validation rules together, we can add the format-of rule after the presence-of rule. Adjust validate-signup to be similar to the following:

;…snipped for brevity…
(let [v (validation-set 
            (presence-of #{:username :email :password)
                                :message "is a required field.")
            (format-of :username 
                             :format #"^[a-zA-Z0-9_]*$"
               :message "Only letters, numbers, and underscores allowed."))])

The format-of rule accepts the key whose value's format is to be validated (:username), a regex pattern to which the value must conform, and an optional message if we don't want to use the default one provided (which in the case of format-of is "has incorrect format").

Note

Many of these functions take additional, optional parameters but for the sake of brevity, we aren't covering the entire suite. You can, as always, check out the docs at http://reference.clojurevalidations.info/validateur.validation.html#var-compose-sets.

At this point, if we try to validate our test data from the last example, we'll get the following response:

hipstr.repl=> (validate-signup {:username "The Dude" :email "[email protected]" :password "12345678"}) {:username #{"Only letters, numbers, and underscores allowed."}}

Excellent! So our format validator is working, but what happens if we don't pass :username at all:

hipstr.repl=> (validate-signup
  {:email "[email protected]" :password "12345678"})
=> {:username #{"is a required field" "can't be blank."}}

Huh, now that's interesting. Why did we get multiple required/blank validation messages for :username? Our first validator, presence-of, failed, so the message we provided was added to the error set for :username. In addition, format-of also does a check to ensure that the value is provided and adds its own validation message to the error set, can't be blank. Considering format-of already has its own check for existence, we may as well remove :username from presence-of, and change the default message for blank value validation in format-of:

;...snipped for brevity...
(let [v (validation-set
          (presence-of #{:email :password}
                      :message "is a required field.")
          (format-of :username
                    :format #"^[a-zA-Z0-9_]*$"
                    :message "Only letters, numbers, and 
                                     underscores allowed."
                    :blank-message "is a required field"))])

Next up, we'll verify the password.

Validating length of values

At this point, you might be starting to see a pattern, and it might come as no surprise that along with presence-of and format-of, there also exists length-of. The length-of rule can either check for an exact length or a range. Because we are software developers and know the importance of good passwords, we'll want a minimum length and a rather large maximum length. Let's add the length-of rule to the list of rules in the validation-set:

;…snipped for brevity…
(length-of :password 
          :within (range 8 101)
          :message-fn 
            (fn [type m attribute & args]
              (if (= type :blank)
                "is a required field"
                "Passwords must be between 8 
                 and 100 characters long.")))

In length-of we provided :within, which accepts a range. Alternatively, we could have specified an exact value using :is. The major difference between length-of and the other rules we've used so far is the :message-fn argument. Because length-of can fail for multiple reasons—it could fall outside our range, or not meet the exact value (if we had provided one), or be altogether blank—we can pass a function that determines the appropriate error message to return. From the docs:

:message-fn (default nil): function to retrieve message with signature (fn [type m attribute & args]). type will be :length:is or :length:within, args will be the applied number or range

Unfortunately, the docs are slightly misleading, as type can also have the value :blank. Hence, our :message-fn function will return a "is a required field" message if the type is :blank, otherwise it will return a message that covers the rest the validation cases appropriately.

Lastly, we'll take a look at one more rule function to help us validate the format of our e-mail address.

Validation predicates

E-mail addresses are hard to validate. Their regular expressions are difficult to maintain, ugly, and I'd rather let some other underlying library validate the e-mail address for me. Thankfully, one of the rules we can use is validate-with-predicate.

The validate-with-predicate rule takes a predicate and returns whatever the predicate returns. We can leverage this in conjunction with the noir.validation/is-email? function to validate the e-mail.

First, add the noir.validation requirement to the namespace:

(ns hipstr.validators.user
  (:require [validateur.validation :refer :all]
  [noir.validation :as v]))

Next, add the validate-with-predicate rule to validation-set, which will determine whether the e-mail is in the correct format:

 ;...snipped for brevity...
   (validate-with-predicate :email
#(v/is-email? (:email %))            ;#1
:message-fn                             ;#2
  (fn [validation-map]
    (if (v/has-value? (:email validation-map))
      "The email's format is incorrect"
      "is a required field")))

At #1, we define the predicate that will be used for this rule, which is just an anonymous function that passes the sign up map to the noir.validation/is-email? function. The interesting bit takes place at #2. Because validate-with-predicate has no idea how the passed map is being validated, or why validation failed, we provide a :message-fn funtion, which takes the entire map. We can then use the map to determine why the validation failed, and thus return an appropriate message. In our case, we know the e-mail's format is incorrect if a value was provided, otherwise we know it's blank. Our :message-fn will simply return an appropriate message for the two cases.

At this point, the validate-signup function should look like this:

(defn validate-signup [signup]
 "Validates the incoming map of values from our signup form, 
  and returns a set of error messages for any invalid key.
  Expects signup to have :username, :email, and :password."
 (let [v (validation-set
          (presence-of #{:email :password}
                       :message "is a required field.")
          (format-of :username
                     :format #"^[a-zA-Z0-9_]*$"
                     :message "Only letters, numbers, and 
                               underscores allowed."
                     :blank-message "is a required field")
          (length-of :password
                     :within (range 8 101)
                     :message-fn (fn [type m attribute & args]
                                   (if (= type :blank)
                                    "is a required field"
                                    "Password must be between 8 
                                                 and 100 characters long.")))
          (validate-with-predicate :email
            #(v/is-email? (:email %))
            :message-fn (fn [validation-map]
                            (if (v/has-value? 
                                (:email validation-map))
                            "the email's format is incorrect"
                            "is a required field"))))]
   (v signup)))

Note

Sometimes code formatting in a book is difficult to read. Here's a link to a gist to give your eyes a break: http://bit.ly/1rHZB0E

"But hold on! At the start of this section, you said that using Validateur allows us to compose reusable validators! This validate-signup can't be reused by anything but the signup form!" you may say. You are correct and we can fix this.

Making reusable validators

So far, we've created one big validator using the validateur.validation/validation-set function. It works, but it's monolithic and can't be used by anything other than the signup form, which is unfortunate.

To open this up, we can use validateur.validation/compose-sets, which instead of taking a collection of validator rules, takes a collection of validators returned by validateur.validation/validation-set. This allows us to extract each of the validator rules in the validate-signup function into its own validator using validation-set, thus allowing us to reuse a single validator wherever we want.

First, let's extract each rule in the validate-signup function into its own validator, such that our code looks like this:

(ns hipstr.validators.user
 (:require [validateur.validation :refer :all]
           [noir.validation :as v]))
(def email-validator
 (validation-set
  (validate-with-predicate :email 
    #(v/is-email? (:email %))
    :message-fn 
      (fn [validation-map]
        (if (v/has-value? (:email validation-map))
          "The email's format is incorrect"
          "is a required field")))))

(def username-validator
  (validation-set
    (format-of :username
             :format #"^[a-zA-Z0-9_]*$"
             :blank-message "is a required field"
             :message "Only letters, numbers, and 
                              underscores allowed.")))
(def password-validator
  (validation-set
    (length-of :password
             :within (range 8 101)
             :blank-message "is a required field."
             :message-fn (fn [type m attribute & args]
                           (if (= type :blank)
                             "is a required field"
                             "Passwords must be between 8 
                              and 100 characters long.")))))

(defn validate-signup [signup]
 "Validates the incoming signup map and returns a set of error
  messages for any invalid field."
  (let [v (validation-set)]
    (v signup)))

In the previous code snippet, we created a single validator for each of our rules. Each of these validators can now be used by anything we want. Of course, now our validate-signup doesn't really do anything. However, we can change this by swapping out validation-set with compose-sets, passing each of the previously defined validators and then immediately invoking it.

(defn validate-signup [signup]
  "Validates the incoming signup map and returns a set of error
  messages for any invalid field."
  ((compose-sets email-validator username-validator password-validator) signup))

Note

A gist of the entire refactored namespace is available at http://bit.ly/1CsSrrd.

Our individual validators are now reusable and we can compose complex validators from them. Now, we just have to report errors to the user for any of the failed validations, or redirect them to the success page.

Reporting errors to the user

We already set up our route handler at the start of this chapter, such that, if there were any errors, we would re-render the signup.html page with the user's submitted data along with the errors. Since we're using Selmer to render our templates, and because Selmer gracefully handles nil to be an empty string, we can safely treat user's data and reported errors as though they were present.

Adjust the signup.html page such that it looks like this:

{% extends "templates/base.html" %}
{% block content %}
<h1>Sign Up <span class="small">Nobody will ever know.</span></h1>
<div class="row">
  <div class="col-md-6">
    <form role="form" method="post">
      <div class="form-group">
        <label for="username">Username</label>
          <ul class="errors">
            {% for e in errors.username %}      <!-- #1 -->
              <li>{{e}}</li>
            {% endfor %}
          </ul>
          <input type="input" name="username" class="form-control" id="username" placeholder="AtticusButch"
        value="{{username}}">
      </div>
      <div class="form-group">
        <label for="email">Email address</label>
        <ul class="errors">
          {% for e in errors.email %}
          <li>{{e}}</li>
          {% endfor %}
        </ul>
      <input type="email" name="email" class="form-control" id="email" placeholder="[email protected]" value="{{email}}">
      </div>
      <div class="form-group">
        <label for="password">Password</label>
        <ul class="errors">
          {% for e in errors.password %}
          <li>{{e}}</li>
          {% endfor %}
        </ul>
        <input type="password" name="password" class="form-control" id="password" placeholder="security-through-obscurity">
      </div>
      <button type="submit" class="btn btn-default">Submit
      </button>
    </form>
  </div>
</div>
{% endblock %}

We are using Selmer's cycle tag (as shown at #1)—which is actually a for loop—to loop through any errors that exist for a given field and render out a fragment of HTML. We can do this because the validator that we created returns a set of error messages for any key whose validation failed; otherwise an empty map is returned, which Selmer can gracefully handle. We also were able to render out each respective input's value, if the user had already submitted one (so that they don't have to fill it in again, which would lead to them raging).

Finally, let's modify the hipstr.routes.home namespace and the signup-page-submit route handler we created, to make use of this validator.

First, add our user-validator to the list of requirements as follows:

(:require ;...snipped for brevity...
    [hipstr.validators.user-validator :as v])

Then uncomment the let form in our signup-page-submit, and change the signup alias to be v instead:

(defn signup-page-submit [user]
  (let [errors (v/validate-signup user)]
    (if (empty? errors)
      (response/redirect "/signup-success")
      (layout/render "signup.html" (assoc user :errors errors)))))

The preceding Selmer template will now render the following page when any validation errors are present:

Reporting errors to the user

Finally, let's add a Cascade Style Sheet (CSS) style so that our error messages are a deeply foreboding red. CSS styles are stored in the /resources/public/css folder. Out of the box, Luminus generates a screen.css file for us, prewired and ready to go. We'll add our style to it:

.errors {
  padding-left: 20px;
  color: red;
}

Save the file and refresh, and you'll have some urgent looking messages.

Reporting errors to the user

Finally, once we provide a valid username, e-mail, and password, we'll be redirected to the /signup-success route, and our simple little message will be displayed:

Reporting errors to the user
..................Content has been hidden....................

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