Because the topic of this book is database interaction, the rest of this chapter focuses on the Model layer of the MVC methodology. We will show you how to design abstract methods that can be used and reused in a variety of applications. We’ll also show how to weave persistence into your Model with minimal duplication of code, and—for good measure—speed up access by implementation a caching layer.
The Model contains abstractions of all concrete things used within the application. Therefore, a solid Model is an important foundation for the rest of the application.
Luckily for us, designing a Model for a database-driven application is straightforward. That is because the work of discovering the relevant abstractions in a system is done when the database scheme is created, as we described in Chapter 7.
Usually, each table in the database corresponds to one class in the Model. The fields of the tables correspond to the attributes of the class. Relationships between tables can usually be expressed in the following manner:
If two tables have a one-to-one relationship, their relationship can involve either containment or aggregation , concepts that are familiar to object-oriented programmers. If one contains the other, the contained object should be defined as an attribute of the container class. For instance, if every ISBN corresponds to one and only one title, one of them can be an attribute of the other. If the relationship is one of aggregation, the more specific class should be a subclass of the less-specific class.
If two tables have a one-to-many relationship, the “One” class should contain an array of “Many” objects. Thus, a corporation object could contain an array of objects representing products, because most corporations sell multiple products.
If two tables have a many-to-many
relationship, each class can contain an array of objects from the
other class. That is, a Person
table can exist in
a many-to-many relationship with an Employer
table
(with a many-to-many join table in the middle), because a
Person
usually has had more than one
Employer
and each Employer
has
more than one Person
. In the Model, the
Person
class contains an array of
Employer
objects, and the
Employer
class contains an array of
Person
objects. This type of construct can be very
challenging to implement, because of the complexities of recursion.
When you create a Person
, you create all his
Employer
s, each of which then contains the
Person
, which contains the
Employer
s, etc. Because of this, many designers
avoid many-to-many relationships when possible. If they are
necessary, however, it is possible to pull off with careful
implementation.
Like all classes, a Model class is comprised of attributes and methods. An attribute is simply a variable that describes something about the class. As mentioned above, the attributes of a Model class represent the fields of the underlying table and objects from related tables. But what about the methods?
In object-oriented programming, classes have two kinds of
methods:
instance
and
static
. Instance methods are only called on
actual objects created from the class. Because of this, they have
access to the attribute data within the object. Static methods (also
known as class
methods) are called on the class itself. They have no knowledge of
individual objects of that class. For instance, in the first section
of this chapter, we saw a static DBI method called connect(
).
Persistence requires each Model class to implement three instance
methods, which we’ll call update
,
remove
, and create
. These
methods parallel the SQL UPDATE
,
DELETE
, and INSERT
statements
that, along with SELECT
, make up the vast majority
of SQL statements.
update
This method issues an
UPDATE
statement to save the current state of the
object to the database. When an attribute of a Model object is
altered somewhere in the application, the application issues its
update
method so that the change is reflected in
the database and is visible to other applications.
remove
This method issues a
DELETE
statement to remove the row
representing the object from the database. Whenever an object is
destroyed by the program, it must make sure to call this method.
Destruction can occur through garbage collection or at the
termination of the program, as well as through explicit requests.
Unfortunately, delete
is a keyword in Perl, so we
can’t use it is as the method name.
We’ll use remove
in this chapter.
Other common names include destroy
,
Delete
, and deleteObject
.
create
This method issues an
INSERT
statement to create a new row of data
in the database. Not all objects in the Model need to be in the
database, but anything that you want to have persist beyond a single
run of the application needs to be saved through a
create
method. We chose the name
create
because
“creating” an object is a more
logical term than “inserting” an
object.
While these methods are the only required ones for a Model class, a
common object-oriented practice is to use
accessor
or
getter/setter
methods to retrieve and change the values of
attributes. If you do this, each attribute
of the object should have two instance methods: a
get
method that retrieves the value of the
attribute and a set
method that sets the attribute
to a new value. They can be named anything, but a common practice is
to simply prepend “get” and
“set” to the name of the attribute.
So an attribute called firstName
would have the
methods getFirstName
and
setFirstName
.
The instance methods described above cover three of the four basic
SQL commands. This leaves
SELECT
unimplemented. To implement it, we
turn to static methods. Unlike the others, the
SELECT
command does not operate on existing
objects. The point of a SELECT
query is to
retrieve data from the database. In an object-oriented application,
you must create new objects to represent data selected from the
database. Therefore, it is necessary to use static methods that do
not rely on instance data.
Therefore, we’ll write a static method that sends
SELECT
queries to a database and creates new Model
objects from the data returned. Unlike the other methods considered
so far, there are often several methods within a Model class that
need to select data. This is because there are usually different
contexts in which to create new objects. We’ll
implement the two methods that almost every application needs:
Generic Where and Primary Key. For better reuse of code,
we’ll implement Primary Key in terms of Generic
Where. Only the latter needs to issue SQL.
This is the most versatile and common
select method. An SQL WHERE
clause is passed into
it as a parameter (or generated from other parameters) and it sends a
SELECT
query to the database containing this
WHERE
. Out of the resulting data, an array of
Model objects is created. Because of the flexibility of the
WHERE
clause, this method can be leveraged by more
specialized select methods, such as the Primary Key select that
follows.
Well-designed relational tables almost
always have a primary key. If you know a primary key value, you can
retrieve a single row of data from the table. A Model class uses the
method to create a single object corresponding to a row of data. We
implement this method by creating an SQL WHERE
clause containing the value of the primary key and then calling the
Generic Where select, previously described, to execute the query.
Since we are sending in the value of the primary key, we know we will
get an array containing a single row in return. This method then
returns this single object.
You might consider Primary Key to be a utility or convenience function built on top of Generic Where.