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 die "Couldn't load the model class $config->{model}: $@" if $@;
32 $config->{model}->setup_database($config, $calling_class, @_);
33 for my $subclass (@{$config->{classes}}) {
35 unshift @{$subclass."::ISA"}, $config->{model};
36 $config->{model}->adopt($subclass)
37 if $config->{model}->can("adopt");
43 my $config = $class->config;
44 $config->{view} ||= "Maypole::View::TT";
45 $config->{view}->require;
46 die "Couldn't load the view class $config->{view}: $@" if $@;
47 $config->{display_tables} ||= [ @{$class->config->{tables}} ];
48 $class->view_object($class->config->{view}->new);
54 # See Maypole::Workflow before trying to understand this.
56 $class->init unless $class->init_done;
57 my $r = bless { config => $class->config }, $class;
61 $r->model_class($r->config->{model}->class_of($r, $r->{table}));
62 my $status = $r->is_applicable;
64 $status = $r->call_authenticate;
65 if ($r->debug and $status != OK and $status != DECLINED) {
66 $r->view_object->error($r,
67 "Got unexpected status $status from calling authentication");
69 return $status unless $status == OK;
70 $r->additional_data();
72 $r->model_class->process($r);
74 # Otherwise, it's just a plain template.
75 $r->call_authenticate; # No harm in it
76 delete $r->{model_class};
77 $r->{path} =~ s{/}{}; # De-absolutify
78 $r->template($r->{path});
81 if (!$r->{output}) { # You might want to do it yourself
82 $status = $r->view_object->process($r);
90 my $config = $self->config;
91 $config->{ok_tables} = {map {$_ => 1} @{$config->{display_tables}}};
92 warn "We don't have that table ($self->{table})"
93 if $self->debug and not $config->{ok_tables}{$self->{table}};
94 return DECLINED() unless exists $config->{ok_tables}{$self->{table}};
96 # Does the action method exist?
97 my $cv = $self->model_class->can($self->{action});
98 warn "We don't have that action ($self->{action})"
99 if $self->debug and not $cv;
100 return DECLINED() unless $cv;
103 $self->{method_attribs} = join " ", attributes::get($cv);
104 do { warn "$self->{action} not exported" if $self->debug;
106 } unless $self->{method_attribs} =~ /\bExported\b/i;
110 sub call_authenticate {
112 return $self->model_class->authenticate($self) if
113 $self->model_class->can("authenticate");
114 return $self->authenticate($self); # Interface consistency is a Good Thing
117 sub additional_data {}
119 sub authenticate { return OK }
123 Maypole - MVC web application framework
131 A large number of web programming tasks follow the same sort of pattern:
132 we have some data in a datasource, typically a relational database. We
133 have a bunch of templates provided by web designers. We have a number of
134 things we want to be able to do with the database - create, add, edit,
135 delete records, view records, run searches, and so on. We have a web
136 server which provides input from the user about what to do. Something in
137 the middle takes the input, grabs the relevant rows from the database,
138 performs the action, constructs a page, and spits it out.
140 Maypole aims to be the most generic and extensible "something in the
141 middle" - an MVC-based web application framework.
143 An example would help explain this best. You need to add a product
144 catalogue to a company's web site. Users need to list the products in
145 various categories, view a page on each product with its photo and
146 pricing information and so on, and there needs to be a back-end where
147 sales staff can add new lines, change prices, and delete out of date
148 records. So, you set up the database, provide some default templates
149 for the designers to customize, and then write an Apache handler like
152 package ProductDatabase;
153 use base 'Apache::MVC';
154 __PACKAGE__->set_database("dbi:mysql:products");
155 ProductDatabase->config->{uri_base} = "http://your.site/catalogue/";
156 ProductDatabase::Product->has_a("category" => ProductDatabase::Category);
160 my ($self, $request) = @_;
161 return OK if $request->{ar}->get_remote_host() eq "sales.yourcorp.com";
162 return OK if $request->{action} =~ /^(view|list)$/;
167 You then put the following in your Apache config:
169 <Location /catalogue>
170 SetHandler perl-script
171 PerlHandler ProductDatabase
174 And copy the templates found in F<templates/factory> into the
175 F<catalogue/factory> directory off the web root. When the designers get
176 back to you with custom templates, they are to go in
177 F<catalogue/custom>. If you need to do override templates on a
178 database-table-by-table basis, put the new template in
179 F<catalogue/I<table>>.
181 This will automatically give you C<add>, C<edit>, C<list>, C<view> and
182 C<delete> commands; for instance, a product list, go to
184 http://your.site/catalogue/product/list
186 For a full example, see the included "beer database" application.
190 There's some documentation for the workflow in L<Maypole::Workflow>,
191 but the basic idea is that a URL part like C<product/list> gets
192 translated into a call to C<ProductDatabase::Product-E<gt>list>. This
193 propagates the request with a set of objects from the database, and then
194 calls the C<list> template; first, a C<product/list> template if it
195 exists, then the C<custom/list> and finally C<factory/list>.
197 If there's another action you want the system to do, you need to either
198 subclass the model class, and configure your class slightly differently:
200 package ProductDatabase::Model;
201 use base 'Maypole::Model::CDBI';
203 sub supersearch :Exported {
204 my ($self, $request) = @_;
205 # Do stuff, get a bunch of objects back
206 $r->objects(\@objects);
207 $r->template("template_name");
210 Then your top-level application package should change the model class:
211 (Before calling C<setup>)
213 ProductDatabase->config->{model} = "ProductDatabase::Model";
215 (The C<:Exported> attribute means that the method can be called via the
216 URL C</I<table>/supersearch/...>.)
218 Alternatively, you can put the method directly into the specific model
221 sub ProductDatabase::Product::supersearch :Exported { ... }
223 By default, the view class uses Template Toolkit as the template
224 processor, and the model class uses C<Class::DBI>; it may help you to be
225 familiar with these modules before going much further with this,
226 although I expect there to be other subclasses for other templating
227 systems and database abstraction layers as time goes on. The article at
228 C<http://www.perl.com/pub/a/2003/07/15/nocode.html> is a great
229 introduction to the process we're trying to automate.
233 You should probably not use Maypole directly. Maypole is an abstract
234 class which does not specify how to communicate with the outside world.
235 The most popular subclass of Maypole is L<Apache::MVC>, which interfaces
236 the Maypole framework to Apache mod_perl.
238 If you are implementing Maypole subclasses, you need to provide at least
239 the C<parse_location> and C<send_output> methods. You may also want to
240 provide C<get_request> and C<get_template_root>. See the
241 L<Maypole::Workflow> documentation for what these are expected to do.
245 sub get_template_root { "." }
247 sub parse_location { die "Do not use Maypole directly; use Apache::MVC or similar" }
248 sub send_output{ die "Do not use Maypole directly; use Apache::MVC or similar" }
252 There's more documentation, examples, and a wiki at the Maypole web site:
254 http://maypole.simon-cozens.org/
258 Simon Cozens, C<simon@cpan.org>
262 You may distribute this code under the same terms as Perl itself.