Lab 4: Using Models

Before we begin, it is time to find a new person to pair with. Please find a new pair — someone you haven’t worked with yet.

In this lab, we will use ‘models’ in rails to store structured data persistently (aka long-term).

Create a new rails application and configure it to access the database

As usual, we begin by creating a new rails application. However, this time, we will use an additional parameter to tell it we will be using a MySQL database:

$ new_repo lab4
$ rails new lab4 -d mysql
$ cd lab4

This week we have one additional step: we need to configure this application to access the MySQL database. To do so, open up the file config/database.yml. You need to make three edits to each of the three environments.

  1. You need to add your username as a prefix on the name of the database. So I would replace lab4_development with rwash_lab4_development.
  2. You need to fill in your username in the username field. I would do username: rwash
  3. You need to fill in your password

You need to do this one for each of the three environments. In the end, your database file should look something like this (with the correct username and passwords filled in):

development:
  adapter: mysql2
  encoding: utf8
  reconnect: false
  database: rwash_lab4_development
  pool: 5
  username: rwash
  password: ******
  socket: /var/run/mysqld/mysqld.sock

# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
  adapter: mysql2
  encoding: utf8
  reconnect: false
  database: rwash_lab4_test
  pool: 5
  username: rwash
  password: ******
  socket: /var/run/mysqld/mysqld.sock

production:
  adapter: mysql2
  encoding: utf8
  reconnect: false
  database: rwash_lab4_production
  pool: 5
  username: rwash
  password: ******
  socket: /var/run/mysqld/mysqld.sock

Once you’ve edited and saved this file, we need to do 2 things. First, we need to create the database. We do this with the command

$ rake db:create

As long as no error messages were printed, then the command was successful. To test the connection to the database to make sure it works, we can use the command

$ rake db:version

which should return a current version of 0.

Don’t forget to check in your changes to the git repository. You should always do this periodically so you don’t lose anything:

$ git add .
$ git commit
$ git push origin master

We might as well give our pair access right now also:

$ repo_add_user lab4 <pair_username>

Create a New Resource

We will ask rails to generate a new resource. A resource consists of a model to store information in the database, a controller to access and process that information, and a set of views to display that information. To create a resource, we use the command rails generate resource. Let us begin by creating a Person resource:

$ rails generate resource Person

This created a couple things. First, it created a migration – a description of what changes should be made to the database. It also created a model file and a controller file. We will begin by looking at the migration.

We need to add the structure of our Person model to the migration. First, we want to add their name. A person has two parts: a first name and a last name. So we need to add two fields to the person model. We do this in the file db/migrate/2012......_create_people.rb. All those numbers in the filename actually are the current date and time – 2012/02/… We should modify the file to add two strings:

class CreatePeople < ActiveRecord::Migration
  def change
    create_table :people do |t|
      t.string :first_name
      t.string :last_name

      t.timestamps
    end
  end
end

Now that we’ve specified the database structure, we need to actually create the table in the database to store people in. To do that, we run the command

$ rake db:migrate

Periodically check in your changes:

$ git status
$ git add .
$ git commit
$ git push

Look at Model

In addition to the migration, the model we just created is stored in the file app/models/person.rb. Look in that file. It should be almost completely empty, containing just a class declaration:

class Person < ActiveRecord::Base
end

This file does a lot of work already. Because it inherits from ActiveRecord::Base, it will automatically look at the database we’ve created and structure itself appropriately.

Create a New Controller

When we generated our resource above, the generator also created a new controller for us. That was nice of it. But, right now, the controller doesn’t do anything. We need to add our custom business logic code to this controller so that it does something useful.

To do this, we need to add three actions to this controller. First, we will add a new action for displaying the new person form. This action is similar to the input action we created last week. Second, we add a create action that the form gets submitted to. This action is similar to the output action we created last week, but instead of printing out the result, we want to save the data in the form to the database. Finally, we will add an index action for listing all the current people in the database. (index is the computer term for a listing of something.)

To start, we first need to add empty actions to the controller app/controllers/people_controller.rb:

class PeopleController < ApplicationController
  def new
    @person = Person.new
  end

  def create
  end

  def index
  end
end

Note that I added a little bit of code in the new action. This code creates a blank new Person object. This object is not actually saved to the database until you run the @person.save method.

Look at Routes

Whenever you create new actions in a controller, you need to make sure that there is a route that allows the user to access that action. If you go look at your config/routes.rb file, you will find that the generator added one line to that file already: resources :people

This line looks very different from our match lines we’ve seen before. That’ OK; this line actually does a LOT for us. It actually creates seven standard routes. To see the routes, go to the command line and run the command

$ rake routes

The output should look like this:

     Prefix Verb   URI Pattern                Controller#Action
     people GET    /people(.:format)          people#index
            POST   /people(.:format)          people#create
 new_person GET    /people/new(.:format)      people#new
edit_person GET    /people/:id/edit(.:format) people#edit
     person GET    /people/:id(.:format)      people#show
            PATCH  /people/:id(.:format)      people#update
            PUT    /people/:id(.:format)      people#update
            DELETE /people/:id(.:format)      people#destroy

As you can see, this list seven different actions (on the right hand side) that already have routes created for them. We will only use 3 of them today: index, new, and create. Note the URLs that were created: /people goes to the index action, and /people/new goes to the new action. (Also, if you POST a form to the /people url, then it will go to the create action.) It looks like rails already created our routes for us, so we can move on to creating the views.

New Person View

Whenever we create new actions in a controller, we should also need to create the associated view files. But in this case, we only need views for new and list; create doesn’t display anything.

We will begin by creating the new view. Remembering the lessons from the last few weeks, this view will be located in app/views/people/new.html.erb.

To create a new person, we should create a form to do this, much like last week. But we can take advantage of some built-in rails magic to make this easy:

form_for(@person) do |f|
Creates a new form specifically for the object specified

Lets create the following view; it should look similar to, but not exactly the same, as the forms you created last week:

 <h1>New Person</h1>
  <%= form_for(@person) do |f| %>
    First Name: <%= f.text_field :first_name %><br />
    Last Name: <%= f.text_field :last_name %><br />
    <%= f.submit %>
  <% end %>

The form_for line creates a new form, but also specifies that this form is specifically for the Person model, and uses the @person object for values. Note that when this form is submitted, it will automatically go to the create action.

Check your work. Run a server and go to http://webdev.cas.msu.edu:xxxx/people/new

Create the New Person Object

Next, we need to fill in the create action that actually creates a new Person object and saves it to the database. There are three steps to creating a new person object.

First, we need to tell rails which parameters to expect from the form. The data from the form comes in an automatic variable called params. We can call the function .permit() on it to tell rails to allow specific values from the form:

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

Second, we need to create a new Person object with the information from the form:

@person = Person.new(@params)

This will create a new Person object and fill in the values that were in the form that was submitted. However, it isn’t saved to the database yet. So the third step is to save to the database:

@person.save

Finally, we want to then redirect the user to the full list of people. To do that, we will use the redirect_to function. Because we are redirecting the user, we don’t need a view page for this action.

redirect_to(url)
Automatically redirects the user to a different controller and action.

So, the final create action (which belongs in the controller) should look like this:

  def create
    @params = params.require(:person).permit(:first_name, :last_name)
    @person = Person.new(@params)
    @person.save
    redirect_to(people_url)
  end

Displaying a List of People

Finally, we want to have a view that displays a full list of all the people in the database. To do this, we will create an index action. The first thing we need to do is retrieve the list of all the Persons in the database. To do this, we use the all function on the model. Here is the source to the index action in the controller:

  def index
    @people = Person.all
  end

This function will look through the database and retrieve a list of every name that anyone has entered, and put that list in an array called @people.

Next, we need to create the associated view page. On this view page, we will use the .each function to loop through all the names and print them out:

<h1> List of People </h1>
<ul>
<% @people.each do |person| %>
  <li> <%= person.last_name %>, <%= person.first_name %> </li>
<% end %>

</ul>

<%= link_to "Add a new person to the database", new_person_url %>

This view page uses @people.each to loop through all the people in the array. Each person will be filled into the person variable, and then the block (the part between the do and end) will be run. That block prints out the person’s name, last name first, in an HTML list element.

Finally, this view page contains a link (using the link_to function we discussed earlier) to add a new person to the database.

Extra

If you are so interested, you can also sort the results results from the database. In the controller, when you run the all function, you can specify an additional parameter that tells rails how to sort the data. So your list action will look like this:

  def index
    @people = Person.order(:last_name).all
  end

Check your work, and save it in git

Now, we need to check everything and make sure it works. Really, you should be doing this all along. But you should definitely do it now. Run the rails server (rails server --port=xxxx) and enter the url in your web browser. Remember that you want to go to /people – so, the URL will look something like this: http://webdev.cas.msu.edu:1234/people.

Check and make sure everything works correctly. But first, what does it mean to work correctly? What should it do? Talk with your partner to figure out what the system should be able to do. Then try to see if it works; use the system and see if you can actually do what you think you should be able to do.

Once you are satisfied that everything works as it should, you should check the changes into git:

$ git add .
$ git commit
$ git push

Swap Pairs

For the exercise, swap pairs. The person who was driving should become the navigator, and the navigator should drive when doing the exercise. You are still doing all of the work together, as a pair; just a different person is driving.

Don’t forget to have the new driver download a copy of the git repository:

$ git clone git@webdev.cas.msu.edu:<username>/lab4

Make sure that everything works for the new driver before going forward.

Exercise: Create a Student Model, Controller, and View

For this exercise, you should create a new resource (model+controller+views): Student. This model should store the same information as the previous lab’s contained about students:

In the controller, you should have the same three actions: new, create, and index. new should display a form for adding a new student to the database. The form should submit to create, which should add the student info to the database. And index should display a full list of all the students in the database. The index page should also contain a link to the new page.

Mistakes in the Database

One of the big challenges here is that once you have run rake db:migrate, the database has been updated, permanently. If you go back and edit your old migration files, they won’t be able to change the database anymore. If you made a mistake in your migration and ran the rake db:migrate command, then that change is permanent.

Easy Solution

The easy way to fix this is to use the command rake db:rollback. This rolls the database back to a previous version. You can run it multiple times to undo multiple migrations. However, not that this will cause you to lose any data in the database. Once you’ve rolled the database back, you can edit your migration and re-run rake db:migrate to migrate again.

Better Solution

The better way to fix it doesn’t cause you to lose data. Instead, you have to use a new migration. To do this, run this command:

$ rails generate migration fix_people

(of course, you can name the migration whatever you want; just make it informative.)

Once you create the blank migration, then you can put your database changing code in there. To change an existing table, you can use the change_table function and block:

    change_table :products do |t|
      t.rename :frist_name, :first_name
    end

Don’t forget to run rake db:migrate to actually update the database. Remember, migrations are like instructions for creating the database; you actually need to tell the computer to execute those instructions to get the database how you want it.