Rapid Web Application Deployment with Maypole : Part 2
by Simon CozensApril 29, 2004
Who Am I?
In order to add the shopping cart to the site, we need to introduce the concept of a current user. This will allow viewers of the site to log in and have their own cart. We will be adding two new tables to the database, a table to store details about the user, and one to represent the cart. Our tables will look like so:
CREATE TABLE user (
id int not null auto_increment primary key,
first_name varchar(64),
last_name varchar(64),
email varchar(255),
password varchar(64),
address1 varchar(255),
address2 varchar(255),
state varchar(255),
postal_code varchar(64),
country varchar(64)
);
CREATE TABLE cart_item (
id int not null auto_increment primary key,
user int,
item int
);
As before, Maypole automatically creates classes for the tables. We use Class::DBI relationships to tell Maypole what's going on with these tables:
ISellIt::User->has_many( "cart_items" => "ISellIt::BasketItem");
ISellIt::BasketItem->has_a( "user" => "ISellit::User" );
ISellIt::BasketItem->has_a( "item" => "ISellit::Product" );
We now need a way to tell our application about the current user.
There's a long explanation of Maypole's authentication system in
the
Maypole documentation,
but one of the easiest ways to do add the concept of the current user is
with the Maypole::Authentication::UserSessionCookie
module.
As its name implies, this module takes care of associating a user with a session, and issuing a cookie to the user's browser. It also manages validating the user's login credentials, by default by looking up the user name and password in a database table; precisely what we need!
Maypole provides an authentication method for us to override, and it's here that we're going to intercept any request that requires a user -- viewing the shopping cart, adding items to an order, and so on:
sub authenticate {
my ($self, $r) = @_;
unless ($r->{table} eq "cart" or $r->{action} eq "buy") {
return OK;
}
# Else we need a user
$r->get_user;
if (!$r->{user}) {
$r->template("login");
}
return OK;
}
The get_user method, which does all the work of setting the cookie and setting the credentials, is provided by the UserSessionCookie module. The only thing we need to tell it is that we're going to use the user's email address and password as login credentials, rather than some arbitrary user name. We can do this in the configuration for our application, as described in the UserSessionCookie documentation:
ISellIt->{config}->{auth}->{user_field} = "email";
Next, we set up a login template, which will present the users with a form to enter their credentials; there's one in the Maypole manual, in the Request chapter, which we can modify to suit our needs:
[% INCLUDE header %]
<h2> You need to log in before buying anything </h2>
<DIV class="login">
[% IF login_error %]
<FONT COLOR="#FF0000"> [% login_error %] </FONT>
[% END %]
<FORM ACTION="/[% request.path%]" METHOD="post">
Email Address:
<INPUT TYPE="text" NAME="email"> <BR>
Password: <INPUT TYPE="password" NAME="password"> <BR>
<INPUT TYPE="submit">
</FORM>
</DIV>
And now logging in is sorted out; if a user presents the correct credentials, get_user will put the user's ISellIt::User object in the Maypole request object as $r->{user}, and the user's request will continue to where it was going.
Now, of course, since we have a user object we can play with, we can use the user's information in other contexts:
[% IF request.user %]
<DIV class="messages">
Welcome back, [% request.user.first_name %]!
</DIV>
[% END %]
Since we're going to be referring to the user a lot, we pass it to the template as an additional argument, my. Maypole has an open-ended "hook" method, additional_data, which is perfect for doing just this.
sub additional_data {
my $r = shift;
$r->{template_args}{my} = $r->{user};
}
We call it my so that we can say, for instance:
<DIV class="messages">
Welcome back, [% my.first_name %]!
</DIV>
So now we have a user. We can add a new action, order, to add an item to the user's shopping cart:
package ISellIt::Product;
sub order :Exported {
my ($self, $r, $product) = @_;
$r->{user}->add_to_cart_items({ item => $product });
$r->{template} = "view";
}
This adds an entry in the cart_item table associating the item with the user, and then sends us back to viewing the item.
We've sent our user back shopping without an indication that we actually did add an item to his shopping cart; we can give such an indication by passing information into the template:
sub order :Exported {
my ($self, $r, $product) = @_;
$r->{user}->add_to_cart_items({ item => $product });
$r->{template} = "view";
$r->{template_args}{bought} = 1;
}
And then displaying it:
[% IF bought %]
<DIV class="messages">
We've just added this item to your shopping cart. To complete
your transaction, please <A HREF="/user/view_cart">view your
cart</A> and check out.
</DIV>
[% END %]
So now we need to allow the user to view a cart.
Displaying the Cart
This also turns out to be relatively easy -- most things in Maypole are -- involving an action on the user class. We need to fill our Maypole request object with the items in the user's cart:
package ISellIt::User;
sub view_cart :Exported {
my ($self, $r) = @_;
$r->{objects} = [ $r->{user}->cart_items ];
}
And then we need to produce a user/view_cart template that displays them:
[% PROCESS header %]
<h2> Your Shopping Cart </h2>
<TABLE>
<TR> <TH> Product </TH> <TH> Price </TH> </TR>
[% SET count = 0;
FOR item = objects;
SET count = count + 1;
"<tr";
' class="alternate"' IF count % 2;
">";
%]
<TD> [% item.product.name %] </TD>
<TD> [% item.product.price %] </TD>
<TD>
<FORM ACTION="/cart_item/delete/[% item.id %]">
<INPUT TYPE="submit" VALUE="Remove from cart">
</FORM>
</TD>
</tr>
[% END %]
</TABLE>
<A HREF="/user/checkout"> Check out! </A>
Once again, the HTML isn't great, but it gives us something we can pass to the design people to style up nicely. Now on to checking out the cart...
Pages: 1, 2 |

