Input Validation

In this lab, we will modify our application to validate all input from the user to make sure it is proper input.

Lets begin with Lab4

This week we will start out somewhat differently than usual. We will all begin with lab4 and continue to add some functionality. But, in case you want to go back to lab4 again, we should “tag” the lab in git:

$ cd lab4
$ git pull
$ git tag -a lab4

There. Now we’ve labeled the current version of the code as lab4. Anytime you want, you can go back and view that version of the code with the command git checkout lab4 from that directory. When you’re done looking at the code, you can git checkout master to get back to the newest version.

Next, we need to verify that the connection with the database still works. Run the command

$ rake db:version

If it returns a long version number (which is actually the date of the last lab), then it worked correctly. If it returns an error, then you have a configuration problem in config/database.yml that you need to fix.

Modify the Person Model to Include a Middle Initial

If you remember last week, we created a Person model that had two members: first_name and last_name. This week, we want to modify this model to include a new parameter: a middle initial. We will store this initial as a string in the database.

To modify a model that has already been saved to the database, we need to create a migration. We need to give this migration a name when creating it:

$ rails generate migration AddMiddleInitialToPerson

As we discussed in class, a migration needs two pieces of information: what to do to move the database forward to the next version, and how to undo that move. To move the database forward, we want to add a new column to the people table. (Remember, for a model called Person, the table name is always the lowercase and plural version of that word: people).

To add the column we can use the add_column function:

class AddMiddleInitialToPerson < ActiveRecord::Migration
  def change
    add_column :people, :middle_initial, :string
  end
end

Once you have created this migration, run the command rake db:migrate to bring the database up to the current version. Remember, once you run the rake db:migrate command, you can’t go back and modify the migration anymore; you need to use a new migration if you want to change the database.

Next, you need to tell the model that it is OK to assign values to this new field. Edit the person controller file (app/controllers/people_controller.rb) and add middle initial to the permit line:

@params = params.require(:person).permit(:first_name, :last_name, :middle_initial)

Exercise: Add the Middle Initial to the Controller and View

Edit the files app/views/people/new.html.erb and app/views/people/index.html.erb to ask for and display the middle initial of people. Do you need any other modifications to the controller at all?

Test your changes

Once you’ve updated the database, and fixed the new and list actions, you should test your application to make sure it still works as it should. Run your server (rails server --port=xxxx), go to your new webpage and see if you can add new people that have middle initials.

Add validations to the Person model

Next, we want to add error handling to our application. We want to detect and handle cases when people enter invalid data, such as leaving their first name blank. The first thing we need to do is to edit the model file to add validators. Open up the Person model file app/models/person.rb and add these two validators. These validators both verify that what the user entered isn’t blank:

class Person < ActiveRecord::Base
  validates :first_name, presence: true
  validates :last_name, presence: true
end

Next, we will modify the new view page to display any validation errors. To do that, we will use the error_messages_for function. This function will display any and all error messages from the validators.

 <h1>New Person</h1>
  <%= form_for(@person) do |f| %>
  <% if @person.errors.any? %>
  <div id="errorExplanation">
    <h2><%= pluralize(@person.errors.count, "error") %> prohibited this user from being saved:</h2>
    <ul>
    <% @person.errors.full_messages.each do |msg| %>
      <li><%= msg %></li>
    <% end %>
    </ul>
  </div>
  <% end %>
    <%= f.label :first_name, 'First Name:'%> <%= f.text_field :first_name %> <br />
    <%= f.label :middle_initial, "Middle Initial:" %> <%= f.text_field :middle_initial, :size => 2 %> <br />
    <%= f.label :last_name, 'Last Name:'%> <%= f.text_field :last_name %> <br />
    <%= f.submit %>
  <% end %>

Note that we are now using the f.label function to display the label for the text fields. This will become important later.

Finally, we need to modify our create action in the controller to detect when these validations fail. To do this, we will use the fact that the model.save function returns false when a validation fails. When the validation fails, we want to re-draw the new view page with appropriate error messages. To re-draw a different action’s view page, we can use the render functions:

  def create
    @params = params.require(:person).permit(:first_name, :middle_initial, :last_name)
    @person = Person.new(@params)
    if @person.save
      redirect_to people_url
    else
      render action: "new"
    end
  end

Now, let’s test our validators and see if they work. Run your rails server --port=xxxx, go to your people/new webpage, type in a first name only, and click submit. Does your application return an error? The following should all happen when you click submit:

Now, try adding one more validator to the Person model: Ensure that the middle initial is at most one letter, but blank is OK:

class Person < ActiveRecord::Base
  validates :first_name, presence: true
  validates :middle_initial, length: {maximum: 1}
  validates :last_name, presence: true
end

Don’t forget to test this and make sure it works: try entering more than one letter in the middle initial field and see if you get an error. Also, remember to test correct functionality too; you should be able to leave it blank and still be able to submit.

Swap Drivers

Now it is time to swap who is driving and who is navigating. Don’t forget to check in your changes to git so your partner has access to them:

$ git add .
$ git commit
$ git push

Now swap who is driving the computer, and make sure the new partner has the changes:

$ git pull
$ rails server

Lab Exercises

There are two exercises for this lab, corresponding to the two topics for today: migrations and validators. Both will be operating on the Student model that you created last week, and the corresponding students controller.

Add Expected Graduation Date to Student Model

Your student model currently contains columns for name, gender, age, year in school, and major. Now we want to add a new column to this model: expected graduation year. Create a new migration to add this column to the database. Modify the students controller and its associated views so that users can enter this information about students.

Add Validators for all of the Student fields

We want to make sure that when people add information about students, this information is valid and makes sense. Properly display error messages on the form input. The following should be validated:

To do this, you can use the following validators:

validates :column, presence: true
Validates that a column isn’t blank
validates :column, numericality: true
Validates that the user entered a number
validates :column, length: {maximum: 10, minimum: 5}
Validates that the text that was entered was at least 5 but no more than 10 letters long. You don’t need to specify both :maximum and :minimum; only one is required.
validates :column, inclusion: {in: 100..200}
Validates that the number entered is between 100 and 200
validates :column, exclusion: {in: %w( TISM Journalism) }
Validates that the user did not enter the string TISM nor the string Journalism. Note that this is case sensitive; capitalization matters.

Note that you can have more than one validator operating on a column. (Also, if you aren’t careful, it is possible to make it impossible to submit a valid input. For example, if you validate that the length of the input is at least 4 characters, but it is a number between 10 and 20, no possible input will be valid.)

More documentation about available rails validators is available on the web: http://guides.rubyonrails.org/active_record_validations.html

Don’t forget to commit your changes into git.

Extras

Highlighting the Bad Inputs

Try adding the following stylesheet to the top of your app/view/student/new.html.erb file:

<style type="text/css">
#errorExplanation {
  border-style: solid;
  border-color: red;
}
.field_with_errors {
  display: inline;
  background-color: red;
}
</style>

This will cause the webpage to highlight in red all of the fields that have validation errors.

Changing the Error Message

You can change the error message by using the :message parameter on the validates line. Like so:

  validates :age, inclusion: {in: 10..100, message: "No one is that age!  Try again"}