2 use base qw(Class::Accessor Class::Data::Inheritable);
4 use UNIVERSAL::require;
8 __PACKAGE__->mk_classdata($_) for qw( config init_done view_object );
9 __PACKAGE__->mk_accessors ( qw( ar params query objects model_class
10 args action template ));
11 __PACKAGE__->config({});
12 __PACKAGE__->init_done(0);
14 # Ape Apache::Constants interface
16 use constant DECLINED => -1;
21 my $calling_class = shift;
22 $calling_class = ref $calling_class if ref $calling_class;
26 *{$calling_class."::handler"} = sub { Maypole::handler($calling_class, @_) };
28 my $config = $calling_class->config;
29 $config->{model} ||= "Maypole::Model::CDBI";
30 $config->{model}->require;
31 $config->{model}->setup_database($config, $calling_class, @_);
32 for my $subclass (@{$config->{classes}}) {
34 unshift @{$subclass."::ISA"}, $config->{model};
35 $config->{model}->adopt($subclass)
36 if $config->{model}->can("adopt");
42 my $config = $class->config;
43 $config->{view} ||= "Maypole::View::TT";
44 $config->{view}->require;
45 $config->{display_tables} ||= [ @{$class->config->{tables}} ];
46 $class->view_object($class->config->{view}->new);
52 # See Maypole::Workflow before trying to understand this.
54 $class->init unless $class->init_done;
55 my $r = bless { config => $class->config }, $class;
59 $r->model_class($r->config->{model}->class_of($r, $r->{table}));
60 my $status = $r->is_applicable;
62 $status = $r->call_authenticate;
63 if ($r->debug and $status != OK and $status != DECLINED) {
64 $r->view_object->error($r,
65 "Got unexpected status $status from calling authentication");
67 return $status unless $status == OK;
68 $r->additional_data();
70 $r->model_class->process($r);
72 # Otherwise, it's just a plain template.
73 $r->call_authenticate; # No harm in it
74 delete $r->{model_class};
75 $r->{path} =~ s{/}{}; # De-absolutify
76 $r->template($r->{path});
79 if (!$r->{output}) { # You might want to do it yourself
80 $status = $r->view_object->process($r);
88 my $config = $self->config;
89 $config->{ok_tables} = {map {$_ => 1} @{$config->{display_tables}}};
90 warn "We don't have that table ($self->{table})"
91 if $self->debug and not $config->{ok_tables}{$self->{table}};
92 return DECLINED() unless exists $config->{ok_tables}{$self->{table}};
94 # Does the action method exist?
95 my $cv = $self->model_class->can($self->{action});
96 warn "We don't have that action ($self->{action})"
97 if $self->debug and not $cv;
98 return DECLINED() unless $cv;
101 $self->{method_attribs} = join " ", attributes::get($cv);
102 do { warn "$self->{action} not exported" if $self->debug;
104 } unless $self->{method_attribs} =~ /\bExported\b/i;
108 sub call_authenticate {
110 return $self->model_class->authenticate($self) if
111 $self->model_class->can("authenticate");
112 return $self->authenticate($self); # Interface consistency is a Good Thing
115 sub additional_data {}
117 sub authenticate { return OK }
121 Maypole - MVC web application framework
129 A large number of web programming tasks follow the same sort of pattern:
130 we have some data in a datasource, typically a relational database. We
131 have a bunch of templates provided by web designers. We have a number of
132 things we want to be able to do with the database - create, add, edit,
133 delete records, view records, run searches, and so on. We have a web
134 server which provides input from the user about what to do. Something in
135 the middle takes the input, grabs the relevant rows from the database,
136 performs the action, constructs a page, and spits it out.
138 Maypole aims to be the most generic and extensible "something in the
139 middle" - an MVC-based web application framework.
141 An example would help explain this best. You need to add a product
142 catalogue to a company's web site. Users need to list the products in
143 various categories, view a page on each product with its photo and
144 pricing information and so on, and there needs to be a back-end where
145 sales staff can add new lines, change prices, and delete out of date
146 records. So, you set up the database, provide some default templates
147 for the designers to customize, and then write an Apache handler like
150 package ProductDatabase;
151 use base 'Apache::MVC';
152 __PACKAGE__->set_database("dbi:mysql:products");
153 ProductDatabase->config->{uri_base} = "http://your.site/catalogue/";
154 ProductDatabase::Product->has_a("category" => ProductDatabase::Category);
158 my ($self, $request) = @_;
159 return OK if $request->{ar}->get_remote_host() eq "sales.yourcorp.com";
160 return OK if $request->{action} =~ /^(view|list)$/;
165 You then put the following in your Apache config:
167 <Location /catalogue>
168 SetHandler perl-script
169 PerlHandler ProductDatabase
172 And copy the templates found in F<templates/factory> into the
173 F<catalogue/factory> directory off the web root. When the designers get
174 back to you with custom templates, they are to go in
175 F<catalogue/custom>. If you need to do override templates on a
176 database-table-by-table basis, put the new template in
177 F<catalogue/I<table>>.
179 This will automatically give you C<add>, C<edit>, C<list>, C<view> and
180 C<delete> commands; for instance, a product list, go to
182 http://your.site/catalogue/product/list
184 For a full example, see the included "beer database" application.
188 There's some documentation for the workflow in L<Maypole::Workflow>,
189 but the basic idea is that a URL part like C<product/list> gets
190 translated into a call to C<ProductDatabase::Product-E<gt>list>. This
191 propagates the request with a set of objects from the database, and then
192 calls the C<list> template; first, a C<product/list> template if it
193 exists, then the C<custom/list> and finally C<factory/list>.
195 If there's another action you want the system to do, you need to either
196 subclass the model class, and configure your class slightly differently:
198 package ProductDatabase::Model;
199 use base 'Maypole::Model::CDBI';
201 sub supersearch :Exported {
202 my ($self, $request) = @_;
203 # Do stuff, get a bunch of objects back
204 $r->objects(\@objects);
205 $r->template("template_name");
208 Then your top-level application package should change the model class:
209 (Before calling C<setup>)
211 ProductDatabase->config->{model} = "ProductDatabase::Model";
213 (The C<:Exported> attribute means that the method can be called via the
214 URL C</I<table>/supersearch/...>.)
216 Alternatively, you can put the method directly into the specific model
219 sub ProductDatabase::Product::supersearch :Exported { ... }
221 By default, the view class uses Template Toolkit as the template
222 processor, and the model class uses C<Class::DBI>; it may help you to be
223 familiar with these modules before going much further with this,
224 although I expect there to be other subclasses for other templating
225 systems and database abstraction layers as time goes on. The article at
226 C<http://www.perl.com/pub/a/2003/07/15/nocode.html> is a great
227 introduction to the process we're trying to automate.
231 You should probably not use Maypole directly. Maypole is an abstract
232 class which does not specify how to communicate with the outside world.
233 The most popular subclass of Maypole is L<Apache::MVC>, which interfaces
234 the Maypole framework to Apache mod_perl.
236 If you are implementing Maypole subclasses, you need to provide at least
237 the C<parse_location> and C<send_output> methods. You may also want to
238 provide C<get_request> and C<get_template_root>. See the
239 L<Maypole::Workflow> documentation for what these are expected to do.
243 sub get_template_root { "." }
245 sub parse_location { die "Do not use Maypole directly; use Apache::MVC or similar" }
246 sub send_output{ die "Do not use Maypole directly; use Apache::MVC or similar" }
250 There's more documentation, examples, and a wiki at the Maypole web site:
252 http://maypole.simon-cozens.org/
256 Simon Cozens, C<simon@cpan.org>
260 You may distribute this code under the same terms as Perl itself.