=item L<Maypole::Manual::About> - Overview of the Project
-This document is a general introduction to Maypole: What it is
-(a framework for Web development), what it does (at the basic
-level, it converts a URL
-(e.g. C<http://www.mysite.com/product/display/12>) into a
-method call (i.e. "perform the C<display> method on item C<12>
-in the C<product> table") and then shows the result (here,
-presumably, a description of item C<12> in your product
-database)), and how it works (by MVC, a design paradigm in
+This document is a general introduction to Maypole: what it is, what it
+does and how it works.
+
+Maypole is a framework for Web development. At the basic level, it
+converts a URL like C<http://www.mysite.com/product/display/12> into a
+method call such as "perform the C<display> method on item C<12> in the
+C<product> table" and then shows the result: here, presumably, a
+description of item C<12> in your product database,
+
+It is based on Model-View-Controller (MVC), a design paradigm in
which each major aspect of an application's operation is
-handled by a different and totally separate system). Basic
-installation instructions are given. A sample Web
+handled by a different and totally separate system).
+
+Basic installation instructions are given. A sample Web
application--the Beer database--is introduced, set up, and
discussed. Finally, the path a Maypole request takes as it
moves through the system is described.
database and provides a variety of convenient methods for
manipulating each table and its relations. It integrates very
smoothly with Maypole's default L<view class|Maypole::Manual::View>,
-the L<Template> Toolkit.
+the L<Template|Template> Toolkit.
=item L<Maypole::Manual::View> - View Classes *
-This document is an extensive discussion of Maypole's I<view
-class>, which takes the data produced by the model (see
-L<above|Maypole::Manual::Model>) and sends it through a templating
+This document is an extensive discussion of Maypole's I<view class>,
+which takes the data produced by the model (see
+above) and sends it through a templating
system in order to produce output. It focusses chiefly on
the L<Template> Toolkit, which is Maypole's default templating
system, but discusses other possibilities.
paged list of a table suitable for browsing, and C<search>, which
handles a search query and generates search results.
-The standard templates (which generate output for display on
-the Web) include C<list>, which displays the entries in a
+The standard templates, which generate output for display on
+the Web, also include C<list>, which displays the entries in a
table, and C<search>, which displays the result of a search.
You'll note that most actions are associated with templates.
actions and templates, showing you how to write your own
so that you can have a highly customized application.
-=item L<Maypole::Manual::Workflow> - Description of the Maypole Workflow
+=item L<Maypole::Manual::Workflow> - Description of the Request Workflow
This is a technical document that describes the progress of a
request through the entire Maypole system. It should be of
* indicates incomplete chapters.
-=head1 SEE ALSO
-
-L<http://maypole.perl.org>
=head1 AUTHOR
-Sebastian Riedel, C<sri@oook.de>
+The Maypole Manual was written by Simon Cozens. A generous grant from the Perl
+Foundation in the first quarter of 2004 funded some of the chapters of this
+manual.
-This overview written by Jesse Sheidlower, C<jester@panix.com>,
+This overview was rewritten by Jesse Sheidlower, C<jester#panix.com>,
based on Simon Cozens' original I<Overview> document.
+In December 2004, Dave Howorth, C<dave.howorth#acm.org> kindly donated some
+of his spare time to improve the structure of the manual and bring it up to
+date.
+
=head1 AUTHOR EMERITUS
-Simon Cozens, C<simon@cpan.org>
+Simon Cozens, C<simon#cpan.org>
=cut
-=head1 Introduction to the Maypole Request Model
+=head1 Introduction to Maypole
This chapter serves as a gentle introduction to Maypole and setting up
-Maypole applications. We look at Maypole is, how to get it up and
+Maypole applications. We look at what Maypole is, how to get it up and
running, and how to start thinking about building Maypole applications.
=head2 What is Maypole?
the user, and a Controller class which controls the other classes in
response to events triggered by the user. This analogy doesn't
correspond precisely to a web-based application, but we can take an
-important principle from it. As Andy Wardley explains:
+important principle from it. As Template Toolkit author Andy Wardley explains:
What the MVC-for-the-web crowd are really trying to achieve is a clear
separation of concerns. Put your database code in one place, your
I said, it is a blank slate, and everything is customizable. There is a
C<CGI::Maypole> frontend available to run as a standalone CGI script.
+As well as the documentation embedded in the Perl modules the distribution
+also includes the manual, of which this is a part. You can access it using the
+perldoc command, the man command, or by browsing CPAN.
+
=head2 The Beer Database example
Throughout this manual, we're going to be referring back to a particular
talking about. We could say "C<related_accessors> returns a list of
accessors which can be called to return a list of objects in a has-a
relationship to the original", or we could say "if we call
-C<related_accessors> on while viewing C<brewery>, it returns C<beers>,
+C<related_accessors> while viewing a C<brewery>, it returns C<beers>,
because we can call C<beers> on a C<brewery> object to get a list of
-that berwery's beers."
+that brewery's beers."
Because Maypole is all about beer. If you look carefully, you can
probably see men playing cricket on the village green. The first
The first thing we need for a Maypole interface to a database is to
have a database. If you don't have one, now would be a good time to
-create one, using the schema above.
+create one, using the schema above. If you're creating a database
+by hand, don't forget to grant permissions for your Apache server to
+access it as well as yourself (typically a user name like C<www-data>
+or C<wwwrun>).
The next thing we need is a module which is going to do all the work.
Thankfully, it doesn't need to do B<all> the work itself. It's going to be a
subclass of C<Maypole> or a Maypole front-end like C<Apache::MVC>.
+It roughly corresponds to the controller in an MVC design, and is
+also referred to as the driver, handler or request.
Here's the driver class for our beer database application. We're not
-going to go into much detail about it here; we'll do that in L<Beer.pod>.
+going to go into much detail about it here; we'll do that in the
+L<Beer Database|Maypole::Manual::Beer> chapter.
For now, simply admire its brevity, as you realise this is all the code
you need to write for a simple database front-end:
package BeerDB;
use Maypole::Application;
BeerDB->setup("dbi:SQLite:t/beerdb.db");
- BeerDB->config->{uri_base} = "http://localhost/beerdb";
- BeerDB->config->{template_root} = "/path/to/templates";
- BeerDB->config->{rows_per_page} = 10;
- BeerDB->config->{display_tables} = [qw[beer brewery pub style]];
+ BeerDB->config->uri_base("http://localhost/beerdb");
+ BeerDB->config->template_root("/path/to/templates");
+ BeerDB->config->rows_per_page(10);
+ BeerDB->config->display_tables([qw[beer brewery pub style]]);
BeerDB::Brewery->untaint_columns( printable => [qw/name notes url/] );
BeerDB::Style->untaint_columns( printable => [qw/name notes/] );
BeerDB::Beer->untaint_columns(
"a pub has beers on handpumps");
1;
-This defines the C<BeerDB> application, which, as it inherits from
-C<Apache::MVC>, will be a mod_perl handler. This means we need to
-tell the Apache configuration about it:
+There's a version of this program in the F<ex/> directory in the Maypole
+files that you downloaded in the F<~root/.cpan/> build area.
+This defines the C<BeerDB> application.
+To set it up as a mod_perl handler, just tell the Apache configuration
+about it:
<Location /beerdb>
SetHandler perl-script
PerlHandler BeerDB
</Location>
-And now we need some templates. As we'll see in the chapter on views,
-L<View.pod>, there are several types of template. We're going to copy
+To use it as a CGI script, put it in your F<cgi-bin> directory,
+together with a small file called F<beer.cgi>:
+
+ #!/usr/bin/perl
+ use strict;
+ use warnings;
+ use BeerDB;
+ BeerDB->run();
+
+and change one line in C<BeerDB.pm>:
+
+ BeerDB->config->uri_base("http://localhost/cgi-bin/beer.cgi");
+
+And now we need some templates. As we'll see in the chapter on
+L<views|Maypole::Manual::View>, there are several types of template.
+We're going to copy
the whole lot from the F<templates/> directory of the Maypole source
package into the F</beerdb> directory under our web root.
+Make the C<template_root> in C<BeerDB> agree with your path.
And that's it. We should now be able to go to C<http://localhost/beerdb/>
+or C<http://localhost/cgi-bin/beer.cgi/>
and see a menu of things to browse; C<http://localhost/beerdb/beer/list>
will give a list of beers. There might not be any yet. There's a box
that lets you add them.
-If you have any problems getting to this point, you might want to look
-at L<http://maypole.perl.org>.
+If you have any problems getting to this point, you might want to look at
+L<http://maypole.perl.org>. There's a FAQ and a link to a mailing
+list.
Play about with the site. Add some beers. Maybe go out and buy some beers
to review if you need some inspiration. Don't be ill on my carpet.
of this manual will be about how to do that.
In order to do that, we need to look at what Maypole's actually doing.
+Here's a quick overview, there's more detail in the
+L<Workflow|Maypole::Manual::Workflow> chapter.
As mentioned, Maypole is responsible for turning a URL into an object, a
-method call, and some templated output. Here's a handy diagram to
-explain how it does that:
+method call, and some templated output.
+
+=for html
+Here's a handy diagram to explain how it does that:
=for html
<IMG SRC="maypole_process2.png">
as a parameter, and passes the whole lot to the view class for templating.
In the next two chapters, we'll look at how Maypole's default model and
view classes generally do what you want them to do.
+
+=head2 Links
+
+L<Contents|Maypole::Manual>,
+Next L<Maypole Model Classes|Maypole::Manual::Model>
=head1 The Beer Database, Twice
-We briefly introduced the "beer database" example in the L<About.pod>
-material, where we presented its driver class as a fait accompli. Where
+We briefly introduced the "beer database" example in the
+L<Introduction to Maypole|Maypole::Manual::About> chapter, where we
+presented its driver class, C<BeerDB.pm>, as a fait accompli. Where
did all that code come from, and what does it actually mean?
=head2 The big beer problem
package BeerDB;
use Maypole::Application;
- BeerDB->set_database("dbi:SQLite:t/beerdb.db");
- BeerDB->config->{uri_base} = "http://localhost/beerdb";
- BeerDB->config->{template_root} = "/path/to/templates";
- BeerDB->config->{rows_per_page} = 10;
- BeerDB->config->{display_tables} = [qw[beer brewery pub style]];
+ BeerDB->setup("dbi:SQLite:t/beerdb.db");
+ BeerDB->config->uri_base("http://localhost/beerdb");
+ BeerDB->config->template_root("/path/to/templates");
+ BeerDB->config->rows_per_page(10);
+ BeerDB->config->display_tables([qw[beer brewery pub style]]);
BeerDB::Brewery->untaint_columns( printable => [qw/name notes url/] );
BeerDB::Style->untaint_columns( printable => [qw/name notes/] );
BeerDB::Beer->untaint_columns(
going to run.
The second line says that our front-end is going to be
-C<Maypole::Application>, it automatically detects if you're using
-mod_perl or CGI and loads everything neccessary for you.
+L<Maypole::Application>, it automatically detects if you're using
+mod_perl or CGI and loads everything necessary for you.
Thirdly we're going to need to set up our database with the given DBI
connection string. Now the core of Maypole itself doesn't know about
-DBI; as we explained in L<Model.pod>, this argument is passed to our
+DBI; as we explained in the L<Model|Maypole::Manual::Model> chapter,
+this argument is passed to our
model class wholesale. As we haven't said anything about a model
-class, we get the default one, C<Maypole::Model::CDBI>, which takes a
+class, we get the default one, L<Maypole::Model::CDBI>, which takes a
DBI connect string. So this one line declares that we're using a C<CDBI>
model class and it sets up the database for us. In the same way, we
don't say that we want a particular view class, so we get the default
-C<Maypole::View::TT>.
+L<Maypole::View::TT>.
At this point, everything is in place; we have our driver class, it uses
a front-end, we have a model class and a view class, and we have a data
The next of our four sections is the configuration for the application itself.
- BeerDB->config->{uri_base} = "http://localhost/beerdb/";
- BeerDB->config->{rows_per_page} = 10;
- BeerDB->config->{display_tables} = [qw[beer brewery pub style]];
+ BeerDB->config->uri_base("http://localhost/beerdb");
+ BeerDB->config->template_root("/path/to/templates");
+ BeerDB->config->rows_per_page(10);
+ BeerDB->config->display_tables([qw[beer brewery pub style]]);
-Maypole provides a method called C<config> which returns a hash reference
-of the application's whole configuration. We can use this to set some
+Maypole provides a method called C<config> which returns an object that
+holds the application's whole configuration. We can use this to set some
parameters; the C<uri_base> is used as the canonical URL of the base
of this application, and Maypole uses it to construct links.
+We also tell Maypole where we keep our template files, using
+C<template_root>.
+
By defining C<rows_per_page>, we say that any listings we do with the
C<list> and C<search> templates should be arranged in sets of pages, with
a maximum of 10 items on each page. If we didn't declare that, C<list>
date => [ qw/date/],
);
-As explained in L<StandardTemplates.pod>, this is an set of instructions to
-C<Class::DBI::FromCGI> regarding how the given columns should be edited.
+As explained in the
+L<Standard Templates|Maypole::Manual::StandardTemplates> chapter,
+this is an set of instructions to
+L<Class::DBI::FromCGI> regarding how the given columns should be edited.
If we didn't have this section, we'd be able to view and delete records,
but adding and editing them wouldn't work. It took me ages to work that
one out.
the brewery does not appear as an integer like "2" but as the name of
the brewery from the C<brewery> table with an ID of 2.
-The usual C<Class::DBI> way to do this involves the C<has_a> and
+The usual L<Class::DBI> way to do this involves the C<has_a> and
C<has_many> methods, but I can never remember how to use them, so I came
-up with the C<Class::DBI::Loader::Relationship> module; this was another
+up with the L<Class::DBI::Loader::Relationship> module; this was another
yak that needed shaving on the way to the beer database:
use Class::DBI::Loader::Relationship;
"a pub has beers on handpumps");
1;
-C<CDBIL::Relationship> acts on a C<Class::DBI::Loader> object and
+C<CDBIL::Relationship> acts on a L<Class::DBI::Loader> object and
defines relationships between tables in a fairly free-form style.
The equivalent in ordinary C<Class::DBI> would be:
of them are fine, but that "Abv" column stands out. I'd rather that was
"A.B.V.". Maypole uses the C<column_names> method to map between the
names of the columns in the database to the names it displays in the
-default templates. This is provided by C<Maypole::Model::Base>, and
+default templates. This is provided by L<Maypole::Model::Base>, and
normally, it does a pretty good job; it turns C<model_number> into
"Model Number", for instance, but there was no way it could guess that
C<abv> was an abbreviation. Since it returns a hash, the easiest way
(shift->SUPER::column_names(), abv => "A.B.V.")
}
-Similarly, the order of columns is a bit wonky. We can fix this by
+There's something to be aware of here: where are you going to type that
+code? You can just put it in F<BeerDB.pm>. Perl will be happy with that,
+though you might want to put an extra pair of braces around it to limit
+the scope of that package declaration. Alternatively, you might think
+it's neater to put it in a file called F<BeerDB/Beer.pm>, which is the
+natural home for the package. This would certainly be a good idea if you
+have a lot of other code to add to the C<BeerDB::Beer> package. But if
+you do that, you will have to tell Perl to load the F<BeerDB/Beer.pm>
+file by adding a line to F<BeerDB.pm>:
+
+ BeerDB::Beer->require;
+
+For another example of customization, the order of columns is a bit
+wonky. We can fix this by
overriding the C<display_columns> method; this is also a good way to
hide away any columns we don't want to have displayed, in the same way
as declaring the C<display_tables> configuration parameter let us hide
Hey, have you noticed that we haven't done anything with the
beers/handpumps/pubs thing yet? Good, I was hoping that you hadn't.
-Ayway, this is because Maypole can't tell easily that a C<BeerDB::Beer>
+Anyway, this is because Maypole can't tell easily that a C<BeerDB::Beer>
object can call C<pubs> to get a list of pubs. Not yet, at least; we're
working on it. In the interim, we can explicitly tell Maypole which
accessors are related to the C<BeerDB::Beer> class like so:
sub related { "pubs" }
Now when we view a beer, we'll have a list of the pubs that it's on at.
+
+=head2 Links
+
+L<Contents|Maypole::Manual>,
+Next L<The Request Cookbook|Maypole::Manual::Request>,
+Previous L<Maypole|Maypole::Manual::Workflow>
+
=head1 The Maypole iBuySpy Portal
-I think it's good fun to compare Maypole
+I think it's good fun to compare Maypole against other frameworks,
+so here's how to build the ASP.NET tutorial site in Maypole.
-We begin with a length process of planning and investigating the
+We begin with a lengthy process of planning and investigating the
sources. Of prime interest is the database schema and the initial data,
-which we convert to a Mysql database. Converting MS SQL to Mysql is not fun.
+which we convert to a MySQL database. Converting MS SQL to MySQL is not fun.
I shall spare you the gore. Especially the bit where the default insert IDs
didn't match up between the tables.
use Maypole::Application;
Portal->setup("dbi:mysql:ibsportal");
use Class::DBI::Loader::Relationship;
- Portal->config->{loader}->relationship($_) for (
+ Portal->config->loader->relationship($_) for (
"A module has a definition", "A module has settings",
"A tab has modules", "A portal has tabs",
"A role has a portal", "A definition has a portal",
sub parse_path {
my $self = shift;
- $self->{path} ||= "DesktopDefault.aspx";
- return $self->SUPER::parse_path if not exists $pages{$self->{path}};
- my $page = $pages{$self->{path}} ;
- $self->{action} = $page->{action};
- $self->{table} = $page->{table};
- my %query = $self->{ar}->args;
- $self->{args} = [ $query{tabid} || $query{ItemID} || 1];
+ $self->path("DesktopDefault.aspx") unless $self->path;
+ return $self->SUPER::parse_path if not exists $pages{$self->path};
+ my $page = $pages{$self->path} ;
+ $self->action($page->{action});
+ $self->table($page->{table});
+ my %query = $self->ar->args;
+ $self->args( [ $query{tabid} || $query{ItemID} || 1] );
}
1;
Here we're overriding the C<parse_path> method which takes the C<path>
slot from the request and populates the C<table>, C<action> and
-C<arguments> slots. If the user has asked for a page we don't know
+C<args> slots. If the user has asked for a page we don't know
about, we ask the usual Maypole path handling method to give it a try;
this will become important later on. We turn the default page,
C<DesktopDefault.aspx>, into the equivalent of C</tab/view/1> unless
Dead right, but it was here that I got too clever. I guess it was the word
"component" that set me off. I thought that since the page was made up of a
large number of different modules, all requiring their own set of objects, I
-should use a seperate Maypole sub-request for each one, as shown in the
-"Component-based pages" recipe in L<Request.pod>.
+should use a separate Maypole sub-request for each one, as shown in the
+"Component-based pages" recipe in the
+L<Request Cookbook|Maypole::Manual::Request>.
So this is what I did. I created a method in C<Portal::Module> that would
set the template to the appropriate C<ascx> file:
sub view_desktop :Exported {
my ($self, $r) = @_;
- $r->{template} = $r->objects->[0]->definition->DesktopSrc;
+ $r->template($r->objects->[0]->definition->DesktopSrc);
}
and changed the C<pane> macro to fire off a sub-request for each module:
we have to arrange the array of C<module.settings> into a hash of
C<key_name> => C<setting> pairs. Frankly, I can't be bothered to do this
in the template, so we'll add it into the C<template_args> again. This
-time C<addition_data> looks like:
+time C<additional_data> looks like:
sub additional_data {
my $r = shift;
- shift->{template_args}{portal} = Portal::Portal->retrieve(2);
- if ($r->{objects}->[0]->isa("Portal::Module")) {
- $r->{template_args}{module_settings} =
+ shift->template_args->{portal} = Portal::Portal->retrieve(2);
+ if ($r->objects->[0]->isa("Portal::Module")) {
+ $r->template_args->{module_settings} =
{ map { $_->key_name, $_->setting }
- $r->{objects}->[0]->settings };
+ $r->objects->[0]->settings };
}
}
=head2 Adding users
+...
+
+=head2 Links
+
+L<Contents|Maypole::Manual>,
+Next B<That's all folks! Time to start coding ...>,
+Previous L<Flox|Maypole::Manual::Flox>
+
Flox is still in, uh, flux, but it does the essentials. We're going to
see how it was put together, and how the techniques shown in the
-L<Request.pod> chapter can help to create a sophisticated web
+L<Request Cookbook|Maypole::Manual::Request> can help to
+create a sophisticated web
application. Of course, I didn't have this manual available at the time,
so it took a bit longer than it should have done...
Any Maypole application should start with two things: a database schema,
and some idea of what the pages involved are going to look like.
-Usually, these pages will be tying to displaying or editing some element
+Usually, these pages will be displaying or editing some element
of the database, so these two concepts should come hand in hand.
When I started looking at social networking sites, I began by
expires date
);
-Plus the definition of our two auxilliary tables:
+Plus the definition of our two auxiliary tables:
CREATE TABLE affiliation (
id int not null auto_increment primary key,
package Flox;
use Maypole::Application;
Flox->setup("dbi:mysql:flox");
- Flox->config->{display_tables} = [qw[user invitation connection]];
+ Flox->config->display_tables([qw[user invitation connection]]);
1;
Very simple, as these things are meant to be. Now let's build on it.
-=head2 Authentication
+=head2 Users and Authentication
The concept of a current user is absolutely critical in a site like
Flox; it represents "me", the viewer of the page, as the site explores
the connections in my world. We've described the authentication hacks
-briefly in the L<Request.pod> chapter, but now it's time to go into a
-little more detail about how user handling is done.
-
-XXX
+briefly in the L<Request Cookbook|Maypole::Manual::Request>,
+but now it's time to go into a little more detail about how user
+handling is done.
We also want to be able to refer to the current user from the templates,
-so we use the overridable C<additional_data> method to give us a C<my>
-template variable:
+so we use the overridable C<additional_data> method in the driver class
+to give us a C<my> template variable:
sub additional_data {
- my $r = shift; $r->{template_args}{my} = $r->{user};
+ my $r = shift; $r->template_args->{my} = $r->user;
}
I've called it C<my> rather than C<me> because we it lets us check
sub view :Exported {
my ($class, $r) = @_;
- $r->{objects} = [ $r->{user} ] unless @{$r->{objects}||[]};
+ $r->objects([ $r->user ]) unless @{ $r->objects || [] };
}
Maypole, unfortunately, is very good at making programming boring. The
downside of having to write very little code at all is that we now have
to spend most of our time writing nice HTML for the templates.
-XXX
+=head2 Pictures of Users
The next stage is viewing the user's photo. Assuming we've got the photo
stored in the database already (which is a reasonable assumption for the
moment since we don't have a way to upload a photo quite yet) then we
-can use the a variation of the "Displaying pictures" hack from the
-Requests chapter:
+can use a variation of the "Displaying pictures" hack from the
+L<Request Cookbook|Maypole::Manual::Request>:
sub view_picture :Exported {
my ($self, $r) = @_;
- my $user = $r->{objects}->[0] || $r->{user};
- if ($r->{content_type} = $user->photo_type) {
- $r->{output} = $user->photo;
+ my $user = $r->objects->[0] || $r->user;
+ if ($r->content_type($user->photo_type)) {
+ $r->output($user->photo);
} else {
# Read no-photo photo
- $r->{content_type} = "image/png";
- $r->{output} = slurp_file("images/no-photo.png");
+ $r->content_type("image/png");
+ $r->output(slurp_file("images/no-photo.png"));
}
}
In our template, we can now say
- <IMG SRC="/user/view_picture/[% user.id %]">
+ <IMG SRC="[%base%]/user/view_picture/[% user.id %]">
and the appropriate user's mugshot will appear.
However, if we're throwing big chunks of data around like C<photo>, it's
now worth optimizing the C<User> class to ensure that only pertitent
data is fetched by default, and C<photo> and friends are only fetched on
-demand. The "lazy population" section of C<Class::DBI>'s man page
+demand. The "lazy population" section of L<Class::DBI>'s man page
explains how to group the columns by usage so that we can optimize
fetches:
So now we can view users and their photos. It's time to allow the users
to edit their profiles and upload a new photo.
-=head2 Editing users
-
-XXX Editing a profile
+=head2 Editing user profiles
I introduced Flox to a bunch of friends and told them to be as ruthless
as possible in finding bugs and trying to break it. And break it they
use constant MAX_IMAGE_SIZE => 512 * 1024;
sub do_upload :Exported {
my ($class, $r) = @_;
- my $user = $r->{user};
- my $upload = $r->{ar}->upload("picture");
+ my $user = $r->user;
+ my $upload = $r->ar->upload("picture");
if ($upload) {
my $ct = $upload->info("Content-type");
return $r->error("Unknown image file type $ct")
my ($x, $y) = imgsize(\$image);
return $r->error("Image too big! ($x, $y) Maximum size is 350x350")
if $y > 350 or $x > 350;
- $r->{user}->photo_type($ct);
- $r->{user}->photo($image);
+ $r->user->photo_type($ct);
+ $r->user->photo($image);
}
$r->objects([ $user ]);
- $r->{template} = "view";
+ $r->template("view");
}
Now we've gone as far as we want to go about user editing at the moment.
=head2 Invitations
-We need to do two things to invitations working: first provide a way to
-issue an invitation, and then provide a way to accept it. Since what
+We need to do two things to make invitations work: first provide a way
+to issue an invitation, and then provide a way to accept it. Since what
we're doing in issuing an invitation is essentially creating a new
one, we'll use our usual practice of having a page to display the form
to offer an invitation, and then use a C<do_edit> method to actually do
<FORM ACTION="[%base%]/invitation/do_edit/" METHOD="post">
<TABLE>
-Now we use the "Catching errors in a form" recipe from L<Request.pod> and
+Now we use the "Catching errors in a form" recipe from the
+L<Request Cookbook|Maypole::Manual::Request> and
write our form template:
<TR><TD>
things we need to check before we actually do the create. So here's the
untainting of the parameters:
- my ($self, $r) = @_;
- my $h = CGI::Untaint->new(%{$r->{params}});
- my (%errors, %ex);
- for (qw( email forename surname )) {
- $ex{$_} = $h->extract(
- "-as_".($_ eq "email" ? "email" : "printable") => $_
- ) or $errors{$_} = $h->error;
- }
+ sub do_edit :Exported {
+ my ($self, $r) = @_;
+ my $h = CGI::Untaint->new(%{$r->params});
+ my (%errors, %ex);
+ for (qw( email forename surname )) {
+ $ex{$_} = $h->extract(
+ "-as_".($_ eq "email" ? "email" : "printable") => $_
+ ) or $errors{$_} = $h->error;
+ }
Next, we do the usual dance of throwing the user back at the form in case
of errors:
- if (keys %errors) {
- $r->{template_args}{message} = "There was something wrong with that...";
- $r->{template_args}{errors} = \%errors;
- $r->{template} = "issue";
- return;
- }
+ if (keys %errors) {
+ $r->template_args->{message} =
+ "There was something wrong with that...";
+ $r->template_args->{errors} = \%errors;
+ $r->template("issue");
+ return;
+ }
We've introduced a new template variable here, C<message>, which we'll use
to display any important messages to the user.
abort the invite progress and instead redirect them to viewing that user's
profile.
- my ($user) = Flox::User->search({ email => $ex{email} });
- if ($user) {
- if ($user->status eq "real") {
- $r->{template_args}{message} =
- "That user already seems to exist on Flox. ".
- "Is this the one you meant?";
+ my ($user) = Flox::User->search({ email => $ex{email} });
+ if ($user) {
+ if ($user->status eq "real") {
+ $r->template_args->{message} =
+ "That user already seems to exist on Flox. ".
+ "Is this the one you meant?";
- $self->redirect_to_user($r,$user);
- }
+ $self->redirect_to_user($r,$user);
+ }
Where C<redirect_to_user> looks like this:
sub redirect_to_user {
my ($self, $r, $user) = @_;
- $r->{objects} = [ $user ];
- $r->{template} = "view";
- $r->{model_class} = "Flox::User"; # Naughty.
+ $r->objects([ $user ]);
+ $r->template("view");
+ $r->model_class("Flox::User"); # Naughty.
}
This is, as the comment quite rightly points out, naughty. We're currently
we put it into a subroutine so that we can fix it up if we find a better way
to do it.
-Anyway, this is what we should do if a user already exists on the system
+Anyway back in the C<do_edit> action,
+this is what we should do if a user already exists on the system
and has accepted an invite already. What if we're trying to invite a user but
someone else has invited them first and they haven't replied yet?
- } else {
- # Put it back to the form
- $r->{template_args}{message} =
- "That user has already been invited; ".
- "please wait for them to accept";
- $r->{template} = "issue";
+ } else {
+ # Put it back to the form
+ $r->template_args->{message} =
+ "That user has already been invited; " .
+ "please wait for them to accept";
+ $r->template("issue");
+ }
+ return;
}
- return;
- }
Race conditions suck.
Okay. Now we know that the user doesn't exist, and so can create the new
one:
- my $new_user = Flox::User->create({
- email => $ex{email},
- first_name => $ex{forename},
- last_name => $ex{surname},
- status => "invitee"
- });
+ my $new_user = Flox::User->create({
+ email => $ex{email},
+ first_name => $ex{forename},
+ last_name => $ex{surname},
+ status => "invitee"
+ });
We want to give the invitee a URL that they can go to in order to
accept the invite. Now we don't just want the IDs of our invites to
rest of the invite codes. We provide a relatively secure MD5 hash as
the invite ID:
- my $random = md5_hex(time.(0+{}).$$.rand);
+ my $random = md5_hex(time.(0+{}).$$.rand);
For additional security, we're going to have the URL in the form
C</invitation/accept/I<id>/I<from_id>/I<to_id>>, encoding the user ids
of the two users. Now we can send email to the invitee to ask them to
visit that URL:
- my $newid = $new_user->id;
- my $myid = $r->{user}->id;
- _send_mail(to => $ex{email}, url => "$random/$myid/$newid",
- user => $r->{user});
+ my $newid = $new_user->id;
+ my $myid = $r->user->id;
+ _send_mail(to => $ex{email},
+ url => "$random/$myid/$newid",
+ user => $r->user);
I'm not going to show the C<_send_mail> routine, since it's boring.
We haven't actually created the C<Invitation> object yet, so let's
do that now.
- Flox::Invitation->create({
- id => $random,
- issuer => $r->{user},
- recipient => $new_user,
- expires => Time::Piece->new(time + LIFETIME)->datetime
- });
+ Flox::Invitation->create({
+ id => $random,
+ issuer => $r->user,
+ recipient => $new_user,
+ expires => Time::Piece->new(time + LIFETIME)->datetime
+ });
You can also imagine a daily cron job that cleans up the C<Invitation>
table looking for invitations that ever got replied to within their
Anyway, now we've got the invitation created, we can go back to whence we
came: viewing the original user:
- $self->redirect_to_user($r, $r->{user});
+ $self->redirect_to_user($r, $r->user);
Now our invitee has an email, and goes B<click> on the URL. What happens?
=head2 Friendship Connections
-=head2
+XXX
+
+=head2 Links
+
+The source for Flox is available at
+L<http://cvs.simon-cozens.org/viewcvs.cgi/flox>.
+
+L<Contents|Maypole::Manual>,
+Next L<The Maypole iBuySpy Portal|Maypole::Manual::BuySpy>,
+Previous L<Maypole Request Hacking Cookbook|Maypole::Manual::Request>
+
=head1 Maypole Model Classes
-=head2 Class::DBI
+Maypole's model classes provide an interface to your data store.
+In principle Maypole can connect to pretty much any data source,
+but the default model is based on the popular L<Class::DBI> object
+interface that uses the near-universal L<DBI> Perl interface to databases.
=head2 Maypole::Model::CDBI
BeerDB->setup("dbi:mysql:beerdb");
C<setup> is a Maypole method, and it hands its parameter to the model
-class. In our case, the model class is a DBI connect string, because
+class. In our case, the argument is a DBI connect string, because
that's what C<Maypole::Model::CDBI>, the C<Class::DBI>-based model
expects. C<Maypole::Model::CDBI> has a method called C<setup_database>
that creates all the C<Class::DBI> table classes after connecting to the
database with that connect string. It does this by using
C<Class::DBI::Loader>, a utility module which asks a database
about its schema and sets up classes such as C<BeerDB::Beer> to inherit
-from C<Class::DBI>. This is just doing automatically what we did
-manually in our examples above.
+from C<Class::DBI>.
Now it gets interesting. The names of these classes are stashed away in
the application's configuration, and then Maypole forcibly has these
use Class::DBI::Plugin::RetrieveAll;
use Class::DBI::Pager;
-We'll meet most of these goodies in L<StandardTemplates.pod>, where we
-explain how C<Maypole::Model::CDBI> works.
+We'll meet most of these goodies in the
+L<Standard Templates and Actions|Maypole::Manual::StandardTemplates>
+chapter, where we explain how C<Maypole::Model::CDBI> works.
The second reason why we want our table classes to inherit from
-C<Maypole::Model::CDBI> is because C<CDBI> provides a useful set of
+C<Maypole::Model::CDBI> is because it provides a useful set of
default actions. So what's an action, and why are they useful?
=head2 Extending a model class with actions
that. Firstly because it doesn't actually pass the parameter C<20>, but
it passes an object representing row 20 in the database, but we can
gloss over that for the second. No, the real issue is that Maypole does
-not allow you to call any method in the table class; that would be
+not allow you to call just any method in the table class; that would be
somewhat insecure.
Instead, Maypole makes a distinction between the kind of methods that
only the class itself and other Perl code can call, and the kind of
methods that anyone can call from a URL. This latter set of methods are
-called B<exported> methods, and exporting is done with by means of Perl
+called B<exported> methods, and exporting is done by means of Perl
attributes. You define a method to be exported like so:
sub drink :Exported {
Maypole model classes like C<Maypole::Model::CDBI> come with a
relatively handy set of actions which are all you need to set up a CRUD
+(Create, Read, Update, Delete)
database front-end: viewing a row in a database, editing it, adding a
new one, deleting, and so on. The most important thing about Maypole,
though, is that it doesn't stop there. You can add your own.
package BeerDB::Beer;
sub top_five :Exported {
my ($class, $r) = @_;
- $r->{objects} = [ ($r->retrieve_all_sorted_by("score"))[-5..-1] ];
+ $r->objects([ ($r->retrieve_all_sorted_by("score"))[-5..-1] ]);
}
Our action is called as a class method with the Maypole request object.
these five beers.
We'll look more at how to put together actions in the
-L<StandardTemplates.pod> chapter and our case studies.
+L<Standard Templates and Actions|Maypole::Manual::StandardTemplates>
+chapter and our case studies.
=head2 What Maypole wants from a model
=head2 Building your own model class
+
+=head2 Links
+
+L<Contents|Maypole::Manual>,
+Next L<Maypole View Classes|Maypole::Manual::View>,
+Previous L<Introduction to Maypole|Maypole::Manual::About>
It doesn't work.
-B<Solution>: It doesn't work because of the timing of the module
-loading. C<use Beer::Beer> will try to set up the C<has_a> relationships
+B<Solution>: It doesn't work because of the timing of the module loading.
+C<use BeerDB::Beer> will try to set up the C<has_a> relationships
at compile time, when the database tables haven't even been set up,
since they're set up by
You're seeing bizarre problems with Maypole output, and you want to test it in
some place outside of the whole Apache/mod_perl/HTTP/Internet/browser circus.
-B<Solution>: Use the C<Maypole::CLI> module to go directly from a URL to
+B<Solution>: Use the L<Maypole::CLI> module to go directly from a URL to
standard output, bypassing Apache and the network altogether.
-C<Maypole::CLI> is not a standalone front-end, but to allow you to debug your
+L<Maypole::CLI> is not a standalone front-end, but to allow you to debug your
applications without having to change the front-end they use, it temporarily
"borgs" an application. If you run it from the command line, you're expected
to use it like so:
test suites for your application. See the Maypole tests themselves or
the documentation to C<Maypole::CLI> for examples of this.
+Don't forget also to turn on debugging output in your application:
+
+ package BeerDB;
+ use strict;
+ use warnings;
+ use Maypole::Application qw(-Debug);
+
=head3 Changing how URLs are parsed
You don't like the way Maypole URLs look, and want something that either
fits in with the rest of your site or hides the internal workings of the
system.
-C<Solution>: So far we've been using the C</table/action/id/args> form
+B<Solution>: So far we've been using the C</table/action/id/args> form
of a URL as though it was "the Maypole way"; well, there is no Maypole
way. Maypole is just a framework and absolutely everything about it is
overridable.
If we want to provide our own URL handling, the method to override in
the driver class is C<parse_path>. This is responsible for taking
-C<$r-E<gt>{path}> and filling the C<table>, C<action> and C<args> slots
+C<$r-E<gt>path> and filling the C<table>, C<action> and C<args> slots
of the request object. Normally it does this just by splitting the path
-on C</>s, but you can do it any way you want, including getting the
-information from C<POST> form parameters or session variables.
+on 'C</>' characters, but you can do it any way you want, including
+getting the information from C<POST> form parameters or session variables.
For instance, suppose we want our URLs to be of the form
C<ProductDisplay.html?id=123>, we could provide a C<parse_path> method
sub parse_path {
my $r = shift;
- $r->{path} ||= "ProductList.html";
- ($r->{table}, $r->{action}) =
- ($r->{path} =~ /^(.*?)([A-Z]\w+)\.html/);
- $r->{table} = lc $r->{table};
- $r->{action} = lc $r->{action};
- my %query = $r->{ar}->args;
- $self->{args} = [ $query{id} ];
+ $r->path("ProductList.html") unless $r->path;
+ ($r->path =~ /^(.*?)([A-Z]\w+)\.html/);
+ $r->table(lc $1);
+ $r->action(lc $2);
+ my %query = $r->ar->args;
+ $self->args([ $query{id} ]);
}
This takes the path, which already has the query parameters stripped off
lower-cases them, and then grabs the C<id> from the query. Later methods
will confirm whether or not these tables and actions exist.
-See L<BuySpy.pod> for another example of custom URL processing.
+See the L<iBuySpy Portal|Maypole::Manual::BuySpy> for another
+example of custom URL processing.
=head3 Maypole for mobile devices
sub get_template_root {
my $r = shift;
- my $browser = $r->{ar}->headers_in->get('User-Agent');
+ my $browser = $r->headers_in->get('User-Agent');
if ($browser =~ /mobile|palm|nokia/i) {
"/home/myapp/templates/mobile";
} else {
=head2 Content display hacks
These hacks deal primarily with the presentation of data to the user,
-modifying the C<view> template or changing the way that the results of
+modifying the F<view> template or changing the way that the results of
particular actions are displayed.
=head3 Null Action
If, on the other hand, you want to display some data, and what you're
essentially doing is a variant of the C<view> action, then you need to
-ensure that you have an exported action, as described in
-L<StandardTemplates.pod>:
+ensure that you have an exported action, as described in the
+L<templates and actions|Maypole::Manual::StandardTemplates/"C<view> and C<edit>">
+chapter:
sub my_view :Exported { }
objects in the C<objects> slot and changing the C<template> to the
one we wanted to go to.
-In this example from L<Flox.pod>, we've just performed an C<accept>
-method on a C<Flox::Invitation> object and we want to go back to viewing
-a user's page.
+In this example from L<Flox|Maypole::Manual::Flox>, we've just
+performed an C<accept> method on a C<Flox::Invitation> object and we
+want to go back to viewing a user's page.
sub accept :Exported {
my ($self, $r) = @_;
my $invitation = $r->objects->[0];
# [... do stuff to $invitation ...]
- $r->{objects} = [$r->{user}];
- $r->{model_class} = "Flox::User";
- $r->{template} = "view";
+ $r->objects([$r->user]);
+ $r->model_class("Flox::User");
+ $r->template("view");
}
This hack is so common that it's expected that there'll be a neater
<line> <sup>A</sup>Lay<sup>Dm</sup>la <sup>Bb</sup> </line>
<line> <sup>C</sup>Got me on my <sup>Dm</sup>knees </line>
...
-
+
I store the title, artist and key in the database, as well as an "xml"
field which contains the whole song as XML.
To load the songs into the database, I can C<use> the driver class for
my application, since that's a handy way of setting up the database classes
-we're going to need to use. Then the handy C<XML::TreeBuilder> will handle
+we're going to need to use. Then the handy L<XML::TreeBuilder> will handle
the XML parsing for us:
use Songbook;
}
Now we need to set up the custom display for each song; thankfully, with
-the C<Template::Plugin::XSLT> module, this is as simple as putting the
+the L<Template::Plugin::XSLT> module, this is as simple as putting the
following into F<templates/song/view>:
[%
sub view_picture :Exported {
my ($self, $r) = @_;
- my $user = $r->{objects}->[0];
- $r->{content_type} = $user->photo_type;
- $r->{output} = $user->photo;
+ my $user = $r->objects->[0];
+ $r->content_type($user->photo_type);
+ $r->output($user->photo);
}
Of course, the file doesn't necessarily need to be in the database
itself; if your file is stored in the filesystem, but you have a file
name or some other pointer in the database, you can still arrange for
-the data to be fetched and inserted into C<$r-E<gt>{output}>.
+the data to be fetched and inserted into C<$r-E<gt>output>.
=head3 REST
You want to provide a programmatic interface to your Maypole site.
B<Solution>: The best way to do this is with C<REST>, which uses a
-descriptive URL to encode the request. For instance, in L<Flox.pod> we
+descriptive URL to encode the request. For instance, in
+L<Flox|Maypole::Manual::Flox> we
describe a social networking system. One neat thing you can do with
social networks is to use them for reputation tracking, and we can use
that information for spam detection. So if a message arrives from
use URI::Escape;
sub relationship_by_email :Exported {
my ($self, $r) = @_;
- my $email = uri_unescape($r->{args}[0]);
- $r->{content_type} = "text/plain";
+ my $email = uri_unescape($r->args->[0]);
+ $r->content_type("text/plain");
my $user;
unless (($user) = Flox::User->search(email => $email)) {
- $r->{content} = "0\n"; return;
+ $r->content("0\n"); return;
}
- if ($r->{user}->is_friend($user)) { $r->{content} = "2\n"; return; };
- $r->{content} = "1\n"; return;
+ if ($r->user->is_friend($user)) { $r->contenti("2\n"); return; };
+ $r->content("1\n"); return;
}
=head3 Component-based Pages
objects. You want to include the output of one Maypole request call while
building up another.
-B<Solution>: Use C<Maypole::Component>. By inheriting from this, you can
-call the C<component> method on the Maypole request object to make a
-"sub-request". For instance, if you have a template
+B<Solution>: Use L<Maypole::Plugin::Component>. By inheriting like this:
+
+ package BeerDB;
+ use Maypole::Application qw(Component);
+
+you can call the C<component> method on the Maypole request object to
+make a "sub-request". For instance, if you have a template
<DIV class="latestnews">
[% request.component("/news/latest_comp") %]
sub error {
my ($r, $message) = @_;
- $r->{template} = "error";
- $r->{template_args}{error} = $message;
+ $r->template("error");
+ $r->template_args->{error} = $message;
return OK;
}
my ($self, $r) = @_;
$r->get_user;
return $r->error("You do not exist. Go away.")
- if $r->{user} and $r->{user}->status ne "real";
+ if $r->user and $r->user->status ne "real";
...
}
C<messages> template variable onto the request:
if ((localtime)[6] == 1) {
- push @{$r->{template_args}{messages}}, "Warning: Today is Monday";
+ push @{$r->template_args->{messages}}, "Warning: Today is Monday";
}
Now F<custom/messages> can contain:
switcheroo to ensure that you're displaying a page that has the messages box in
it.
-=head2 Authentication hacks
+=head2 Authentication and Authorization hacks
The next series of hacks deals with providing the concept of a "user" for
a site, and what you do with one when you've got one.
You need the concept of a "current user".
B<Solution>: Use something like
-C<Maypole::Authentication::UserSessionCookie> to authenticate a user
-against a user class and store a current user object in the request
-object.
+L<Maypole::Plugin::Authentication::UserSessionCookie> to authenticate
+a user against a user class and store a current user object in the
+request object.
C<UserSessionCookie> provides the C<get_user> method which tries to get
a user object, either based on the cookie for an already authenticated
-session, or by comparing C<username> and C<password> form parameters
+session, or by comparing C<user> and C<password> form parameters
against a C<user> table in the database. Its behaviour is highly
-customizable, so see the documentation, or the authentication paper at
-C<http://maypole.simon-cozens.org/docs/authentication.html> for examples.
+customizable and described in its documentation.
=head3 Pass-through login
You want to intercept a request from a non-logged-in user and have
them log in before sending them on their way to wherever they were
-originally going.
+originally going. Override C<Maypole::authenticate> in your driver
+class, something like this:
B<Solution>:
+ use Maypole::Constants; # Otherwise it will silently fail!
+
sub authenticate {
my ($self, $r) = @_;
$r->get_user;
- return OK if $r->{user};
+ return OK if $r->user;
# Force them to the login page.
- $r->{template} = "login";
+ $r->template("login");
return OK;
}
[% IF login_error %]
<FONT COLOR="#FF0000"> [% login_error %] </FONT>
[% END %]
- <FORM ACTION="/[% request.path%]" METHOD="post">
+ <FORM ACTION="[% base ; '/' ; request.path %]" METHOD="post">
Username:
- <INPUT TYPE="text" NAME="[% config.auth.user_field || "user" %]"> <BR>
+ <INPUT TYPE="text" NAME="[% config.auth.user_field || "user" %]"><BR>
Password: <INPUT TYPE="password" NAME="password"> <BR>
<INPUT TYPE="submit">
</FORM>
</DIV>
+ [% INCLUDE footer %]
Notice that this request gets C<POST>ed back to wherever it came from, using
C<request.path>. This is because if the user submits correct credentials,
again and taking the authentication cookie away from them, sending
them back to the front page as an unprivileged user.
-B<Solution>: This action, on the user class, is probably overkill, but
-it does the job:
-
- sub logout :Exported {
- my ($class, $r) = @_;
- # Remove the user from the request object
- my $user = delete $r->{user};
- # Destroy the session
- tied(%{$r->{session}})->delete;
- # Send a new cookie which expires the previous one
- my $cookie = Apache::Cookie->new($r->{ar},
- -name => $r->config->{auth}{cookie_name},
- -value => undef,
- -path => "/"
- -expires => "-10m"
- );
- $cookie->bake();
- # Template switcheroo
- $r->template("frontpage");
- }
+B<Solution>: Just call the C<logout> method of
+C<Maypole::Plugin::Authentication::UserSessionCookie>. You may also want
+to use the template switcheroo hack to send them back to the frontpage.
-=head3 Multi-level Authentication
+=head3 Multi-level Authorization
You have both a global site access policy (for instance, requiring a
user to be logged in except for certain pages) and a policy for
You don't know whether to override the global C<authenticate> method or
provide one for each class.
-B<Solution>: Do both. Have a global C<authenticate> method which calls
-a C<sub_authenticate> method based on the class:
+B<Solution>: Do both.
+Maypole checks whether there is an C<authenticate> method for the model
+class (e.g. BeerDB::Beer) and if so calls that. If there's no such
+method, it calls the default global C<authenticate> method in C<Maypole>,
+which always succeeds. You can override the global method as we saw
+above, and you can provide methods in the model classes.
- sub authenticate {
- ...
- if ($r->{user}) {
- return $r->model_class->sub_authenticate($r)
- if $r->model_class && $r->model_class->can("sub_authenticate");
- return OK;
- }
- ...
+To use per-table access control you can just add methods to your model
+subclasses that specify individual policies, perhaps like this:
+
+ sub authenticate { # Ensure we can only create, reject or accept
+ my ($self, $r) = @_;
+ return OK if $r->action =~ /^(issue|accept|reject|do_edit)$/;
+ return; # fail if any other action
}
-And now your C<sub_authenticate> methods can specify the policy for
-each table:
+If you define a method like this, the global C<authenticate> method will
+not be called, so if you want it to be called you need to do so
+explicitly:
- sub sub_authenticate { # Ensure we can only create, reject or accept
+ sub authenticate { # Ensure we can only create, reject or accept
my ($self, $r) = @_;
- return OK if $r->{action} =~ /^(issue|accept|reject|do_edit)$/;
- return;
+ return unless $r->authenticate($r) == OK; # fail if not logged in
+ # now it's safe to use $r->user
+ return OK if $r->action =~ /^(accept|reject)$/
+ or ($r->user eq 'fred' and $r->action =~ /^(issue|do_edit)$/);
+ return; # fail if any other action
}
=head2 Creating and editing hacks
going to display on the site, but you don't want them to stick images in
it, launch cross-site scripting attacks or otherwise insert messy HTML.
-B<Solution>: Use the C<CGI::Untaint::html> module to sanitize the HTML
-on input. C<CGI::Untaint::html> uses C<HTML::Sanitizer> to ensure that
+B<Solution>: Use the L<CGI::Untaint::html> module to sanitize the HTML
+on input. C<CGI::Untaint::html> uses L<HTML::Sanitizer> to ensure that
tags are properly closed and can restrict the use of certain tags and
attributes to a pre-defined list.
B<Solution>: Munge the contents of C< $r-E<gt>params > before jumping
to the original C<do_edit> routine. For instance, in this method,
-we use a C<Net::Amazon> object to fill in some fields of a database row based
-on an ISBN:
+we use a L<Net::Amazon> object to fill in some fields of a database row
+based on an ISBN:
sub create_from_isbn :Exported {
my ($self, $r) = @_;
- my $response = $ua->search(asin => $r->{params}{isbn});
+ my $response = $ua->search(asin => $r->params->{isbn});
my ($prop) = $response->properties;
# Rewrite the CGI parameters with the ones from Amazon
- @{$r->{params}{qw(title publisher author year)} =
+ @{$r->params->{qw(title publisher author year)} =
($prop->title,
$prop->publisher,
(join "/", $prop->authors()),
The request will carry on as though it were a normal C<do_edit> POST, but
with the additional fields we have provided.
+You might also want to add a template switcheroo so the user can verify
+the details you imported.
=head3 Catching errors in a form
keyed to the erroneous field. The hash is put into the template as
C<errors>, and we process the same F<edit> template again:
- $r->{template_args}{errors} = \%errors;
- $r->{template} = "edit";
+ $r->template_args->{errors} = \%errors;
+ $r->template("edit");
This throws us back to the form, and so the form's template should take
note of the errors, like so:
sub upload_picture : Exported {}
-And here's the template:
+And here's the F<custom/upload_picture> template:
<FORM action="/user/do_upload" enctype="multipart/form-data" method="POST">
(Although you'll probably want a bit more HTML around it than that.)
Now we need to write the C<do_upload> action. At this point we have to get a
-little friendly with the front-end system. If we're using C<Apache::Request>,
+little friendly with the front-end system. If we're using L<Apache::Request>,
then the C<upload> method of the C<Apache::Request> object (which
-C<Apache::MVC> helpfully stores in C<$r-E<gt>{ar}>) will work for us:
+L<Apache::MVC> helpfully stores in C<$r-E<gt>{ar}>) will work for us:
sub do_upload :Exported {
my ($class, $r) = @_;
- my $user = $r->{user};
- my $upload = $r->{ar}->upload("picture");
+ my $user = $r->user;
+ my $upload = $r->ar->upload("picture");
-This returns a C<Apache::Upload> object, which we can query for its
+This returns a L<Apache::Upload> object, which we can query for its
content type and a file handle from which we can read the data. It's
also worth checking the image isn't going to be too massive before we
try reading it and running out of memory, and that the content type is
my $fh = $upload->fh;
my $image = do { local $/; <$fh> };
+Don't forget C<binmode()> in there if you're on a platform that needs it.
Now we can store the content type and data into our database, store it
into a file, or whatever:
- $r->{user}->photo_type($ct);
- $r->{user}->photo($image);
+ $r->user->photo_type($ct);
+ $r->user->photo($image);
}
And finally, we use our familiar template switcheroo hack to get back to
a useful page:
$r->objects([ $user ]);
- $r->{template} = "view";
+ $r->template("view");
}
Now, as we've mentioned, this only works because we're getting familiar with
-C<Apache::Request> and its C<Apache::Upload> objects. If we're planning to use
-C<CGI::Maypole> instead, or want to write our application in a generic way so
-that it'll work regardless of front-end, then we need to replace the C<upload>
-call with an equivalent which uses the C<CGI> module to get the upload data.
-This is convoluted and horrific and we're not going to show it here, but it's
-possible.
+C<Apache::Request> and its C<Apache::Upload> objects. If we're using
+L<CGI::Maypole> instead, we can write the action in a similar style:
+
+ sub do_upload :Exported {
+ my ($class, $r) = @_;
+ my $user = $r->user;
+ my $cgi = $r->cgi;
+ if ($cgi->upload == 1) { # if there was one file uploaded
+ my $filename = $cgi->param('picture');
+ my $ct = $cgi->upload_info($filename, 'mime');
+ return $r->error("Unknown image file type $ct")
+ if $ct !~ m{image/(jpeg|gif|png)};
+ return $r->error("File too big! Maximum size is ".MAX_IMAGE_SIZE)
+ if $cgi->upload_info($filename, 'size') > MAX_IMAGE_SIZE;
+ my $fh = $cgi->upload($filename);
+ my $image = do { local $/; <$fh> };
+ $r->user->photo_type($ct);
+ $r->user->photo($image);
+ }
+
+ $r->objects([ $user ]);
+ $r->template("view");
+ }
+
+It's easy to adapt this to upload multiple files if desired.
+You will also need to enable uploads in your driver initialization,
+with the slightly confusing statement:
+
+ $CGI::Simple::DISABLE_UPLOADS = 0; # enable uploads
Combine with the "Displaying pictures" hack above for a happy time.
+
+=head2 Links
+
+L<Contents|Maypole::Manual>,
+Next L<Flox|Maypole::Manual::Flox>,
+Previous L<The Beer Database, Twice|Maypole::Manual::Beer>
+
+
=head1 Maypole's Standard Templates and Actions
-As we saw in our CRUD example, Maypole does all it can to make your life
+As we saw in our Create-Read-Update-Delete (CRUD) example,
+Maypole does all it can to make your life
easier; this inclues providing a set of default actions and
factory-supplied templates. These are written in such a generic way,
making extensive use of class metadata, that they are more or less
Once we have an understanding of what Maypole does for us automatically,
we can begin to customize and create our own templates and actions.
+Although the standard templates can be applied in many situations,
+they're really provided just as examples,
+as a starting point to create your own templates to suit your needs.
+The goal of templating is to keep templates simple so the presentation
+can be changed easily when you desire.
+We're not trying to build a single set of reusable templates that cover
+every possible situation.
+
=head2 The standard actions
+Remember that actions are just subroutines in the model classes with an
+I<Exported> attribute.
A simple, uncustomized Maypole model class, such as one of the classes
in the beer database application, provides the following default actions
- that is, provides access to the following URLs:
=item C</[table]/do_edit/[id]>
+When called with an ID, the C<do_edit> action provides row editing.
+
=item C</[table]/do_edit/>
-This provides both editing and row creation facilities.
+When called without an ID, the C<do_edit> action provides row creation.
=item C</[table]/delete/id>
taking the first argument and turning it into an object is such a common
action, it is handled directly by the model class's C<process> method.
Similarly, the default template name provided by the C<process> method
-is the name of the acction, and so will be C<view> or C<edit>
+is the name of the action, and so will be C<view> or C<edit>
accordingly.
So the code required to make these two actions work turns out to be:
"actions" are purely concerned with displaying a record, and don't need
to do any "acting". Remember that the "edit" method doesn't actually do
any editing - this is provided by C<do_edit>; it is just another view of
-the data, albeit once which allows the data to be modified later. These
+the data, albeit one which allows the data to be modified later. These
two methods don't need to modify the row in any way, they don't need to
do anything clever. They just are.
sub do_edit :Exported {
my ($self, $r) = @_;
- my $h = CGI::Untaint->new(%{$r->{params}});
+ my $h = CGI::Untaint->new(%{$r->params});
my ($obj) = @{$r->objects || []};
if ($obj) {
# We have something to edit
$obj = $self->create_from_cgi($h);
}
-The C<CDBI> model uses L<Class::DBI::FromCGI> to turn C<POST> parameters
-into database table data. This in turn uses C<CGI::Untaint> to ensure
+The C<CDBI> model uses the C<update_from_cgi> and C<create_from_cgi>
+methods of L<Class::DBI::FromCGI> to turn C<POST> parameters
+into database table data. This in turn uses L<CGI::Untaint> to ensure
that the data coming in is suitable for the table. If you're using the
default C<CDBI> model, then, you're going to need to set up your tables
in a way that makes C<FromCGI> happy.
-=over
+The data is untainted, and any errors are collected into a hash which is
+passed to the template. We also pass back in the parameters, so that the
+template can re-fill the form fields with the original values. The user
+is then sent back to the C<edit> template.
-=item Digression on C<Class::DBI::FromCGI>
+ if (my %errors = $obj->cgi_update_errors) {
+ # Set it up as it was:
+ $r->template_args->{cgi_params} = $r->params;
+ $r->template_args->{errors} = \%errors;
+ $r->template("edit");
+ }
+
+Otherwise, the user is taken back to viewing the new object:
+
+ } else {
+ $r->template("view");
+ }
+ $r->objects([ $obj ]);
+
+Notice that this does use hard-coded names for the templates to go to next.
+Feel free to override this in your subclasses:
+
+ sub do_edit :Exported {
+ my ($class, $r) = @_;
+ $class->SUPER::do_edit($r);
+ $r->template("my_edit");
+ }
+
+=head3 Digression on C<Class::DBI::FromCGI>
C<CGI::Untaint> is a mechanism for testing that incoming form data
conforms to various properties. For instance, given a C<CGI::Untaint>
key. If not, you probably want C<printable>, but you probably know what
you're doing anyway.
-=back
-
-The data is untainted, and any errors are collected into a hash which is
-passed to the template. We also pass back in the parameters, so that the
-template can re-fill the form fields with the original values. The user
-is then sent back to the C<edit> template.
-
- if (my %errors = $obj->cgi_update_errors) {
- # Set it up as it was:
- $r->{template_args}{cgi_params} = $r->{params};
- $r->{template_args}{errors} = \%errors;
- $r->{template} = "edit";
- }
-
-Otherwise, the user is taken back to viewing the new object:
-
- } else {
- $r->{template} = "view";
- }
- $r->objects([ $obj ]);
-
-Notice that this does use hard-coded names for the templates to go to next.
-Feel free to override this in your subclasses:
-
- sub do_edit :Exported {
- my ($class, $r) = @_;
- $class->SUPER::do_edit($r);
- $r->template("my_edit");
- }
-
=head3 delete
The delete method takes a number of arguments and deletes those rows from the
However, things are slightly complicated by paging and ordering by
column; the default implementation also provides a C<Class::DBI::Pager>
object to the templates and uses that to retrieve the appropriate bit of
-the data, as specified by the C<page> URL query parameter. See the F<pager>
-template below.
+the data, as specified by the C<page> URL query parameter. See the
+L<"pager"> template below.
=head3 search
=head3 F<edit>
-The F<edit> template is pretty much the same as F<view>, but it uses the
+The F<edit> template is pretty much the same as F<view>, but it uses
+L<Class::DBI::AsForm>'s
C<to_field> method on each column of an object to return a C<HTML::Element>
object representing a form element to edit that property. These elements
-are then rendered to HTML with C<as_HTML>. It expects to see a list of
+are then rendered to HTML with C<as_HTML> or to XHTML with C<as_XML>.
+It expects to see a list of
editing errors, if any, in the C<errors> template variable:
FOR col = classmetadata.columns;
<h2> Listing of all [% classmetadata.plural %]</h2>
[% END %]
-=head1 Customizing Generic CRUD Applications
+=head3 F<pager>
+
+The pager template controls the list of pages at the bottom (by default)
+of the list and search views. It expects a C<pager> template argument
+which responds to the L<Data::Page> interface.
+There's a description of how it works in
+L<the Template Toolkit section|Maypole::Manual::View/"The Template Toolkit">
+of the View chapter.
+
+=head3 F<macros>
+
+The F<macros> template is included at the start of most other templates
+and makes some generally-useful template macros available:
+
+=over
+
+=item C<link(table, command, additional, label)>
+
+This makes an HTML link pointing to C</base/table/command/additional>
+labelled by the text in I<label>. C<base> is the template variable that
+contains the base URL of this application.
+
+=item C<maybe_link_view(object)>
+
+C<maybe_link_view> takes something returned from the database - either
+some ordinary data, or an object in a related class expanded by a
+has-a relationship. If it is an object, it constructs a link to the view
+command for that object. Otherwise, it just displays the data.
+
+=item C<display_line(object)>
+
+C<display_line> is used in the list template to display a row from the
+database, by iterating over the columns and displaying the data for each
+column. It misses out the C<id> column by default, and magically
+URLifies columns called C<url>. This may be considered too much magic
+for some.
+
+=item C<button(object, action)>
+
+This is a simple button that is submitted to C</base/table/action/id>,
+where C<table> and C<id> are those belonging to the database row C<object>.
+The button is labelled with the name of the action.
+You can see buttons on many pages, including lists.
+
+=item C<view_related(object)>
+
+This takes an object, and looks up its C<related_accessors>; this gives
+a list of accessor methods that can be called to get a list of related
+objects. It then displays a title for that accessor, (e.g. "Beers" for a
+C<brewery.beers>) calls the accessor, and displays a list of the results.
+You can see it in use at the bottom of the standard view pages.
+
+=back
+
+=begin TODO
+
+=head2 Customizing Generic CRUD Applications
+
+=end TODO
+
+=head2 Links
+
+L<Contents|Maypole::Manual>,
+Next L<The Request Workflow|Maypole::Manual::Workflow>,
+Previous L<Maypole View Classes|Maypole::Manual::View>,
+
of filling in the template and coming up with the output.
You can choose whatever Maypole view class you want, but the default
-view class is C<Maypole::View::TT>, and it feeds its data and templates
+view class is L<Maypole::View::TT>, and it feeds its data and templates
to a module called the Template Toolkit.
=head2 The Template Toolkit
generic templating system. It provides its own little formatting language
which supports loops, conditionals, hash and array dereferences and
method calls, macro processing and a plug-in system to connect it to
-external Perl modules. There are several good introductions to the
+external Perl modules.
+Its homepage is C<http://www.template-toolkit.org/>.
+There are several good introductions to the
Template Toolkit available: you should have one installed as
L<Template::Tutorial::Datafile>; there's one at
L<http://www.perl.com/pub/a/2003/07/15/nocode.html>, and of course
there's the "Badger Book" - I<The Perl Template Toolkit>, by Andy et al.
+C<http://www.oreilly.com/catalog/perltt/index.html>
We'll present a brief introduction here by deconstructing some of the
templates used by Maypole applications. For more deconstruction, see
-L<StandardTemplates.pod>, which is an entire chapter dealing with the
+L<Standard Templates and Actions|Maypole::Manual::StandardTemplates>,
+which is an entire chapter dealing with the
factory supplied templates.
-Here's the template which is called for the front page of the standard
+Here's a template that could be called for the front page of the example
beer database application, C<custom/frontpage>.
[% INCLUDE header %]
The first thing to note about this is that everything outside of the
Template Toolkit tags (C<[%> and C<%]>) is output verbatim. That is,
-you're guaranteed to see
+somewhere in the output you're guaranteed to see
<h2> The beer database </h2>
<TABLE BORDER="0" ALIGN="center" WIDTH="70%">
-in the output somewhere. Inside the tags, magic happens. The first piece
+Inside the tags, magic happens. The first piece
of magic is the C<[% INCLUDE header %]> directive. This goes away and
finds a file called F<header> - don't worry about how it finds that yet,
we'll come to that later on - and processes the file's contents as
We're seeing a lot of things here at once. C<config> is where we should
start looking. This is a template variable, which is what templates are
all about - templating means getting data from somewhere outside and
-presenting it to the user in a useful way, and the C<config> hash is a
-prime example of data that we want to use. It's actually the hash of
-configuration parameters for this Maypole application, and one of the
-keys in that hash is C<display_tables>, the database tables that we're
-allowed to play with. In the application, we probably said something
-like
+presenting it to the user in a useful way, and C<config> is a
+prime example of data that we want to use. It's actually an object
+containing configuration parameters for this Maypole application, and
+one of the methods is C<display_tables>, which returns a list of the
+database tables that we're supposed to show. In the application, we
+probably said something like
- BeerDB->config->{display_tables} = [qw[beer brewery pub style]];
+ BeerDB->config->display_tables([qw[beer brewery pub style]]);
This stores the four values - C<beer>, C<brewery>, C<pub> and C<style> -
-in an array, which is placed in the config hash under the key
-C<display_tables>. Now we're getting them back again.
+in an array, which is placed in the config object using the
+accessor/mutator method C<display_tables>. Now we're getting them back
+again. Note that we're not going to show the handpump table.
The Template Toolkit's dot operator is a sort of do-the-right-thing
operator; we can say C<array.0> to get the first element of an array,
C<hash.key> to look up the C<key> key in a hash, and C<object.method> to
call C<method> on an object. So, for instance, if we said
-C<config.display_tables.2>, we'd look up the C<display_tables> key in
-the configuration hash and get our array back, then look up the 2nd
+C<config.display_tables.2>, we'd look up the C<display_tables> method in
+the configuration object and get our array back, then look up the 3rd
element and get C<pub>.
+Thing is, you don't have to care whether C<display_tables> is an object
+or a hash. You can pretend it's a hash if you want. The syntax is the
+same, and Template Toolkit knows the right thing to do.
The C<FOR> loop will repeat the code four times, setting our new
variable C<table> to the appropriate array element. This code:
ELSE;
SET args = "?page=" _ num;
SET label = "[" _ num _ "]";
- link(classmetadata.moniker, "list", args, label);
+ link(classmetadata.table, "list", args, label);
END;
END;
%]
functions - you can provide them some parameters and they'll run a little
sub-template based on them. The C<macros> file contains some handy macros
that I've found useful for constructing Maypole templates; again, these
-will be covered in full detail in L<StandardTemplates.pod>.
+will be covered in full detail in
+L<Standard Templates and Actions|Maypole::Manual::StandardTemplates>.
We're going to be displaying something like this:
Here we're manually constructing an array of numbers, using the range
operator (C<..>) to fill in all the numbers from the C<first_page> (1)
to the C<last_page> (4). The same dot operator is used to ask the C<pager>
-what its C<first_page> and C<last_page> is. Remember when we said
-C<config.display_tables>, we were looking up the C<display_tables> key
-in the C<config> hash? Well, this time we're not looking anything up in
-a hash. C<pager> is an object, and C<first_page> is a method. Thing is,
-you don't have to care. You can pretend it's a hash if you want. The
-syntax is the same, and Template Toolkit knows the right thing to do.
+object what its C<first_page> and C<last_page> are.
Now we're going to be executing this loop four times, once each for C<num>
being set to 1, 2, 3, and 4. At some point, we'll come across the page
C<classmetadata.table>. This macro takes four arguments, C<table>,
C<action>, C<args> and C<label>, and constructs a link of the form
- <A HREF="[% config.base_url %]/[% table %]/[% action %][% args %]">
+ <A HREF="[% base %]/[% table %]/[% action %][% args %]">
[% label %]
</A>
In our case, it'll be filled in like so:
- <A HREF="[% config.base_url %]/[% classmetadata.table %]/list?page=4">
+ <A HREF="[% base %]/[% classmetadata.table %]/list?page=4">
[ 4 ]
</A>
Where C<classmetadata.table> will actually be the name of the current
-table, and C<config.base_url> will be replaced by the appropriate URL for
+table, and C<base> will be replaced by the appropriate URL for
this application.
=head2 Locating Templates
When you configure a Maypole application, you can tell it the base
directory of your templates like so:
- BeerDB->config->{template_root} = "/var/www/beerdb/templates";
+ BeerDB->config->template_root("/var/www/beerdb/templates");
If you don't do this, most Maypole front-ends will use the current
-directory, which is generally what you want anyway. Off this directory,
+directory, which may be what you want anyway. Off this directory,
Maypole will look for a set of subdirectories.
For instance, I said we were in the middle of processing the front page
the template. As these are the building blocks of your pages, it's worth
looking at precisely what variables are available.
+=head3 objects
+
The most important variable is called C<objects>, and is a list of all
the objects that this page is going to deal with. For instance,
+if the URL is C<http://localhost/beerdb/beer/view/23>, then
in the template F</beer/view>, C<objects> will contain the C<BeerDB::Beer>
-object for the 23rd item in the database, while F</brewery/list> will
-fill C<objects> will all the breweries; or at least, all the breweries
-on the current page.
+object for the 23rd item in the database, while for the F</brewery/list>
+template, the view will fill C<objects> with all the breweries; or at
+least, all the breweries on the current page.
+
+=head3 breweries!
This variable is so important that to help design templates with it,
C<Maypole::View::TT> provides a helpful alias to it depending on
though, it's available in C<brewery>, since there's only one brewery to
be displayed.
+=head3 base
+
Additionally, you can get the base URL for the application from the
C<base> template variable; this allows you to construct links, as we
saw earlier:
<A HREF="[% base %]/brewery/edit/[% brewery.id %]">Edit this brewery</A>
+=head3 config
+
You can also get at the rest of the configuration for the site with the
-C<config> variable as we saw above, and the entire request object in
+C<config> variable as we saw above.
+
+=head3 request
+
+The entire request object is made available in
C<request>, should you really need to poke at it. (I've only found this
useful when working with authentication modules which stash a current user
object in C<request.user>.)
+=head3 classmetadata
+
To allow the construction of the "generic" templates which live in
F<factory>, Maypole also passes in a hash called C<classmetadata>,
which contains all sorts of useful information about the class under
This is the name of the table that is represented by the class.
-=item C<class>
+=item C<name>
This is the Perl's idea of the class; you don't need this unless you're
doing really tricky things.
=item C<moniker>
This is a more human-readable version of the table name, that can be
-used for display.
+used for display. "brewery" for example.
=item C<plural>
=item C<columns>
-The list of columns for display; see the section "Customizing Generic
-CRUD Applications" in L<StandardTemplates.pod>.
+The list of columns for display; see the
+L<hard way|Maypole::Manual::Beer/"The hard way"> section in the Beer
+Database chapter.
+
+=item C<list_columns>
+
+As for C<columns>, but these are the columns to be displayed on a
+F<list> page.
=item C<colnames>
This is a hash mapping the database's name for a column to a more
-human-readable name. Again, see "Customizing Generic CRUD Applications>.
+human-readable name. Again, see "Customizing Generic CRUD Applications".
=item C<cgi>
a C<HTML::Element> suitable for entering data into a new instance of
that class. That is, for the C<beer> table, C<classmetadata.cgi.style>
should be a C<HTML::Element> object containing a drop-down list of
-beer styles. This is explained in L<StandardTemplates.pod>.
-
-=item C<description>
+beer styles.
-This is the human-readable description provided by a class.
+TODO =item C<description>
+TODO
+TODO This is the human-readable description provided by a class.
=item C<related_accessors>
=back
+=head3 Additional variables and overrides
+
+You can pass additional data to templates by creating new variables.
+You'd typically do this in your view class.
+Just add the name of your template variable as a key to the
+C<template_args> hash in the request object, and supply its value:
+
+ $r->template_args->{your_variable_name} = 'some_value';
+
+You can also override the value of any of the standard variables by
+giving their name as the key.
+
=head2 Other view classes
Please note that these template variables, C<config>, C<classmetadata>,
use base Maypole::Application;
...
BeerDB->setup("dbi:SQLite:t/beerdb.db");
- BeerDB->config->{uri_base} = "http://localhost/beerdb/";
- BeerDB->config->{rows_per_page} = 10;
- BeerDB->config->{view} = "Maypole::View::Mason";
+ BeerDB->config->uri_base(http://localhost/beerdb/");
+ BeerDB->config->rows_per_page(10);
+ BeerDB->config->view("Maypole::View::Mason");
Where do these alternate view classes come from? Gentle reader, they
come from B<you>.
here, but try. You'd like to use it with Maypole, which means writing your
own view class. How is it done?
-We'll demonstrate by implementing a view class for C<HTML::Mason>,
+We'll demonstrate by implementing a view class for L<HTML::Mason>,
although no value judgement is implied. C<HTML::Mason> is a templating
system which embeds pure Perl code inside its magic tags. The good side
of this is that it can get into hash references and objects, and so
it wants already at its disposal through CGI parameters and the like, so
we have to fiddle a bit to get these variables into our template.
-The key to building view classes is C<Maypole::View::Base>. This is the
+The key to building view classes is L<Maypole::View::Base>. This is the
base class that you're going to inherit from and, to be honest, it does
pretty much everything you need. It provides a method called C<vars>
which returns a hash of all the template variables described above, so
The module will do the right thing for us if we agree to provide a
method called C<template>. This is responsible for taking the Maypole
-request object (of which more later) and putting the appropriate output
-either into C<$r-E<gt>{output}> or C<$r-E<gt>{error}>, depending, of
+request object C<$r> (of which more later) and putting the appropriate output
+either into C<$r-E<gt>output> or C<$r-E<gt>error>, depending, of
course, whether things are OK or whether we got an error.
Thankfully, C<HTML::Mason> makes things really easy for us. We B<can>
was an error, then Mason will have produced some suitable output, so we can
pretend that everything's OK anyway.)
- $r->{output} = $output;
+ $r->output($output);
return 1;
And that's all we need to do. Barely twenty lines of code for the finished
product. Wasn't that easy? Don't you feel inspired to write Maypole view
classes for your favourite templating language? Well, don't let me stop you!
Patches are always welcome!
+
+=head2 Links
+
+L<Contents|Maypole::Manual>,
+Next L<Standard Templates and Actions|Maypole::Manual::StandardTemplates>,
+Previous L<Maypole Model Classes|Maypole::Manual::Model>,
=pod
-=head1 NAME
+=head1 Maypole's Request Workflow
-Maypole::Manual::Workflow - Describes the progress of a request through Maypole
+This chapter describes the progress of a request through Maypole.
-=head1 SYNOPSIS
+An application based on C<Maypole> provides an Apache or CGI handler,
+and eventually delivers a page. This document explains how that happens,
+and how to influence it. We'll use the C<BeerDB> project as our example.
+Here's a diagram that gives an overview:
config $h
|
|
$r->view_object->process($r)
-
-=head1 DESCRIPTION
-
-An application based on C<Maypole> will provide an Apache handler,
-and eventually deliver a page. This document explains how that happens,
-and how to influence it. We'll use the C<BeerDB> project as our example.
-
=head2 Initialize class
-When the first request comes in, the class will call its own
-C<init> method. This creates a new view object, sets up inheritance
-relationships between the model classes and their parent, and so on.
+When the first request comes in, the application class will call its own
+C<init> method, inherited from L<Maypole>.
+This creates a new view object.
=head2 Construction
We then look for an appropriate C<authenticate> method to call; first
it will try calling the C<authenticate> method of the model class, or,
if that does not exist, the C<authenticate> method on itself. By
-default, this allows access to everyone for everything. Similarly, this
-should return an Apache status code.
+default, this allows access to everyone for everything.
+Your C<authenticate> methods must return an Apache status code: C<OK> or
+C<DECLINED>. These codes are defined by the L<Maypole::Constants>
+module, which is automatically used by your application.
=head2 Add any additional data to the request
-The open-ended C<additional_data> method allows any additional fiddling
+You can write an C<additional_data> method to do any additional fiddling
with the request object before it is despatched. Specifically, it allows
you to add to the C<template_args> slot, which is a hash of arguments to
-be added to the template.
+be added to the template, like this:
+
+ sub additional_data {
+ my $self = shift;
+ $self->{template_args}{answer} = 42;
+ }
+
+which adds a new template variable C<answer> with the value 42.
=head2 Ask model for widget set
any work it needs for the given command, and populate the C<objects> and
C<template> slots of the request.
-=head2 Ask view to process template
-
-Now the view class has its C<process> method called, finds the
-appropriate templates, passes the C<objects> and any additional data to
-the template, and pushes the output to the web server.
-
-We will go into more detail about these last two phases.
-
-=head1 Model class processing
-
The model's C<process> method is usually a thin wrapper around the
action that we have selected. It sets the template name to the name of
the action, fills C<objects> with an object of that class whose ID comes
required, including modifying the list of objects to be passed to the
template, or the name of the template to be called.
-=head1 Template class processing
+=head2 Ask view to process template
+
+Now the view class has its C<process> method called. It finds the
+appropriate templates and calls the L<Template Toolkit|Template>
+processor.
-Finally, the template processor is handed the objects, the template
+The template processor is handed the objects, the template
name, and various other bits and pieces, and tries to find the right
template. It does this by looking first for C</beer/foo>: that is, a
specific template appropriate to the class. Next, it looks at
C</factory/foo>, one of the default templates that came with
C<Maypole>.
-=head2 Default template arguments
-
-The following things are passed to the Template Toolkit template by
-default:
-
-=over 3
-
-=item request
+The view puts the template's output in the C<$r-E<gt>{output}> slot. The
+application's C<handler> method calls the C<send_output> method to push
+it to the web server.
-The whole C<Maypole> request object, for people getting really dirty
-with the templates.
-
-=item objects
-
-The objects handed to us by the model.
-
-=item base
-
-The base URL of the application.
-
-=item config
-
-The whole configuration hash for the application.
-
-=item classmetadata
-
-A hash consisting of:
-
-C<name> - The name of the model class for the request: e.g. C<BeerDB::Beer>.
-
-C<columns> - The names of the columns in this class.
-
-C<colnames> - A hash mapping between the database's idea of a column
-name and a human-readable equivalent. (C<abv> should be mapped to
-C<A.B.V.>, perhaps.)
-
-C<related_accessors> - A list of accessors which are not exactly fields
-in the table but are related by a has-many relationship. For instance,
-breweries have many beers, so C<beers> would appear in the list.
-
-C<moniker> - The human-readable name for the class: C<beer>.
-
-C<plural> - The same, only plural: C<beers>.
-
-C<cgi> - A hash mapping columns and C<HTML::Element> objects
-representing a form field for editing that column.
-
-C<description> - (Perhaps) a user-supplied description of the class.
+=head2 Default template arguments
-=back
+If you're looking for the list of variables that are passed to the
+Template Toolkit template by default, you'll find it in the
+L<View|Maypole::Manual::View> chapter.
-Additionally, depending on the number of objects, there will be an alias
-for the C<objects> slot with the name of the moniker or plural moniker.
+=head2 Links
-That sounds a bit tricky, but what it means is that if you look at
-C</beer/view/4> then C<beer> will be populated with a C<BeerDB::Beer>
-object with ID 4. On the other hand, if you look at C</beer/list> you
-can get all the beers in C<beers> as well as in C<objects>.
+L<Contents|Maypole::Manual>,
+Next L<The Beer Database Revisited|Maypole::Manual::Beer>,
+Previous
+L<Standard Templates and Actions|Maypole::Manual::StandardTemplates>
=head1 DESCRIPTION
-This is the default view class for Maypole; it uses the Template Toolkit
-to fill in templates with the objects produced by Maypole's model classes.
-Please see the Maypole manual, and in particular, the C<View> chapter,
-for the template variables available and for a refresher on how template
-components are resolved.
+This is the default view class for Maypole; it uses the Template Toolkit to
+fill in templates with the objects produced by Maypole's model classes. Please
+see the Maypole manual, and in particular, the L<view|Maypole::Manual::View>
+chapter for the template variables available and for a refresher on how
+template components are resolved.
The underlying Template toolkit object is configured through
-C<$r-E<gt>config-E<gt>view_options>. See L<Template> for available options.
+C<$r-E<gt>config-E<gt>view_options>. See L<Template|Template> for available
+options.
=over 4