Relationships Between Models

This lab covers one small topic and one large topic. The small topic is how to create an action/view to see an individual record. The larger topic is how to create relationships between models. For example, you can create a ‘Project Group’ datatype, and be able to assign students to project groups.

Lets begin with Lab5

This week we will start out somewhat differently than usual. We will all begin with lab5, which, if you remember, is a continuation of lab4. But, in case you want to go back to lab5 again, we should “tag” the lab in git:

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

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.

Remember, lab 5 was based on lab4. So we are still using the same database that we’ve been using for the last couple of weeks. You are welcome to change the database if you like. Just remember to run rake db:create and rake db:migrate if you change the database in config/database.yml.

From lab 5, you should have a working Person model (connected to the people table in the database), and a people controller. You should also have a working Student model (connected to the students table in the database) and a students controller. If that isn’t working, and you’d prefer to start with my working copy of lab5, let me know, and I’ll give you a working copy of my lab5 to start from. Today’s lab is difficult, and it will be extra hard to do if you don’t have labs 4 and 5 working correctly.

View an Individual Record

We are going to start by modifying lab5 to add a page where we can view an individual record. You did something similar to this for Assignment 2.

First, we need to create a link from the list. So let’s edit the file app/views/people/index.html.erb and add a link:

<ul>
<% @people.each do |person| %>
  <li> <%= person.first_name %> <%= person.middle_initial %> <%= person.last_name %> 
  (<%= link_to "Full Information", person_url(person) %>)</li>
<% end %>
</ul>

Notice the new link_to line. This creates a link to a page that views the individual person. To get the URL that we want to link to, we use the function person_url(), and then pass in the specific person we want to link to. This will result in a url that looks like /person/5 (where 5 is the database ID of the person in the person variable). Because of the resource route we created, this will automatically route to the “show” action. We can then create and edit a show action in the people controller (app/controllers/people_controller.rb) to retrieve the person data corresponding to this ID, and store it in the @person variable:

def show
  id = params[:id]
  @person = Person.find id
end

Finally, since we have just created a new show action, we should create the associated view file. Use nano to edit the file app/views/people/show.html.erb. We have our name information in the @person variable, so let’s print out its contents:

<h1> One Person </h1>
<%= @person.first_name %> <%= @person.middle_initial %> <%= @person.last_name %> <br />
<hr />
<%= link_to "Back to the List", people_url %>

Now test your application and make sure this works. Run your server, go to the list of people (you may need to enter some additional names so that you have a few to choose from), and click on the “full information” links. Each link should take you to a page that just lists the one person’s information. Most of the time, the single show page has more information than the full list. Think about amazon.com: the list (search results) usually only has title and author, but the show page also has publisher, comments, reviews, and all kinds of other information.

Create a Project Group model

Next, we are going to add project groups to our application. Each person can will be able to be assigned to a single project group.

To do this, we will add a new model, controller, and set of views. We can begin by adding a new resource called ProjectGroup. If you remember lab 4, the way to do this is to use the command

$ rails generate resource ProjectGroup

Once you run this, you can edit the created migration file to specify what columns you want in a ProjectGroup. For our purposes, we only have one column right now: the name of the group.

class CreateProjectGroups < ActiveRecord::Migration
  def change
    create_table :project_groups do |t|
      t.string :group_name

      t.timestamps
    end
  end
end

After you’ve edited this file, run rake db:migrate to actually create the table in the database. The generator also created a model file, app/models/project_group.rb.

The generator also created a new controller for us: app/controllers/project_groups_controller.rb. Add some actions into it for creating new project groups, listing all project groups, and viewing a single group. These actions should look familar; we’ve written many just like them in previous labs:

class ProjectGroupsController < ApplicationController
  def new
    @group = ProjectGroup.new
  end

  def create
    @params = params.require(:project_group).permit(:group_name)
    @group = ProjectGroup.new(@params)
    if @group.save
      redirect_to project_groups_url
    else
      render action: 'new'
    end
  end

  def index
    @groups = ProjectGroup.all
  end

  def show
    @group = ProjectGroup.find params[:id]
  end
end

Next, we can create our standard view pages, starting withnew.html.erb page.

<h1> New Project Group </h1>
<%= form_for(@group) do |f| %>
  <% if @group.errors.any? %>
  <div id="errorExplanation">
    <h2><%= pluralize(@group.errors.count, "error") %> prohibited this user from being saved:</h2>
    <ul>
    <% @group.errors.full_messages.each do |msg| %>
      <li><%= msg %></li>
    <% end %>
    </ul>
  </div>
  <% end %>
  <%= f.label :group_name, "Group Name" %> <%= f.text_field :group_name %>
  <%= f.submit %>
<% end %>

<%= link_to "Back to list", project_groups_url %>

And our standard index.html.erb page:

<h1> List of all Project Groups </h1>
<ul>
<% @groups.each do |group| %>
  <li> <%= link_to group.group_name, project_group_url(group) %> </li>
<% end %>
</li>

<%= link_to "New Project Group", new_project_group_url %>

And a simple show.html.erbpage:

<h1> <%= @group.group_name %> </h1>

<%= link_to "Back to Group List", project_groups_url %>

Once you’ve created these pages, go and test them. Run your server, go to these web pages, and make sure they work as you expect them do. DO NOT move forward until you’ve tested these pages and ensured that they work correctly.

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 and also to /project_groups – so, the URL will look something like this: http://webdev.cas.msu.edu:1234/people or `http://webdev.cas.msu.edu:xxx/project_groups.

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.

The pair should already have a copy of the git repository in their directoy. So rather than cloning, they should be able to simply git pull the changes.

$ cd lab4
$ git pull

Run the rails server and make sure everything works correctly for the new driver before proceeding.

Create a relationship between Persons and Project Groups

The next thing we are going to do is create a many-to-one relationship between Persons and ProjectGroups. Each ProjectGroup can have many Persons associated with it, but each Person only has one ProjectGroup it is a member of. To create this relationship, we need to do two things. First, we need to modify the Person table to have a new column in the database. As you remember from last week, we do this by using a migration:

$ rails generate migration AddProjectGroupToPeople

And we use t.integer to add a column to an existing table:

class AddProjectGroupToPeople < ActiveRecord::Migration
  def change
    add_column :people, :project_group_id, :integer
  end
end

Don’t forget to run rake db:migrate once you’ve finished filling in the details of the migrations.

Now, we should edit the model files to establish this relationship. First, app/models/person.rb

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

  belongs_to :project_group
end

Note the new line that starts with belongs_to. This command makes the “Person” model the many part of the many-to-one relationship. A Person belongs to a ProjectGroup.

Next, we can modify the Project Group model file (app/models/project_group.rb) to say that is has many different persons:

class ProjectGroup < ActiveRecord::Base
  has_many :people
end

Notice, we use the singular form for the belongs_to line, but the plural form for the has_many line. This is because when thinking about a person, he or she belongs to a single project group. But when thinking about project groups, each project group will have many people (plural). Rails is smart enough to know that when you say has_many :people, it looks for a model named by the singular version of that – Person.

An interface to specify project groups when creating people

Whenever we create a new person through the web application, we want to be able to specify a project group that this person is part of. To do this, we will use a dropdown box in the form where we add people. We can use the following functions to help us:

f.collection_select(:method, :collection, :id, :display_name)
Displays a dropdown selection box. It will fill in the :method parameter of the form object with whatever the user chooses. The :collection parameter is the list of objects to display. On each of the objects, it will display the :display_name of the object, but will return the :id part of the object.

To use this function, we add the following line to our new person form in app/views/people/new.html.erb:

Project Group: <%= f.collection_select :project_group_id, ProjectGroup.all, :id, :group_name %>

Because this is part of the person object, we need to tell our controller that it is OK for this form to include project_group_id by adding this to our permitted fields line in the people controller:

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

Display the Project Group of a Person

Before we can test this, though, we need to add some code to our person view that displays the currently assigned project group. To do that, open up the person view in app/views/people/show.html.erb and add the following three lines:

<% unless @person.project_group.nil? %>
Project Group: <%= @person.project_group.group_name %>
<% end %>

This displays the name of the project group for the person, but ONLY if that group has been assigned. If the project group is nil (ruby terminology for blank), then it doesn’t display that line.

Now we can test this. Run your server, and make a couple of project groups. Then go to your new person form, and see if you can select a project group. Select one, and then view that person to see if the project group is assigned correctly.

Display the List of People in a Project Group

Now that we can add people to Project Groups, the only thing left is to display the list of people in a project group when viewing that group. Edit the file app/views/project_groups/show.html.erb:

<h1> <%= @group.group_name %> </h1>

<ul>
<% @group.people.each do |person| %>
  <li> <%= person.first_name %> <%= person.middle_initial %> <%= person.last_name %> </li>
<% end %>
</ul>

<%= link_to "Back to Group List", project_groups_url %>

OK, now everything should work! Run your server, and spend some time experimenting. Add some project groups to the system. Then add some users to those project groups. Then go and view the various project groups, and see how it only displays the people who are members of the groups. Read through the associated code and see if you can figure out why this work. This is one of the most important pieces of the class, and its also one of the most complicated. Spend some time with a working version before moving on to try it on your own below.

Check your changes into git

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

$ git add .
$ git commit
$ git push

Exercise: Put students in Majors

Whew! That was a long lab. The first priority of today is to get everything above working correctly, so you have a correct, working example. Once you have that working, and you’ve successfully tested everything, then you can optionally do the following exercise:

The exercise for today is to add a Major model. Each student can only have one major, but any major can have many different students in it. You’ll want to add a new major controller and its associate views for creating, listing, and viewing majors. You should be able to