Everything you’ve done so far has been in the context of an application with one and only one table. That’s actually enough power to run a lot of different projects, from contact managers to time-series data collection. However, you’ll quickly find that most of the projects for which it’s worth creating a web application require more than just one table. Fortunately, Rails makes that easy, giving you the tools you need to create multiple tables and manage even complex relationships between them.
If you don’t know much about databases, now is a good time to visit Appendix B. Up to this point, it’s been possible to largely forget that there was a relational database underneath the application, except for some mechanics. From this point on, you’ll need to understand how tables work in order to understand how Rails models work. (You still don’t need to understand SQL, however.)
Working with multiple tables is, on the surface, pretty simple. Every Rails model maps to a table, so working with multiple tables just means working with multiple models. The hard part is managing the relationships between the tables—which in Rails demands managing the relationships between models.
Most of the steps for working with multiple models are the same as for working with single models, just done once for each table. Once the models are created, though, the real work begins. Some of it can be done easily and declaratively, while other parts require thinking ahead and writing your own code. This chapter marks the point where Rails itself can’t directly support the operations suggested by your data models, and so there’s a lot of coding to do. While the scaffolding still provides a helpful supporting framework, there’s a lot of editing to do on models, migrations, routes, controllers, and views.
Once again, it’s important to emphasize how much easier it is to create a Rails application from scratch rather than trying to build it on top of an existing database. If you’re trying to retrofit an old database with a shiny new Rails interface, odds are good that you need a much more advanced book than this one. You’ll need to learn what goes on behind the scenes, not just how they work when all is well.
The guestbook example of the previous few chapters isn’t the best foundation on which to demonstrate a multi-table application, so it’s time to change course. If you’d like to get an overview of the structures this chapter will create, these structures will be the same as those introduced in Appendix B, using students, awards, and courses. (The first version of them can be found in ch09/students001.)
Start by creating a new application:
rails students
If necessary (if you’re not in Heroku or a similar environment),
cd students
, and then create a
student
model and the usual related
scaffolding:
script/generate scaffold student given_name:string middle_name:string family_name:string date_of_birth:date grade_point_average:decimal start_date:date
Then create a second model, award
, and its scaffolding:
script/generate scaffold award name:string year:integer student_id:integer
The students application now contains two models, one for students
and one for awards. Students will receive awards, and awards will be
connected to students, but Rails doesn’t know that yet. The script/generate
command gives a hint of this
because it includes a student_id
field, an integer that will connect to the (unspecified but automatic) id
field of the students model.
To tell Rails about the connections between the two models, you
need to modify the models. In app/models/student.rb, add the following
between the class
line and the
end
:
# a student can have many awards has_many :awards
And in app/models/award.rb, add:
# every award is linked to a student, through student_id belongs_to :student
These two declarations establish a relationship between the two models. Student records have awards—students don’t have to have awards, but they can have many of them. Awards, however, for purposes of this example, are always linked to students.
Technically, has_many
and
belongs_to
are method names. They
just happen to look like declarations, and it’s a lot easier to
think of them that way.
Now Rails knows about the connections between the models. What is it going to do to support that relationship, and what’s still up to you?
Rails doesn’t add automatic checking or validation to ensure
that the relationships between objects work. It doesn’t require, for
example, that every award have a valid student_id
. It doesn’t change the
scaffolding that was already built. Establishing the connection in the
model is just the first step toward building the connection into your
application.
Rails does provide some help in doing that, though. With these
declarations, Rails adds methods to your classes, making it much
easier for a student
object to work
with its award
objects and for an
award
object to work with its
student
objects. You can find a
complete listing of the methods added in the API documentation for
has_many
and belongs_to
. For now, it probably makes sense
to show how the association can help.
There is only one reference to a possible connection in the
original forms created by the scaffolding: a student field, meant to
hold the student_id
, on the forms
for entering and editing awards, shown in Figure 9-1. (You’ll need to
run rake db:migrate
and ruby script/server
to make the form
appear.)
As it turns out, while you can enter numbers corresponding to
students in the student field (if you know them, figuring them out
from the URLs for student records), there isn’t any constraint on the
numbers that go there. Awards can go to nonexistent students. It’s
easy to improve the situation, though, by adding a select
field to the app/views/awards/new.html.erb view:
<p> <%= f.label :student_id %><br /><%= f.select :student_id, Student.find(:all , :order => "family_name,
given_name").collect {|s|
[(s.given_name + " " + s.family_name), s.id]} %>
</p>
The highlighted piece there might seem indigestible, but
it’s a fairly common way to create select lists based on related
collections. The select
method
needs a field to bind to—:student_id
—as its first parameter. The
second parameter is a collection for the list to display. Student
gets an object referring to the
students
model. The find
method, which you’ve encountered before
in show.html.erb templates,
retrieves the list of all (:all
)
student records, sorted by family name and then given names (thanks to
the :order
parameter).
Although calling Student.find(:all)
works, it’s better
practice for views to reference only the instance variables—i.e.,
the variables with names prefixed by @
—rather than connecting directly to a
model.
The find
method doesn’t quite
finish the work, though. You could stop here, if you were content to
list object reference information in the select field. To show
something a little more meaningful, however—both to the human user and
to the program interpreting what comes back from the form—you need to
specify both what gets displayed in the select field and the value
that will get sent back.
That’s where the collect
method is useful. It takes a block as an argument
({}
). The |s|
is a very brief way of saying that
Ruby should loop through the collection of students and put each row
into a variable named s
. On each
iteration of the loop, the block will return an array, contained in
[
and ]
. Each of those arrays, which will become
lines in the select list, will have two values. The first is the name
of the student, generated by concatenating its given_name
to a space and its family_name
. That value will be displayed to
the user. The second is the id
value for the student, and that value will be what comes back from the
form to the server.
All of that work creates the simple form shown in Figure 9-2, with its drop-down box for students.
When the user submits this form, Rails gets back a “1”
identifying the student’s id
. (At
least it will if the students table looks like Figure B-1 in Appendix B.) That will go
in the student_id
field in the
table. A “1” will be puzzling for humans, though. To fix that, in
app/views/awards/, in show.html.erb, and index.html.erb, you might want to
replace:
<%=h @award.student_id %>
with:
<%=h @award.student.given_name %> <%=h @award.student.family_name %>
Note that the @award
variable
(just award
in index.html.erb) suddenly has a new method.
Of course it understood student_id
—that’s a field defined by the
original script/generate
command.
But the student
method, and its
methods given_name
and family_name
, are new.
Those features are the result of Rails recognizing the belong_to
declaration and providing a more convenient notation for
getting to the specific student that this particular award belongs_to
.
While using student
is great,
there’s one problem with the code just shown—it keeps repeating itself
to combine given_name
and family_name
. There’s a way to avoid that and
to simplify most of this code. In the model for student (in app/models.student.rb), add a method called
name
that returns a simpler
form:
def name given_name + " " + family_name end
Like the methods representing database fields, the name
method will be available from awards,
as in:
<%=h @award.student.name
%>
or:
<%= f.select :student_id, Student.find(:all).collect {|s|
[s.name
, s.id]} %>
You’ll now get the cleaner-looking result shown in Figure 9-3 for a little less work.
Awards are now connected to students, but there still isn’t any
enforcement of that connection, just a form field that makes it
difficult to enter anything else. Even with the form, though, there
are corner cases—someone could, for example, delete a student after
the form had been sent to a user. Or, more likely, a REST request
could send XML with a bad student_id
—fixing up the view hasn’t changed
anything in the model.
Rails itself doesn’t provide a simple mechanism for validating that the
student_id
matches a student. You
could, if you’re handy with the underlying database, add such
constraints through migrations. If you’d rather do something that
feels like it’s Rails, however, and operates within the model instead
of the database, you can install the validates_existence_of
plug-in. From your application’s top directory, issue
the command:
ruby script/plugin install http://svn.hasmanythrough.com/public/plugins/validates_existence/
and add this line underneath the belongs_to
declaration of app/models/awards.rb:
validates_existence_of :student
Now, if you try to save an award record with a student that doesn’t exist, you’ll get a message like that shown in Figure 9-4. (Note that because the student was deleted, his or her name isn’t available in the select list, and Giles Boschwick comes up again.)
If you don’t check for the existence of the student, then users
will see a strange and incomprehensible message about a nil
object when the view tries to process award.student.name
, so this is most likely
an improvement. For more information on validates_existence_of
, including
discussion on whether it’s a good idea in the first place, see http://blog.hasmanythrough.com/2007/7/14/validate-your-existence.
As that blog entry notes, you could decide to use Rails’
built-in validates_associated
method for this purpose, but it goes beyond checking
whether there is an associated record all the
way into checking whether there is a valid
associated record. Depending on your needs, that could be more
appropriate, but validates_existence_of
is lighter-weight.
Later in this chapter, you’ll see another approach to connecting awards to students that helps avoid these problems: nested resources.