package Maypole;
use base qw(Class::Accessor Class::Data::Inheritable);
use attributes ();
-use Class::DBI::Loader;
use UNIVERSAL::require;
use Apache::Constants ":common";
use strict;
use warnings;
-our $VERSION = "0.2";
+our $VERSION = "1.0";
__PACKAGE__->mk_classdata($_) for qw( config init_done view_object );
__PACKAGE__->mk_accessors ( qw( ar params query objects model_class
args action template ));
__PACKAGE__->init_done(0);
-sub import {
- my $real = shift;
- if ($real ne "Apache::MVC") {
- no strict 'refs';
- *{$real."::handler"} = sub { Apache::MVC::handler($real, @_) };
- }
-}
-
-sub set_database {
- my ($calling_class, $dsn) = @_;
+sub setup {
+ my $calling_class = shift;
$calling_class = ref $calling_class if ref $calling_class;
+ {
+ no strict 'refs';
+ # Naughty.
+ *{$calling_class."::handler"} = sub { Maypole::handler($calling_class, @_) };
+ }
my $config = $calling_class->config;
- $config->{model} ||= "Apache::MVC::Model::CDBI";
+ $config->{model} ||= "Maypole::Model::CDBI";
$config->{model}->require;
- $config->{dsn} = $dsn;
- $config->{loader} = Class::DBI::Loader->new(
- namespace => $calling_class,
- dsn => $dsn
- );
- $config->{classes} = [ $config->{loader}->classes ];
+ $config->{model}->setup_database($config, $calling_class, @_);
for my $subclass (@{$config->{classes}}) {
no strict 'refs';
unshift @{$subclass."::ISA"}, $config->{model};
sub init {
my $class = shift;
my $config = $class->config;
- $config->{view} ||= "Apache::MVC::View::TT";
+ $config->{view} ||= "Maypole::View::TT";
$config->{view}->require;
- $config->{display_tables} ||= [ $class->config->{loader}->tables ];
+ $config->{display_tables} ||= [ @{$class->config->{tables}} ];
$class->view_object($class->config->{view}->new);
$class->init_done(1);
}
-sub class_of {
- my ($self, $table) = @_;
- return $self->config->{loader}->_table2class($table);
-}
-
sub handler {
- # See Apache::MVC::Workflow before trying to understand this.
+ # See Maypole::Workflow before trying to understand this.
my $class = shift;
$class->init unless $class->init_done;
my $r = bless { config => $class->config }, $class;
$r->get_request();
$r->parse_location();
- $r->model_class($r->class_of($r->{table}));
+ $r->model_class($r->config->{model}->class_of($r, $r->{table}));
my $status = $r->is_applicable;
if ($status == OK) {
$status = $r->call_authenticate;
return $r->view_object->process($r);
}
-sub get_request {
- my $self = shift;
- require Apache; require Apache::Request;
- $self->{ar} = Apache::Request->new(Apache->request);
-}
-
-sub parse_location {
- my $self = shift;
- $self->{path} = $self->{ar}->uri;
- my $loc = $self->{ar}->location;
- $self->{path} =~ s/^$loc//; # I shouldn't need to do this?
- $self->{path} ||= "frontpage";
- my @pi = split /\//, $self->{path};
- shift @pi while @pi and !$pi[0];
- $self->{table} = shift @pi;
- $self->{action} = shift @pi;
- $self->{args} = \@pi;
-
- $self->{params} = { $self->{ar}->content };
- $self->{query} = { $self->{ar}->args };
-}
-
sub is_applicable {
my $self = shift;
my $config = $self->config;
sub call_authenticate {
my $self = shift;
return $self->model_class->authenticate($self) if
- $self->model_class->can("authenticate");
- return $self->authenticate();
+ $self->model_class->can("authenticate");
+ return $self->authenticate($self); # Interface consistency is a Good Thing
}
sub additional_data {}
sub authenticate { return OK }
-1;
-
=head1 NAME
Maypole - MVC web application framework
=head1 SYNOPSIS
-See L<Apache::MVC>.
+See L<Maypole>.
=head1 DESCRIPTION
this:
package ProductDatabase;
- use base 'Apache::MVC';
+ use base 'Maypole';
__PACKAGE__->set_database("dbi:mysql:products");
- BeerDB->config->{uri_base} = "http://your.site/catalogue/";
+ ProductDatabase->config->{uri_base} = "http://your.site/catalogue/";
ProductDatabase::Product->has_a("category" => ProductDatabase::Category);
# ...
=head1 HOW IT WORKS
-There's some documentation for the workflow in L<Apache::MVC::Workflow>,
+There's some documentation for the workflow in L<Maypole::Workflow>,
but the basic idea is that a URL part like C<product/list> gets
translated into a call to C<ProductDatabase::Product-E<gt>list>. This
propagates the request with a set of objects from the database, and then
subclass the model class, and configure your class slightly differently:
package ProductDatabase::Model;
- use base 'Apache::MVC::Model::CDBI';
+ use base 'Maypole::Model::CDBI';
sub supersearch :Exported {
my ($self, $request) = @_;
$r->template("template_name");
}
+Then your top-level application package should change the model class:
+(Before calling C<setup>)
+
ProductDatabase->config->{model_class} = "ProductDatabase::Model";
(The C<:Exported> attribute means that the method can be called via the
C<http://www.perl.com/pub/a/2003/07/15/nocode.html> is a great
introduction to the process we're trying to automate.
+=head1 USING MAYPOLE
+
+You should probably not use Maypole directly. Maypole is an abstract
+class which does not specify how to communicate with the outside world.
+The most popular subclass of Maypole is L<Apache::MVC>, which interfaces
+the Maypole framework to Apache mod_perl.
+
+If you are implementing Maypole subclasses, you need to provide at least
+the C<get_request> and C<parse_location> methods. See the
+L<Maypole::Workflow> documentation for what these are expected to do.
+
+=cut
+
+sub get_request { die "Do not use Maypole directly; use Apache::MVC or similar" }
+sub parse_location { die "Do not use Maypole directly; use Apache::MVC or similar" }
+
=head1 AUTHOR
Simon Cozens, C<simon@cpan.org>
=head1 LICENSE
You may distribute this code under the same terms as Perl itself.
+
+=cut
+
+1;
+