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 $r->call_authenticate; # No harm in it
66 delete $r->{model_class};
67 $r->{path} =~ s{/}{}; # De-absolutify
68 $r->template($r->{path});
70 return $r->view_object->process($r);
75 my $config = $self->config;
76 $config->{ok_tables} = {map {$_ => 1} @{$config->{display_tables}}};
77 #warn "We don't have that table ($self->{table})"
78 # unless $config->{ok_tables}{$self->{table}};
79 return DECLINED() unless exists $config->{ok_tables}{$self->{table}};
81 # Does the action method exist?
82 my $cv = $self->model_class->can($self->{action});
83 #warn "We don't have that action ($self->{action})" unless $cv;
84 return DECLINED() unless $cv;
87 $self->{method_attribs} = join " ", attributes::get($cv);
88 do { # warn "$self->{action} not exported";
90 } unless $self->{method_attribs} =~ /\bExported\b/i;
94 sub call_authenticate {
96 return $self->model_class->authenticate($self) if
97 $self->model_class->can("authenticate");
98 return $self->authenticate($self); # Interface consistency is a Good Thing
101 sub additional_data {}
103 sub authenticate { return OK }
107 Maypole - MVC web application framework
115 A large number of web programming tasks follow the same sort of pattern:
116 we have some data in a datasource, typically a relational database. We
117 have a bunch of templates provided by web designers. We have a number of
118 things we want to be able to do with the database - create, add, edit,
119 delete records, view records, run searches, and so on. We have a web
120 server which provides input from the user about what to do. Something in
121 the middle takes the input, grabs the relevant rows from the database,
122 performs the action, constructs a page, and spits it out.
124 Maypole aims to be the most generic and extensible "something in the
125 middle" - an MVC-based web application framework.
127 An example would help explain this best. You need to add a product
128 catalogue to a company's web site. Users need to list the products in
129 various categories, view a page on each product with its photo and
130 pricing information and so on, and there needs to be a back-end where
131 sales staff can add new lines, change prices, and delete out of date
132 records. So, you set up the database, provide some default templates
133 for the designers to customize, and then write an Apache handler like
136 package ProductDatabase;
137 use base 'Apache::MVC';
138 __PACKAGE__->set_database("dbi:mysql:products");
139 ProductDatabase->config->{uri_base} = "http://your.site/catalogue/";
140 ProductDatabase::Product->has_a("category" => ProductDatabase::Category);
144 my ($self, $request) = @_;
145 return OK if $request->{ar}->get_remote_host() eq "sales.yourcorp.com";
146 return OK if $request->{action} =~ /^(view|list)$/;
151 You then put the following in your Apache config:
153 <Location /catalogue>
154 SetHandler perl-script
155 PerlHandler ProductDatabase
158 And copy the templates found in F<templates/factory> into the
159 F<catalogue/factory> directory off the web root. When the designers get
160 back to you with custom templates, they are to go in
161 F<catalogue/custom>. If you need to do override templates on a
162 database-table-by-table basis, put the new template in
163 F<catalogue/I<table>>.
165 This will automatically give you C<add>, C<edit>, C<list>, C<view> and
166 C<delete> commands; for instance, a product list, go to
168 http://your.site/catalogue/product/list
170 For a full example, see the included "beer database" application.
174 There's some documentation for the workflow in L<Maypole::Workflow>,
175 but the basic idea is that a URL part like C<product/list> gets
176 translated into a call to C<ProductDatabase::Product-E<gt>list>. This
177 propagates the request with a set of objects from the database, and then
178 calls the C<list> template; first, a C<product/list> template if it
179 exists, then the C<custom/list> and finally C<factory/list>.
181 If there's another action you want the system to do, you need to either
182 subclass the model class, and configure your class slightly differently:
184 package ProductDatabase::Model;
185 use base 'Maypole::Model::CDBI';
187 sub supersearch :Exported {
188 my ($self, $request) = @_;
189 # Do stuff, get a bunch of objects back
190 $r->objects(\@objects);
191 $r->template("template_name");
194 Then your top-level application package should change the model class:
195 (Before calling C<setup>)
197 ProductDatabase->config->{model_class} = "ProductDatabase::Model";
199 (The C<:Exported> attribute means that the method can be called via the
200 URL C</I<table>/supersearch/...>.)
202 Alternatively, you can put the method directly into the specific model
205 sub ProductDatabase::Product::supersearch :Exported { ... }
207 By default, the view class uses Template Toolkit as the template
208 processor, and the model class uses C<Class::DBI>; it may help you to be
209 familiar with these modules before going much further with this,
210 although I expect there to be other subclasses for other templating
211 systems and database abstraction layers as time goes on. The article at
212 C<http://www.perl.com/pub/a/2003/07/15/nocode.html> is a great
213 introduction to the process we're trying to automate.
217 You should probably not use Maypole directly. Maypole is an abstract
218 class which does not specify how to communicate with the outside world.
219 The most popular subclass of Maypole is L<Apache::MVC>, which interfaces
220 the Maypole framework to Apache mod_perl.
222 If you are implementing Maypole subclasses, you need to provide at least
223 the C<get_request> and C<parse_location> methods. See the
224 L<Maypole::Workflow> documentation for what these are expected to do.
228 sub get_request { die "Do not use Maypole directly; use Apache::MVC or similar" }
229 sub parse_location { die "Do not use Maypole directly; use Apache::MVC or similar" }
233 Simon Cozens, C<simon@cpan.org>
237 You may distribute this code under the same terms as Perl itself.