]> git.decadent.org.uk Git - maypole.git/blobdiff - lib/Maypole.pm
Improved Maypole::load_model_subclass(). Added sequence
[maypole.git] / lib / Maypole.pm
index bec9675fda4f1fb643f1c43a0f123bfffd93ce27..d8e893dadb7693d141eee068e80032a215c69c88 100644 (file)
@@ -6,6 +6,7 @@ use warnings;
 use Maypole::Config;
 use Maypole::Constants;
 use Maypole::Headers;
+use URI();
 
 our $VERSION = '2.11';
 
@@ -15,41 +16,314 @@ our $VERSION = '2.11';
 #     including plugins
 # - double leading underscore - private to the current package
 
+=head1 NAME
+
+Maypole - MVC web application framework
+
+=head1 SYNOPSIS
+
+See L<Maypole::Application>.
+
+=head1 DESCRIPTION
+
+This documents the Maypole request object. See the L<Maypole::Manual>, for a
+detailed guide to using Maypole.
+
+Maypole is a Perl web application framework similar to Java's struts. It is 
+essentially completely abstracted, and so doesn't know anything about
+how to talk to the outside world.
+
+To use it, you need to create a driver package which represents your entire
+application. This is the C<BeerDB> package used as an example in the manual.
+
+This needs to first use L<Maypole::Application> which will make your package
+inherit from the appropriate platform driver such as C<Apache::MVC> or
+C<CGI::Maypole>. Then, the driver calls C<setup>.  This sets up the model classes and
+configures your application. The default model class for Maypole uses
+L<Class::DBI> to map a database to classes, but this can be changed by altering
+configuration (B<before> calling setup.)
+
+
+=head1 DOCUMENTATION AND SUPPORT
+
+Note that some details in some of these resources may be out of date.
+
+=over 4 
+
+=item The Maypole Manual
+
+The primary documentation is the Maypole manual. This lives in the 
+C<Maypole::Manual> pod documents included with the distribution. 
+
+=item Embedded POD
+
+Individual packages within the distribution contain (more or less) detailed
+reference documentation for their API.
+
+=item Mailing lists
+
+There are two mailing lists - maypole-devel and maypole-users - see
+http://maypole.perl.org/?MailingList
+
+=item The Maypole Wiki
+
+The Maypole wiki provides a useful store of extra documentation -
+http://maypole.perl.org
+
+In particular, there's a FAQ (http://maypole.perl.org/?FAQ) and a cookbook
+(http://maypole.perl.org/?Cookbook). Again, certain information on these pages
+may be out of date.
+
+=item Web applications with Maypole
+
+A tutorial written by Simon Cozens for YAPC::EU 2005 -
+http://www.droogs.org/perl/maypole/maypole-tutorial.pdf [228KB].
+
+=item A Database-Driven Web Application in 18 Lines of Code
+
+By Paul Barry, published in Linux Journal, March 2005.
+
+http://www.linuxjournal.com/article/7937
+
+"From zero to Web-based database application in eight easy steps".
+
+Maypole won a 2005 Linux Journal Editor's Choice Award
+(http://www.linuxjournal.com/article/8293) after featuring in this article. 
+
+=item Build Web apps with Maypole
+
+By Simon Cozens, on IBM's DeveloperWorks website, May 2004.
+
+http://www-128.ibm.com/developerworks/linux/library/l-maypole/
+
+=item Rapid Web Application Deployment with Maypole
+
+By Simon Cozens, on O'Reilly's Perl website, April 2004.
+
+http://www.perl.com/pub/a/2004/04/15/maypole.html
+
+=item Authentication
+
+Some notes written by Simon Cozens. A little bit out of date, but still 
+very useful: http://www.droogs.org/perl/maypole/authentication.html
+
+=item CheatSheet
+
+There's a refcard for the Maypole (and Class::DBI) APIs on the wiki -
+http://maypole.perl.org/?CheatSheet. Probably a little out of date now - it's a
+wiki, so feel free to fix any errors!
+
+=item Plugins and add-ons
+
+There are a large and growing number of plugins and other add-on modules
+available on CPAN - http://search.cpan.org/search?query=maypole&mode=module
+
+=item del.icio.us
+
+You can find a range of useful Maypole links, particularly to several thoughtful
+blog entries, starting here: http://del.icio.us/search/?all=maypole
+
+=item CPAN ratings
+
+There are a couple of short reviews here:
+http://cpanratings.perl.org/dist/Maypole
+
+=back
+
+=head1 DEMOS
+
+A couple of demos are available, sometimes with source code and configs. 
+
+=over 4 
+
+=item http://maypole.perl.org/beerdb/
+
+The standard BeerDB example, using the TT factory templates supplied in the
+distribution.
+
+=item beerdb.riverside-cms.co.uk
+
+The standard BeerDB example, running on Mason, using the factory templates
+supplied in the L<MasonX::Maypole> distribution.
+
+=item beerfb.riverside-cms.co.uk
+
+A demo of L<Maypole::FormBuilder>. This site is running on the set of Mason 
+templates included in the L<Maypole::FormBuilder> distribution. See the 
+synopsis of L<Maypole::Plugin::FormBuilder> for an example driver
+
+=back
+
+=cut
+
 __PACKAGE__->mk_classdata($_) for qw( config init_done view_object );
+
 __PACKAGE__->mk_accessors(
     qw( params query objects model_class template_args output path
         args action template error document_encoding content_type table
-        headers_in headers_out stash)
+        headers_in headers_out stash session)
 );
+
 __PACKAGE__->config( Maypole::Config->new() );
+
 __PACKAGE__->init_done(0);
 
-sub debug { 0 }
+=head1 HOOKABLE METHODS
+
+As a framework, Maypole provides a number of B<hooks> - methods that are
+intended to be overridden. Some of these methods come with useful default
+behaviour, others do nothing by default. Likely hooks include:
+
+    Class methods
+    -------------
+    debug 
+    setup 
+    setup_model 
+    load_model_subclass
+    init
+    
+    Instance methods
+    ----------------
+    start_request_hook
+    is_model_applicable
+    get_session
+    authenticate
+    exception
+    additional_data
+    preprocess_path
+
+=head1 CLASS METHODS
+
+=over 4
+
+=item debug
+
+    sub My::App::debug {1}
+
+Returns the debugging flag. Override this in your application class to
+enable/disable debugging.
+
+You can also set the C<debug> flag via L<Maypole::Application>.
+
+=cut
+
+sub debug { 0 }      
+
+=item config
 
-sub setup 
+Returns the L<Maypole::Config> object
+
+=item setup
+
+    My::App->setup($data_source, $user, $password, \%attr);
+
+Initialise the Maypole application and plugins and model classes - see
+L<Maypole::Manual::Plugins>.
+
+If your model is based on L<Maypole::Model::CDBI>, the C<\%attr> hashref can 
+contain options that are passed directly to L<Class::DBI::Loader>, to control 
+how the model hierarchy is constructed. 
+
+Your application should call this B<after> setting up configuration data via
+L<"config">.
+
+=cut
+
+sub setup
+{
+    my $class = shift;
+    
+    $class->setup_model(@_);   
+}
+
+=item setup_model
+
+Called by C<setup>. This method builds the Maypole model hierarchy. 
+
+A likely target for over-riding, if you need to build a customised model.
+
+This method also ensures any code in custom model classes is loaded, so you
+don't need to load them in the driver.
+
+=cut
+
+sub setup_model 
 {
-    my $calling_class = shift;
+    my $class = shift;
     
-    $calling_class = ref $calling_class if ref $calling_class;
+    $class = ref $class if ref $class;
     
-    my $config = $calling_class->config;
+    my $config = $class->config;
     
     $config->model || $config->model('Maypole::Model::CDBI');
     
     $config->model->require or die sprintf 
         "Couldn't load the model class %s: %s", $config->model, $@;
     
-    $config->model->setup_database($config, $calling_class, @_);
+    # among other things, this populates $config->classes
+    $config->model->setup_database($config, $class, @_);
     
     foreach my $subclass ( @{ $config->classes } ) 
     {
         no strict 'refs';
         unshift @{ $subclass . "::ISA" }, $config->model;
-        $config->model->adopt($subclass)
-          if $config->model->can("adopt");
+        
+        # Load custom model code, if it exists - nb this must happen after the 
+        # unshift, to allow code attributes to work, but before adopt(),  
+        # in case adopt() calls overridden methods on $subclass
+        $class->load_model_subclass($subclass);
+        
+        $config->model->adopt($subclass) if $config->model->can("adopt");
+
+#      eval "use $subclass"; 
+#      die "Error loading $subclass: $@"  
+#            if $@ and $@ !~ /Can\'t locate \S+ in \@INC/;
     }
 }
 
+=item load_model_subclass($subclass)
+
+This method is called from C<setup_model()>. It attempts to load the
+C<$subclass> package, if one exists. So if you make a customized C<BeerDB::Beer>
+package, you don't need to explicitly load it. 
+
+If, perhaps during development, you don't want to load up custom classes, you 
+can override this method and load them manually. 
+
+=cut
+
+sub load_model_subclass
+{
+    my ($class, $subclass) = @_;
+    
+    my $config = $class->config;
+    
+    # Load any external files for the model base class or subclasses
+    # (e.g. BeerDB/DBI.pm or BeerDB/Beer.pm) based on code borrowed from
+    # Maypole::Plugin::Loader and Class::DBI.
+    if ( $subclass->require ) 
+    {
+        warn "Loaded external module for '$subclass'\n" if $class->debug > 1;
+    } 
+    else 
+    {
+        (my $filename = $subclass) =~ s!::!/!g;
+        die "Loading '$subclass' failed: $@\n"
+               unless $@ =~ /Can\'t locate \Q$filename\E\.pm/;
+        warn "Did not find external module for '$subclass'\n" 
+            if $class->debug > 1;
+   }
+}
+
+=item init
+
+Loads the view class and instantiates the view object.
+
+You should not call this directly, but you may wish to override this to add
+application-specific initialisation - see L<Maypole::Manual::Plugins>.
+
+=cut
+
 sub init 
 {
     my $class  = shift;
@@ -61,9 +335,14 @@ sub init
       || $config->display_tables( $class->config->tables );
     $class->view_object( $class->config->view->new );
     $class->init_done(1);
-
 }
 
+=item new
+
+Constructs a very minimal new Maypole request object.
+
+=cut
+
 sub new
 {
     my ($class) = @_;
@@ -76,10 +355,29 @@ sub new
     return $self;
 }
 
+=item view_object
+
+Get/set the Maypole::View object
+
+=back
+
+=head1 INSTANCE METHODS
+
+=head2 Workflow
+
+=over 4
+
+=item handler
+
+This method sets up the class if it's not done yet, sets some defaults and
+leaves the dirty work to C<handler_guts>.
+
+=cut
+
 # handler() has a method attribute so that mod_perl will invoke
 # BeerDB->handler() as a method rather than a plain function
 # BeerDB::handler() and so this inherited implementation will be
-# found. See e.g. "Practical mod_perl"  by Bekman & Cholet for
+# found. See e.g. "Practical mod_perl" by Bekman & Cholet for
 # more information <http://modperlbook.org/html/ch25_01.html>
 sub handler : method 
 {
@@ -95,7 +393,13 @@ sub handler : method
     $self->get_request($req);
     $self->parse_location;
     
-    my $status = $self->handler_guts;
+    # hook useful for declining static requests e.g. images
+    my $status = $self->start_request_hook;
+    return $status unless $status == Maypole::Constants::OK();
+    
+    $self->session($self->get_session);
+    
+    $status = $self->handler_guts;
     
     # moving this here causes unit test failures - need to check why
     # before committing the move
@@ -103,25 +407,34 @@ sub handler : method
     
     return $status unless $status == OK;
     
+    # TODO: require send_output to return a status code
     $self->send_output;
     
     return $status;
 }
 
+=item handler_guts
+
+This is the main request handling method and calls various methods to handle the
+request/response and defines the workflow within Maypole.
+
+B<Currently undocumented and liable to be refactored without warning>.
+
+=cut
+
 # The root of all evil
 sub handler_guts 
 {
     my ($self) = @_;
     
-    $self->__load_model;
+    $self->__load_request_model;
 
     my $applicable = $self->is_model_applicable;
     
     $self->__setup_plain_template unless $applicable;
-    
-    # We authenticate every request, needed for proper session management
+
     my $status;
-    
+
     eval { $status = $self->call_authenticate };
     
     if ( my $error = $@ ) 
@@ -171,6 +484,12 @@ sub handler_guts
     return $self->__call_process_view;
 }
 
+sub __load_request_model
+{
+    my ($self) = @_;
+    $self->model_class( $self->config->model->class_of($self, $self->table) );
+}
+
 # is_applicable() returned false, so set up a plain template. Model processing 
 # will be skipped, but need to remove the model anyway so the template can't 
 # access it. 
@@ -213,18 +532,72 @@ sub __call_process_view
     return $status;
 }
 
-sub __load_model
+=item get_request
+
+You should only need to define this method if you are writing a new
+Maypole backend. It should return something that looks like an Apache
+or CGI request object, it defaults to blank.
+
+=cut
+
+sub get_request { }
+
+=item parse_location
+
+Turns the backend request (e.g. Apache::MVC, Maypole, CGI) into a Maypole
+request. It does this by setting the C<path>, and invoking C<parse_path> and
+C<parse_args>.
+
+You should only need to define this method if you are writing a new Maypole
+backend.
+
+=cut
+
+sub parse_location 
 {
-    my ($self) = @_;
-    $self->model_class( $self->config->model->class_of($self, $self->table) );
+    die "parse_location is a virtual method. Do not use Maypole directly; " . 
+               "use Apache::MVC or similar";
 }
 
-# is_applicable() should return true or false, not OK or DECLINED, because 
-# the return value is never used as the return value from handler(). There's 
-# probably a lot of code out there supplying the return codes though, so
-# instead of changing is_applicable() to return 0 or 1, the return value is
-# passed through __to_boolean. I think it helps handler_guts() if we don't
-# have multiple sets of return codes being checked for different things -drb.
+=item start_request_hook
+
+This is called immediately after setting up the basic request. The default
+method simply returns C<Maypole::Constants::OK>.
+
+Any other return value causes Maypole to abort further processing of the
+request. This is useful for filtering out requests for static files, e.g.
+images, which should not be processed by Maypole or by the templating engine:
+
+    sub start_request_hook
+    {
+        my ($r) = @_;
+       
+       return Maypole::Constants::DECLINED if $r->path =~ /\.jpg$/;
+       return Maypole::Constants::OK;
+    }
+
+=cut
+
+sub start_request_hook { Maypole::Constants::OK }
+
+=item is_applicable
+
+B<This method is deprecated> as of version 2.11. If you have overridden it,
+please override C<is_model_applicable> instead, and change the return type
+from a Maypole:Constant to a true/false value.
+
+Returns a Maypole::Constant to indicate whether the request is valid.
+
+=item is_model_applicable
+
+Returns true or false to indicate whether the request is valid.
+
+The default implementation checks that C<< $r->table >> is publicly
+accessible and that the model class is configured to handle the
+C<< $r->action >>.
+
+=cut
+
 sub is_model_applicable 
 {
     my ($self) = @_;
@@ -256,13 +629,6 @@ sub is_model_applicable
     {
         $ok = 1;
     } 
-# implements tj's default_table_view(), but there's no _default_table_view()
-# or _have_default_table_view() yet
-#    else 
-#    {
-#        $ok = $self->default_table_view($self->path, $self->args)
-#            if $self->_have_default_table_view;
-#    }
 
     if (not $ok) 
     {
@@ -284,6 +650,22 @@ sub is_model_applicable
     return 0;
 }
 
+=item get_session
+
+The default method is empty. 
+
+=cut
+
+sub get_session { }
+
+=item call_authenticate
+
+This method first checks if the relevant model class
+can authenticate the user, or falls back to the default
+authenticate method of your Maypole application.
+
+=cut
+
 sub call_authenticate 
 {
     my ($self) = @_;
@@ -299,6 +681,30 @@ sub call_authenticate
     return $self->authenticate($self);   
 }
 
+=item authenticate
+
+Returns a Maypole::Constant to indicate whether the user is authenticated for
+the Maypole request.
+
+The default implementation returns C<OK>
+
+=cut
+
+sub authenticate { return OK }
+
+
+=item call_exception
+
+This model is called to catch exceptions, first after authenticate, then after
+processing the model class, and finally to check for exceptions from the view
+class.
+
+This method first checks if the relevant model class
+can handle exceptions the user, or falls back to the default
+exception method of your Maypole application.
+
+=cut
+
 sub call_exception 
 {
     my ($self, $error) = @_;
@@ -313,367 +719,373 @@ sub call_exception
     return $self->exception($error);
 }
 
-sub default_table_view {
-  my ($self,$path,$args) = @_;
-  my $path_is_ok = 0;
-  my $default_table_view = __PACKAGE__->_default_table_view;
-  # (path class action field)
-  my @path = $self->{path} =~ m{([^/]+)/?}g;
-  my $search_value = shift(@path);
-  if ($default_table_view->{path}) {
-    if ($default_table_view->{path} eq $search_value) {
-      $search_value = shift(@path);
-    } else {
-      return 0;
-    }
-  }
-
-  $self->{table} = $default_table_view->{class};
-  $self->{action} = $default_table_view->{action};
-  $self->{args} = [ $search_value,@path ];
-  return $path_is_ok;
-}
+=item exception
 
-sub additional_data { }
+This method is called if any exceptions are raised during the authentication or
+model/view processing. It should accept the exception as a parameter and return
+a Maypole::Constant to indicate whether the request should continue to be
+processed.
 
-sub authenticate { return OK }
+=cut
 
 sub exception { return ERROR }
 
-sub parse_path 
-{
-    my ($self) = @_;
-    
-    $self->path || $self->path('frontpage');
-    
-    my @pi = grep {length} split '/', $self->path;
-    
-    $self->table(shift @pi);
-    
-    $self->action( shift @pi or 'index' );
-    
-    $self->args(\@pi);
-}
+=item additional_data
 
-# like CGI::param(), but read only 
-sub param 
-{ 
-    my ($self, $key) = @_;
-    
-    return keys %{$self->params} unless defined $key;
-    
-    return unless exists $self->params->{$key};
-    
-    my $val = $self->params->{$key};
-    
-    return ref $val ? @$val : ($val) if wantarray;
-        
-    return ref $val ? $val->[0] : $val;
-}
+Called before the model processes the request, this method gives you a chance to
+do some processing for each request, for example, manipulating C<template_args>.
 
-sub get_template_root {'.'}
-sub get_request       { }
+=cut
 
-sub get_protocol {
-  die "get_protocol is a virtual method. Do not use Maypole directly; use Apache::MVC or similar";
-}
+sub additional_data { }
 
-sub parse_location {
-    die "parse_location is a virtual method. Do not use Maypole directly; use Apache::MVC or similar";
-}
+=item send_output
 
-sub redirect_request {
-  die "parse_location is a virtual method. Do not use Maypole directly; use Apache::MVC or similar";
-}
-
-sub redirect_internal_request {
+Sends the output and additional headers to the user.
 
-}
+=cut
 
 sub send_output {
     die "send_output is a virtual method. Do not use Maypole directly; use Apache::MVC or similar";
 }
 
-# Session and Repeat Submission Handling
 
-sub make_random_id {
-    use Maypole::Session;
-    return Maypole::Session::generate_unique_id();
-}
 
-=head1 NAME
 
-Maypole - MVC web application framework
+=back
 
-=head1 SYNOPSIS
+=head2 Path processing and manipulation
 
-See L<Maypole::Application>.
+=over 4
 
-=head1 DESCRIPTION
+=item path
 
-This documents the Maypole request object. See the L<Maypole::Manual>, for a
-detailed guide to using Maypole.
+Returns the request path
 
-Maypole is a Perl web application framework similar to Java's struts. It is 
-essentially completely abstracted, and so doesn't know anything about
-how to talk to the outside world.
+=item parse_path
 
-To use it, you need to create a package which represents your entire
-application. In our example above, this is the C<BeerDB> package.
+Parses the request path and sets the C<args>, C<action> and C<table>
+properties. Calls C<preprocess_path> before parsing path and setting properties.
 
-This needs to first use L<Maypole::Application> which will make your package
-inherit from the appropriate platform driver such as C<Apache::MVC> or
-C<CGI::Maypole>, and then call setup.  This sets up the model classes and
-configures your application. The default model class for Maypole uses
-L<Class::DBI> to map a database to classes, but this can be changed by altering
-configuration. (B<Before> calling setup.)
+=cut
 
-=head2 CLASS METHODS
+sub parse_path 
+{
+    my ($self) = @_;
+    
+    # Previous versions unconditionally set table, action and args to whatever 
+    # was in @pi (or else to defaults, if @pi is empty).
+    # Adding preprocess_path(), and then setting table, action and args 
+    # conditionally, broke lots of tests, hence this:
+    $self->$_(undef) for qw/action table args/;
+    
+    $self->preprocess_path;
 
-=head3 config
+    $self->path || $self->path('frontpage');
+    
+    my @pi = grep {length} split '/', $self->path;
+    
+    $self->table  || $self->table(shift @pi);
+    $self->action || $self->action( shift @pi or 'index' );
+    $self->args   || $self->args(\@pi);
+}
 
-Returns the L<Maypole::Config> object
+=item preprocess_path
 
-=head3 setup
+Sometimes when you don't want to rewrite or over-ride parse_path but
+want to rewrite urls or extract data from them before it is parsed.
 
-    My::App->setup($data_source, $user, $password, \%attr);
+This method is called after parse_location has populated the request
+information and before parse_path has populated the model and action
+information, and is passed the request object.
 
-Initialise the maypole application and model classes. Your application should
-call this after setting configuration via L<"config">
+You can set action, args or table in this method and parse_path will
+then leave those values in place or populate them if not present
 
-=head3 init
+=cut
 
-You should not call this directly, but you may wish to override this to
-add
-application-specific initialisation.
+sub preprocess_path { };
 
-=head3 new
+=item make_path( %args or \%args or @args )
 
-Constructs a very minimal new Maypole request object.
+This is the counterpart to C<parse_path>. It generates a path to use
+in links, form actions etc. To implement your own path scheme, just override
+this method and C<parse_path>.
 
-=head3 view_object
+    %args = ( table      => $table,
+              action     => $action,        
+              additional => $additional,    # optional - generally an object ID
+              );
+              
+    \%args = as above, but a ref
+    
+    @args = ( $table, $action, $additional );   # $additional is optional
 
-Get/set the Maypole::View object
+C<id> can be used as an alternative key to C<additional>.
 
-=head3 debug
+C<$additional> can be a string, an arrayref, or a hashref. An arrayref is
+expanded into extra path elements, whereas a hashref is translated into a query
+string. 
 
-    sub My::App::debug {1}
+=cut
 
-Returns the debugging flag. Override this in your application class to
-enable/disable debugging.
+sub make_path
+{
+    my $r = shift;
+    
+    my %args;
+    
+    if (@_ == 1 and ref $_[0] and ref $_[0] eq 'HASH')
+    {
+        %args = %{$_[0]};
+    }
+    elsif ( @_ > 1 and @_ < 4 )
+    {
+        $args{table}      = shift;
+        $args{action}     = shift;
+        $args{additional} = shift;
+    }
+    else
+    {
+        %args = @_;
+    }
+    
+    do { die "no $_" unless $args{$_} } for qw( table action );    
 
-=head2 INSTANCE METHODS
+    my $additional = $args{additional} || $args{id};
+    
+    my @add = ();
+    
+    if ($additional)
+    {
+        # if $additional is a href, make_uri() will transform it into a query
+        @add = (ref $additional eq 'ARRAY') ? @$additional : ($additional);
+    }    
+    
+    my $uri = $r->make_uri($args{table}, $args{action}, @add);
+    
+    return $uri->as_string;
+}
 
-=head3 parse_location
 
-Turns the backend request (e.g. Apache::MVC, Maypole, CGI) into a
-Maypole
-request. It does this by setting the C<path>, and invoking C<parse_path>
-and
-C<parse_args>.
 
-You should only need to define this method if you are writing a new
-Maypole
-backend.
+=item make_uri( @segments )
 
-=head3 path
+Make a L<URI> object given table, action etc. Automatically adds
+the C<uri_base>. 
 
-Returns the request path
+If the final element in C<@segments> is a hash ref, C<make_uri> will render it
+as a query string.
 
-=head3 parse_path
+=cut
 
-Parses the request path and sets the C<args>, C<action> and C<table> 
-properties
+sub make_uri
+{
+    my ($r, @segments) = @_;
 
-=head3 table
+    my $query = (ref $segments[-1] eq 'HASH') ? pop(@segments) : undef;
+    
+    my $base = $r->config->uri_base; 
+    $base =~ s|/$||;
+    
+    my $uri = URI->new($base);
+    $uri->path_segments($uri->path_segments, grep {length} @segments);
+    
+    my $abs_uri = $uri->abs('/');
+    $abs_uri->query_form($query) if $query;
+    return $abs_uri;
+}
 
-The table part of the Maypole request path
+=item parse_args
 
-=head3 action
+Turns post data and query string paramaters into a hash of C<params>.
 
-The action part of the Maypole request path
+You should only need to define this method if you are writing a new Maypole
+backend.
 
-=head3 args
+=cut 
 
-A list of remaining parts of the request path after table and action
-have been
-removed
+sub parse_args
+{
+    die "parse_args() is a virtual method. Do not use Maypole directly; ".
+            "use Apache::MVC or similar";
+}
 
-=head3 headers_in
+=item get_template_root
 
-A L<Maypole::Headers> object containing HTTP headers for the request
+Implementation-specific path to template root.
 
-=head3 headers_out
+You should only need to define this method if you are writing a new Maypole
+backend. Otherwise, see L<Maypole::Config/"template_root">
 
-A L<HTTP::Headers> object that contains HTTP headers for the output
+=cut
 
-=head3 parse_args
+sub get_template_root {'.'}
 
-Turns post data and query string paramaters into a hash of C<params>.
+=back
 
-You should only need to define this method if you are writing a new
-Maypole
-backend.
+=head2 Request properties
 
-=head3 param
+=over 4
 
-An accessor for request parameters. It behaves similarly to CGI::param() for
-accessing CGI parameters.
+=item model_class
 
-=head3 params
+Returns the perl package name that will serve as the model for the
+request. It corresponds to the request C<table> attribute.
 
-Returns a hash of request parameters. The source of the parameters may vary
-depending on the Maypole backend, but they are usually populated from request
-query string and POST data.
 
-B<Note:> Where muliple values of a parameter were supplied, the
-C<params> 
-value
-will be an array reference.
+=item objects
 
-=head3 get_template_root
+Get/set a list of model objects. The objects will be accessible in the view
+templates.
 
-Implementation-specific path to template root.
+If the first item in C<$self-E<gt>args> can be C<retrieve()>d by the model
+class, it will be removed from C<args> and the retrieved object will be added to
+the C<objects> list. See L<Maypole::Model> for more information.
 
-You should only need to define this method if you are writing a new
-Maypole
-backend. Otherwise, see L<Maypole::Config/"template_root">
+=item template_args
 
-=head3 get_request
+    $self->template_args->{foo} = 'bar';
 
-You should only need to define this method if you are writing a new
-Maypole backend. It should return something that looks like an Apache
-or CGI request object, it defaults to blank.
+Get/set a hash of template variables.
 
-=head3 default_table_view
+=item stash
 
-=head3 is_applicable
+A place to put custom application data. Not used by Maypole itself. 
 
-Returns a Maypole::Constant to indicate whether the request is valid.
+=item template
 
-B<This method is deprecated> as of version 2.11. If you have overridden it,
-please override C<is_model_applicable> instead, and change the return type
-from Maypole:Constants to true/false.
+Get/set the template to be used by the view. By default, it returns
+C<$self-E<gt>action>
 
-=head3 is_model_applicable
 
-Returns true or false to indicate whether the request is valid.
+=item error
 
-The default implementation checks that C<< $r->table >> is publicly
-accessible and that the model class is configured to handle the
-C<< $r->action >>.
+Get/set a request error
 
-=head3 authenticate
+=item output
 
-Returns a Maypole::Constant to indicate whether the user is
-authenticated for
-the Maypole request.
+Get/set the response output. This is usually populated by the view class. You
+can skip view processing by setting the C<output>.
 
-The default implementation returns C<OK>
+=item table
 
-=head3 model_class
+The table part of the Maypole request path
 
-Returns the perl package name that will serve as the model for the
-request. It corresponds to the request C<table> attribute.
+=item action
 
-=head3 additional_data
+The action part of the Maypole request path
 
-Called before the model processes the request, this method gives you a
-chance
-to do some processing for each request, for example, manipulating
-C<template_args>.
+=item args
 
-=head3 objects
+A list of remaining parts of the request path after table and action
+have been
+removed
 
-Get/set a list of model objects. The objects will be accessible in the
-view
-templates.
+=item headers_in
 
-If the first item in C<$self-E<gt>args> can be C<retrieve()>d by the model
-class,
-it will be removed from C<args> and the retrieved object will be added
-to the
-C<objects> list. See L<Maypole::Model> for more information.
+A L<Maypole::Headers> object containing HTTP headers for the request
 
-=head3 template_args
+=item headers_out
 
-    $self->template_args->{foo} = 'bar';
+A L<HTTP::Headers> object that contains HTTP headers for the output
 
-Get/set a hash of template variables.
+=item document_encoding
 
-=head3 stash
+Get/set the output encoding. Default: utf-8.
 
-A place to put custom application data. Not used by Maypole itself. 
+=item content_type
 
-=head3 template
+Get/set the output content type. Default: text/html
 
-Get/set the template to be used by the view. By default, it returns
-C<$self-E<gt>action>
+=item get_protocol
 
-=head3 exception
+Returns the protocol the request was made with, i.e. https
 
-This method is called if any exceptions are raised during the
-authentication 
-or
-model/view processing. It should accept the exception as a parameter and 
-return
-a Maypole::Constant to indicate whether the request should continue to
-be
-processed.
+=cut
+
+sub get_protocol {
+  die "get_protocol is a virtual method. Do not use Maypole directly; use Apache::MVC or similar";
+}
 
-=head3 error
+=back
 
-Get/set a request error
+=head2 Request parameters
 
-=head3 output
+The source of the parameters may vary depending on the Maypole backend, but they
+are usually populated from request query string and POST data.
 
-Get/set the response output. This is usually populated by the view
-class. You
-can skip view processing by setting the C<output>.
+Maypole supplies several approaches for accessing the request parameters. Note
+that the current implementation (via a hashref) of C<query> and C<params> is
+likely to change in a future version of Maypole. So avoid direct access to these
+hashrefs:
 
-=head3 document_encoding
+    $r->{params}->{foo}      # bad
+    $r->params->{foo}        # better
 
-Get/set the output encoding. Default: utf-8.
+    $r->{query}->{foo}       # bad
+    $r->query->{foo}         # better
 
-=head3 content_type
+    $r->param('foo')         # best
 
-Get/set the output content type. Default: text/html
+=over 4
 
-=head3 send_output
+=item param
 
-Sends the output and additional headers to the user.
+An accessor (get or set) for request parameters. It behaves similarly to
+CGI::param() for accessing CGI parameters, i.e.
 
-=head3 call_authenticate
+    $r->param                   # returns list of keys
+    $r->param($key)             # returns value for $key
+    $r->param($key => $value)   # returns old value, sets to new value
 
-This method first checks if the relevant model class
-can authenticate the user, or falls back to the default
-authenticate method of your Maypole application.
+=cut
 
+sub param 
+{ 
+    my ($self, $key) = (shift, shift);
+    
+    return keys %{$self->params} unless defined $key;
+    
+    return unless exists $self->params->{$key};
+    
+    my $val = $self->params->{$key};
+    
+    if (@_)
+    {
+        my $new_val = shift;
+       $self->params->{$key} = $new_val;
+    }
+    
+    return ref $val ? @$val : ($val) if wantarray;
+        
+    return ref $val ? $val->[0] : $val;
+}
 
-=head3 call_exception
 
-This model is called to catch exceptions, first after authenticate, then after
-processing the model class, and finally to check for exceptions from the view
-class.
+=item params
 
-This method first checks if the relevant model class
-can handle exceptions the user, or falls back to the default
-exception method of your Maypole application.
+Returns a hashref of request parameters. 
+
+B<Note:> Where muliple values of a parameter were supplied, the C<params> value
+will be an array reference.
 
-=head3 make_random_id
+=item query
 
-returns a unique id for this request can be used to prevent or detect repeat
-submissions.
+Alias for C<params>.
 
-=head3 get_protocol
+=back
 
-Returns the protocol the request was made with, i.e. https
+=head3 Utility methods
 
-=head3 redirect_request
+=over 4
+
+=item redirect_request
 
 Sets output headers to redirect based on the arguments provided
 
-Accepts either a single argument of the full url to redirect to, or a hash of named parameters :
+Accepts either a single argument of the full url to redirect to, or a hash of
+named parameters :
 
 $r->redirect_request('http://www.example.com/path');
 
@@ -683,19 +1095,141 @@ $r->redirect_request(protocol=>'https', domain=>'www.example.com', path=>'/path/
 
 The named parameters are protocol, domain, path, status and url
 
-Only 1 named parameter is required but other than url, they can be combined as required and current values (from the request) will be used in place of any missing arguments. The url argument must be a full url including protocol and can only be combined with status.
+Only 1 named parameter is required but other than url, they can be combined as
+required and current values (from the request) will be used in place of any
+missing arguments. The url argument must be a full url including protocol and
+can only be combined with status.
+
+=cut
+
+sub redirect_request {
+  die "redirect_request is a virtual method. Do not use Maypole directly; use Apache::MVC or similar";
+}
+
+=item redirect_internal_request 
+
+=cut
+
+sub redirect_internal_request {
+
+}
 
-=head3 handler
 
-This method sets up the class if it's not done yet, sets some
-defaults and leaves the dirty work to handler_guts.
+=item make_random_id
 
-=head3 handler_guts
+returns a unique id for this request can be used to prevent or detect repeat
+submissions.
 
-This is the main request handling method and calls various methods to handle the request/response
-and defines the workflow within Maypole.
+=cut
+
+# Session and Repeat Submission Handling
+sub make_random_id {
+    use Maypole::Session;
+    return Maypole::Session::generate_unique_id();
+}
 
-Currently undocumented and liable to be refactored without warning.
+=back
+
+=head1 SEQUENCE DIAGRAMS
+
+See L<Maypole::Manual::Workflow> for a detailed discussion of the sequence of 
+calls during processing of a request. This is a brief summary:
+
+    INITIALIZATION
+                               Model e.g.
+         BeerDB           Maypole::Model::CDBI
+           |                        |
+   setup   |                        |
+ o-------->||                       |
+           || setup_model           |     setup_database() creates
+           ||------+                |      a subclass of the Model
+           |||<----+                |        for each table
+           |||                      |                |
+           |||   setup_database     |                |
+           |||--------------------->|| 'create'      *
+           |||                      ||----------> $subclass
+           |||                      |                  |
+           ||| load_model_subclass  |                  |
+ foreach   |||------+  ($subclass)  |                  |
+ $subclass ||||<----+               |    require       |
+           ||||--------------------------------------->|
+           |||                      |                  |
+           |||   adopt($subclass)   |                  |
+           |||--------------------->||                 |
+           |                        |                  |
+           |                        |                  |
+           |-----+ init             |                  |
+           ||<---+                  |                  |
+           ||                       |     new          |     view_object: e.g
+           ||---------------------------------------------> Maypole::View::TT
+           |                        |                  |          |
+           |                        |                  |          |
+           |                        |                  |          |
+           |                        |                  |          |
+           |                        |                  |          |
+           
+
+
+    HANDLING A REQUEST
+
+
+          BeerDB                                Model  $subclass  view_object
+            |                                      |       |         |
+    handler |                                      |       |         |
+  o-------->| new                                  |       |         |
+            |-----> r:BeerDB                       |       |         |
+            |         |                            |       |         |
+            |         |                            |       |         |
+            |         ||                           |       |         |
+            |         ||-----+ parse_location      |       |         |
+            |         |||<---+                     |       |         |
+            |         ||                           |       |         |
+            |         ||-----+ start_request_hook  |       |         |
+            |         |||<---+                     |       |         |
+            |         ||                           |       |         |
+            |         ||-----+ get_session         |       |         |
+            |         |||<---+                     |       |         |
+            |         ||                           |       |         |
+            |         ||-----+ handler_guts        |       |         |
+            |         |||<---+                     |       |         |
+            |         |||     class_of($table)     |       |         |
+            |         |||------------------------->||      |         |
+            |         |||       $subclass          ||      |         |
+            |         |||<-------------------------||      |         |
+            |         |||                          |       |         |
+            |         |||-----+ is_model_applicable|       |         |
+            |         ||||<---+                    |       |         |
+            |         |||                          |       |         |
+            |         |||-----+ call_authenticate  |       |         |
+            |         ||||<---+                    |       |         |
+            |         |||                          |       |         |
+            |         |||-----+ additional_data    |       |         |
+            |         ||||<---+                    |       |         |
+            |         |||             process      |       |   fetch_objects
+            |         |||--------------------------------->||-----+  |
+            |         |||                          |       |||<---+  |
+            |         |||                          |       ||        |
+            |         |||                          |       ||   $action
+            |         |||                          |       ||-----+  |
+            |         |||                          |       |||<---+  |
+            |         |||                          |       |         |
+            |         |||         process          |       |         |
+            |         |||------------------------------------------->|| template
+            |         |||                          |       |         ||-----+
+            |         |||                          |       |         |||<---+
+            |         |||                          |       |         |
+            |         ||     send_output           |       |         |
+            |         ||-----+                     |       |         |
+            |         |||<---+                     |       |         |
+   $status  |         ||                           |       |         |
+   <------------------||                           |       |         |
+            |         |                            |       |         |
+            |         X                            |       |         |           
+            |                                      |       |         |
+            |                                      |       |         |
+            |                                      |       |         |
+           
+           
 
 =head1 SEE ALSO
 
@@ -708,7 +1242,7 @@ L<Maypole::Application>, L<Apache::MVC>, L<CGI::Maypole>.
 
 =head1 AUTHOR
 
-Maypole is currently maintained by Simon Flack C<simonflk#cpan.org>
+Maypole is currently maintained by Aaron Trevena
 
 =head1 AUTHOR EMERITUS