------------------------------------------------------------
Listing 5: Updated version of People.pm

package People;

use strict;
use DBI;

# Declare global variables
use vars qw($dbhost $dbuser $dbpassword $dsn @ISA);

my $dbhost = 'localhost';
my $dbuser = 'reuven';
my $dbpassword = '';
my $dsn = "DBI:Pg:dbname=atf;host=$dbhost;";

# We don't inherit from anyone
@ISA = ();

# Constructor: Takes a class as an argument, and connects to the
# database.
# Returns a new People object, or undef if there was an error.
sub new
{                           
    # Get our class
    my $class = shift;

    # Create our instance
    my $self = {};

    # Connect to the database.  Set RaiseError, but not PrintError,
    # since objects should not display errors when they occur.
    my $dbh = DBI->connect($dsn, $dbuser, $dbpassword,
                           {
                            RaiseError => 1, AutoCommit => 1});

    # If we could not connect, return undef
    return undef unless (defined $dbh);

    # Store the database handle as an instance variable
    $self->{dbh} = $dbh;

    # Set the current person
    $self->{current_person} = undef;

    # Turn $self into an object
      bless $self, $class;

    # Return the new instance
    return $self;
}

# ^L
# get_current_person: Returns a unique internal numeric ID for
# the current person.
sub get_current_person
{
    # Get myself
    my $self = shift;

    # Get my current person
    my $current_person = $self->{current_person};

    # Return the value
    return $current_person;
}

# ^L                    
# get_all_full_names: Returns a list of strings
# containing the first
# and last names of all people in the database
sub get_all_full_names
{
    # Get myself
    my $self = shift;

    # Get the database handle
    my $dbh = $self->{dbh};

    # Initialize the array
    my @full_names = ();

    # Set the SQL to retrieve all names
    my $sql = "SELECT first_name || ' ' || last_name ";
    $sql .= "FROM People ";
    $sql .= "ORDER BY first_name ";

    # Perform the query
    my $sth = $dbh->prepare($sql);
    $sth->execute();

    # Retrieve query results   
    while (my ($name) = $sth->fetchrow_array)
    {
        push @full_names, $name;
    }

    # Finish with this statement
    $sth->finish();

    # Return self
    return @full_names;
}

# ^L
# get_all_email_addresses: Returns a list of array references, in
# which the first element is the name and the second element is the
# e-mail address
sub get_names_and_addresses
{
    # Get myself
    my $self = shift;

    # Get the database handle 
    my $dbh = $self->{dbh};

    # Initialize the array
    my @names_and_addresses = ();

    # Set the SQL to retrieve all names
    my $sql = "SELECT first_name || ' ' || last_name, email ";
    $sql .= "FROM People ";
    $sql .= "ORDER BY last_name, first_name ";

    # Perform the query
    my $sth = $dbh->prepare($sql);
    $sth->execute();

    # Retrieve query results
    while (my ($name, $email_address) = $sth->fetchrow_array)
    {
        push @names_and_addresses, [$name, $email_address];
    }

    # Finish with this statement
    $sth->finish(); 

    # Return the e-mail addresses
    return @names_and_addresses;
}

# ^L
# get_current_info: Returns a hash reference with name-value pairs
# describing information about the current person.
sub get_current_info
{
    # Get myself
    my $self = shift;

    # Get the database handle
    my $dbh = $self->{dbh};

    # Get the current person
    my $current_person = $self->{current_person};

    # Create the empty hash reference
    my $user_info = {};

    # Make sure that we have a current person set!
    return undef unless $current_person;

    # Set the SQL to retrieve all information
    my $sql = "SELECT first_name, last_name, address1, address2, ";
    $sql .= "email, city, state, postal_code, country, comments ";
    $sql .= "FROM People ";
    $sql .= "WHERE person_id = ? ";

    # Perform the query
    my $sth = $dbh->prepare($sql);
    $sth->execute($current_person);

    # Retrieve query results, copying the returned hashref into
    # another hashref.
    while (my $person_hashref = $sth->fetchrow_hashref)
    {
        %{$user_info} = %{$person_hashref};
    }

    # Finish with this statement
    $sth->finish();
                 
    return $user_info;
}

# ^L
# Returns a list all of the e-mail addresses in the database.
sub get_email_addresses
{
    # Get myself
    my $self = shift;

    # Get the database handle
    my $dbh = $self->{dbh};

    # Get ready to store IDs
    my @email_addresses = ();

    # Set the SQL
    my $sql = "SELECT email People ";
    $sql .=   "ORDER BY email";

    # Perform the query
    my $sth = $dbh->prepare($sql);        
    $sth->execute();

    # Retrieve query results
    while (my ($address) = $sth->fetchrow_array)
    {
        push @email_addresses, $address;
    }

    # Finish with this statement
    $sth->finish();

    # Return self
    return @email_addresses;
}

# ^L
# Set the current person, based on the e-mail address
sub set_current_person_by_email
{
    # Get myself
    my $self = shift;

    # Get the e-mail address
    my $email_address = shift;

    # Get the database handle
    my $dbh = $self->{dbh};

    # Set the SQL
    my $sql = "SELECT person_id ";
    $sql .= "FROM People ";
    $sql .= "WHERE email = ? ";

    # Perform the query
    my $sth = $dbh->prepare($sql);
    $sth->execute($email_address);

    # Get the person_id
    my ($person_id) = $sth->fetchrow_array;

    # Finish with this statement
    $sth->finish();

    # Set the current person to the ID from the database
    $self->{current_person} = $person_id; 

    # Return the object
    return $self;
}

# ^L
# Set the current person, based on the first and last names
sub set_current_person_by_name
{
    # Get myself
    my $self = shift;

    # Get the names
    my $first_name = shift;
    my $last_name = shift;

    # Get the database handle
    my $dbh = $self->{dbh};

    # Set the SQL
    my $sql = "SELECT person_id ";
    $sql .= "FROM People ";
    $sql .= "WHERE first_name = ? ";
    $sql .= "  AND last_name = ? ";

    # Perform the query
    my $sth = $dbh->prepare($sql);
    $sth->execute($first_name, $last_name);

    # Get the person_id
    my ($person_id) = $sth->fetchrow_array;

    # Finish with this statement
    $sth->finish();

    # If we got a user ID, set it and return the object
    if ($person_id)
    {
        # Set the current person to the ID from the database
        $self->{current_person} = $person_id;

        # Return the object
        return $self;
    }
    else                       
    {
        return undef;
    }
}

# ^L
# Create a new person
# Takes a hash of arguments, and uses it to insert a new row into People
sub new_person
{
    # Get myself
    my $self = shift;

    # Use the rest of the arguments as a hash
    my %args = @_;

    # Get the database handle
    my $dbh = $self->{dbh};

    # Make sure we have at least the items we need
    return undef unless ($args{first_name} and $args{last_name} and
                         $args{email} and $args{country});
    
    # Start a transaction, so that we can be sure everything is done
    # together
    $dbh->{AutoCommit} = 0;

    # Does a person with this e-mail address (a UNIQUE key)
    # already exist:
    my $sql = "SELECT person_id ";
    $sql .=   "FROM People ";
    $sql .=   "WHERE email = ? ";

    # Look for such a primary key
    my $sth = $dbh->prepare($sql);
    $sth->execute($args{email});

    # Get a primary key, if one exists
    my ($person_id) = $sth->fetchrow_array;

    # If we got an ID, then the user exists: rollback, and return undef
    if ($person_id)
    {
        $dbh->rollback();
        $dbh->{AutoCommit} = 1;
                                          
        return undef;
    }

    # Create the SQL to insert a new row
    $sql = "INSERT INTO People ";
    $sql .= "(first_name, last_name, address1, address2, email, ";
    $sql .= " city, state, postal_code, country, comments) ";
    $sql .= "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ";

    # Insert the row
    my $affected_rows =
        $dbh->do($sql, undef, $args{first_name}, $args{last_name},
                 $args{address1}, $args{address2}, $args{email},
                 $args{city}, $args{state}, $args{postal_code},
                 $args{country}, $args{comments});

    # If the INSERT was successful, set the current person
    # to be the newly inserted primary key
    if ($affected_rows)
    {
        # Get the inserted primary key
        my $sql = "SELECT currval(?)";

        # Prepare and execute the SELECT
        my $sth = $dbh->prepare($sql);
        $sth->execute('people_person_id_seq');

        # Retrieve the primary key
        my ($person_id) = $sth->fetchrow_array;

        # We're finished with this statement handle
        $sth->finish;

        # Get the latest
        $self->{current_person} = $person_id;

        # Commit the transaction
        $dbh->commit();
        $dbh->{AutoCommit} = 1;

        return $self;
    }

    # If the INSERT was unsuccessful, return undef
    else
    {
        # Commit the transaction
        $dbh->rollback();
        $dbh->{AutoCommit} = 1;

        return undef;
    }
}

# ^L
# Takes one argument (in addition to the object instance), a new first
# name.  The new name is updated in the database.  Returns the object
# upon success, and undef upon failure.
sub update_first_name
{
    # Get myself
    my $self = shift;

    # Get the new first name
    my $new_first_name = shift;

    # Get the database handle 
    my $dbh = $self->{dbh};

    # Set the SQL
    my $sql = "UPDATE People ";
    $sql .= "SET first_name = ? ";
    $sql .= "WHERE person_id = ? ";

    # Perform the UPDATE
    my $modified_rows =
        $dbh->do($sql, undef, $new_first_name, $self->{current_person});

    # We succeeded; return the object
    if ($modified_rows)
    {
        return $self;
    }

    # We failed; return undef
    else
    {
        return undef;
    }
}
   
# ^L
# Destructor: Called automatically by Perl.  We use this to close the
# database handle.  This isn't really necessary if we are running
# under Apache::DBI.
sub DESTROY
{
    # Get myself
    my $self = shift;

    # Get the database handle
    my $dbh = $self->{dbh};

    # Close the database handle
    $dbh->disconnect;

    return;
}

# Always return a true value from a module 1;
1;                                                                                                                                                                                                         
