2 use base qw(Class::Accessor Class::Data::Inheritable);
4 use UNIVERSAL::require;
5 use Apache::Constants ":common";
9 __PACKAGE__->mk_classdata($_) for qw( config init_done view_object );
10 __PACKAGE__->mk_accessors ( qw( ar params query objects model_class
11 args action template ));
12 __PACKAGE__->config({});
13 __PACKAGE__->init_done(0);
17 my $calling_class = shift;
18 $calling_class = ref $calling_class if ref $calling_class;
22 *{$calling_class."::handler"} = sub { Maypole::handler($calling_class, @_) };
24 my $config = $calling_class->config;
25 $config->{model} ||= "Maypole::Model::CDBI";
26 $config->{model}->require;
27 $config->{model}->setup_database($config, $calling_class, @_);
28 for my $subclass (@{$config->{classes}}) {
30 unshift @{$subclass."::ISA"}, $config->{model};
31 $config->{model}->adopt($subclass)
32 if $config->{model}->can("adopt");
38 my $config = $class->config;
39 $config->{view} ||= "Maypole::View::TT";
40 $config->{view}->require;
41 $config->{display_tables} ||= [ @{$class->config->{tables}} ];
42 $class->view_object($class->config->{view}->new);
48 # See Maypole::Workflow before trying to understand this.
50 $class->init unless $class->init_done;
51 my $r = bless { config => $class->config }, $class;
55 $r->model_class($r->config->{model}->class_of($r, $r->{table}));
56 my $status = $r->is_applicable;
58 $status = $r->call_authenticate;
59 return $status unless $status == OK;
60 $r->additional_data();
62 $r->model_class->process($r);
64 # Otherwise, it's just a plain template.
65 delete $r->{model_class};
66 $r->{path} =~ s{/}{}; # De-absolutify
67 $r->template($r->{path});
69 return $r->view_object->process($r);
74 my $config = $self->config;
75 $config->{ok_tables} = {map {$_ => 1} @{$config->{display_tables}}};
76 warn "We don't have that table ($self->{table})"
77 unless $config->{ok_tables}{$self->{table}};
78 return DECLINED() unless exists $config->{ok_tables}{$self->{table}};
80 # Does the action method exist?
81 my $cv = $self->model_class->can($self->{action});
82 warn "We don't have that action ($self->{action})" unless $cv;
83 return DECLINED() unless $cv;
86 $self->{method_attribs} = join " ", attributes::get($cv);
87 do { warn "$self->{action} not exported";
89 } unless $self->{method_attribs} =~ /\bExported\b/i;
93 sub call_authenticate {
95 return $self->model_class->authenticate($self) if
96 $self->model_class->can("authenticate");
97 return $self->authenticate($self); # Interface consistency is a Good Thing
100 sub additional_data {}
102 sub authenticate { return OK }
106 Maypole - MVC web application framework
114 A large number of web programming tasks follow the same sort of pattern:
115 we have some data in a datasource, typically a relational database. We
116 have a bunch of templates provided by web designers. We have a number of
117 things we want to be able to do with the database - create, add, edit,
118 delete records, view records, run searches, and so on. We have a web
119 server which provides input from the user about what to do. Something in
120 the middle takes the input, grabs the relevant rows from the database,
121 performs the action, constructs a page, and spits it out.
123 Maypole aims to be the most generic and extensible "something in the
124 middle" - an MVC-based web application framework.
126 An example would help explain this best. You need to add a product
127 catalogue to a company's web site. Users need to list the products in
128 various categories, view a page on each product with its photo and
129 pricing information and so on, and there needs to be a back-end where
130 sales staff can add new lines, change prices, and delete out of date
131 records. So, you set up the database, provide some default templates
132 for the designers to customize, and then write an Apache handler like
135 package ProductDatabase;
137 __PACKAGE__->set_database("dbi:mysql:products");
138 ProductDatabase->config->{uri_base} = "http://your.site/catalogue/";
139 ProductDatabase::Product->has_a("category" => ProductDatabase::Category);
143 my ($self, $request) = @_;
144 return OK if $request->{ar}->get_remote_host() eq "sales.yourcorp.com";
145 return OK if $request->{action} =~ /^(view|list)$/;
150 You then put the following in your Apache config:
152 <Location /catalogue>
153 SetHandler perl-script
154 PerlHandler ProductDatabase
157 And copy the templates found in F<templates/factory> into the
158 F<catalogue/factory> directory off the web root. When the designers get
159 back to you with custom templates, they are to go in
160 F<catalogue/custom>. If you need to do override templates on a
161 database-table-by-table basis, put the new template in
162 F<catalogue/I<table>>.
164 This will automatically give you C<add>, C<edit>, C<list>, C<view> and
165 C<delete> commands; for instance, a product list, go to
167 http://your.site/catalogue/product/list
169 For a full example, see the included "beer database" application.
173 There's some documentation for the workflow in L<Maypole::Workflow>,
174 but the basic idea is that a URL part like C<product/list> gets
175 translated into a call to C<ProductDatabase::Product-E<gt>list>. This
176 propagates the request with a set of objects from the database, and then
177 calls the C<list> template; first, a C<product/list> template if it
178 exists, then the C<custom/list> and finally C<factory/list>.
180 If there's another action you want the system to do, you need to either
181 subclass the model class, and configure your class slightly differently:
183 package ProductDatabase::Model;
184 use base 'Maypole::Model::CDBI';
186 sub supersearch :Exported {
187 my ($self, $request) = @_;
188 # Do stuff, get a bunch of objects back
189 $r->objects(\@objects);
190 $r->template("template_name");
193 Then your top-level application package should change the model class:
194 (Before calling C<setup>)
196 ProductDatabase->config->{model_class} = "ProductDatabase::Model";
198 (The C<:Exported> attribute means that the method can be called via the
199 URL C</I<table>/supersearch/...>.)
201 Alternatively, you can put the method directly into the specific model
204 sub ProductDatabase::Product::supersearch :Exported { ... }
206 By default, the view class uses Template Toolkit as the template
207 processor, and the model class uses C<Class::DBI>; it may help you to be
208 familiar with these modules before going much further with this,
209 although I expect there to be other subclasses for other templating
210 systems and database abstraction layers as time goes on. The article at
211 C<http://www.perl.com/pub/a/2003/07/15/nocode.html> is a great
212 introduction to the process we're trying to automate.
216 You should probably not use Maypole directly. Maypole is an abstract
217 class which does not specify how to communicate with the outside world.
218 The most popular subclass of Maypole is L<Apache::MVC>, which interfaces
219 the Maypole framework to Apache mod_perl.
221 If you are implementing Maypole subclasses, you need to provide at least
222 the C<get_request> and C<parse_location> methods. See the
223 L<Maypole::Workflow> documentation for what these are expected to do.
227 sub get_request { die "Do not use Maypole directly; use Apache::MVC or similar" }
228 sub parse_location { die "Do not use Maypole directly; use Apache::MVC or similar" }
232 Simon Cozens, C<simon@cpan.org>
236 You may distribute this code under the same terms as Perl itself.