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;
19 my $config = $calling_class->config;
20 $config->{model} ||= "Maypole::Model::CDBI";
21 $config->{model}->require;
22 $config->{model}->setup_database($config, $calling_class, @_);
23 for my $subclass (@{$config->{classes}}) {
25 unshift @{$subclass."::ISA"}, $config->{model};
26 $config->{model}->adopt($subclass)
27 if $config->{model}->can("adopt");
33 my $config = $class->config;
34 $config->{view} ||= "Maypole::View::TT";
35 $config->{view}->require;
36 $config->{display_tables} ||= [ @{$class->config->{tables}} ];
37 $class->view_object($class->config->{view}->new);
43 # See Maypole::Workflow before trying to understand this.
45 $class->init unless $class->init_done;
46 my $r = bless { config => $class->config }, $class;
50 $r->model_class($r->config->{model}->class_of($r, $r->{table}));
51 my $status = $r->is_applicable;
53 $status = $r->call_authenticate;
54 return $status unless $status == OK;
55 $r->additional_data();
57 $r->model_class->process($r);
59 # Otherwise, it's just a plain template.
60 delete $r->{model_class};
61 $r->{path} =~ s{/}{}; # De-absolutify
62 $r->template($r->{path});
64 return $r->view_object->process($r);
69 require Apache; require Apache::Request;
70 $self->{ar} = Apache::Request->new(Apache->request);
75 $self->{path} = $self->{ar}->uri;
76 my $loc = $self->{ar}->location;
77 $self->{path} =~ s/^$loc//; # I shouldn't need to do this?
78 $self->{path} ||= "frontpage";
79 my @pi = split /\//, $self->{path};
80 shift @pi while @pi and !$pi[0];
81 $self->{table} = shift @pi;
82 $self->{action} = shift @pi;
85 $self->{params} = { $self->{ar}->content };
86 $self->{query} = { $self->{ar}->args };
91 my $config = $self->config;
92 $config->{ok_tables} = {map {$_ => 1} @{$config->{display_tables}}};
93 warn "We don't have that table ($self->{table})"
94 unless $config->{ok_tables}{$self->{table}};
95 return DECLINED() unless exists $config->{ok_tables}{$self->{table}};
97 # Does the action method exist?
98 my $cv = $self->model_class->can($self->{action});
99 warn "We don't have that action ($self->{action})" unless $cv;
100 return DECLINED() unless $cv;
103 $self->{method_attribs} = join " ", attributes::get($cv);
104 do { warn "$self->{action} not exported";
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();
117 sub additional_data {}
119 sub authenticate { return OK }
125 Maypole - MVC web application framework
133 A large number of web programming tasks follow the same sort of pattern:
134 we have some data in a datasource, typically a relational database. We
135 have a bunch of templates provided by web designers. We have a number of
136 things we want to be able to do with the database - create, add, edit,
137 delete records, view records, run searches, and so on. We have a web
138 server which provides input from the user about what to do. Something in
139 the middle takes the input, grabs the relevant rows from the database,
140 performs the action, constructs a page, and spits it out.
142 Maypole aims to be the most generic and extensible "something in the
143 middle" - an MVC-based web application framework.
145 An example would help explain this best. You need to add a product
146 catalogue to a company's web site. Users need to list the products in
147 various categories, view a page on each product with its photo and
148 pricing information and so on, and there needs to be a back-end where
149 sales staff can add new lines, change prices, and delete out of date
150 records. So, you set up the database, provide some default templates
151 for the designers to customize, and then write an Apache handler like
154 package ProductDatabase;
156 __PACKAGE__->set_database("dbi:mysql:products");
157 BeerDB->config->{uri_base} = "http://your.site/catalogue/";
158 ProductDatabase::Product->has_a("category" => ProductDatabase::Category);
162 my ($self, $request) = @_;
163 return OK if $request->{ar}->get_remote_host() eq "sales.yourcorp.com";
164 return OK if $request->{action} =~ /^(view|list)$/;
169 You then put the following in your Apache config:
171 <Location /catalogue>
172 SetHandler perl-script
173 PerlHandler ProductDatabase
176 And copy the templates found in F<templates/factory> into the
177 F<catalogue/factory> directory off the web root. When the designers get
178 back to you with custom templates, they are to go in
179 F<catalogue/custom>. If you need to do override templates on a
180 database-table-by-table basis, put the new template in
181 F<catalogue/I<table>>.
183 This will automatically give you C<add>, C<edit>, C<list>, C<view> and
184 C<delete> commands; for instance, a product list, go to
186 http://your.site/catalogue/product/list
188 For a full example, see the included "beer database" application.
192 There's some documentation for the workflow in L<Maypole::Workflow>,
193 but the basic idea is that a URL part like C<product/list> gets
194 translated into a call to C<ProductDatabase::Product-E<gt>list>. This
195 propagates the request with a set of objects from the database, and then
196 calls the C<list> template; first, a C<product/list> template if it
197 exists, then the C<custom/list> and finally C<factory/list>.
199 If there's another action you want the system to do, you need to either
200 subclass the model class, and configure your class slightly differently:
202 package ProductDatabase::Model;
203 use base 'Maypole::Model::CDBI';
205 sub supersearch :Exported {
206 my ($self, $request) = @_;
207 # Do stuff, get a bunch of objects back
208 $r->objects(\@objects);
209 $r->template("template_name");
212 ProductDatabase->config->{model_class} = "ProductDatabase::Model";
214 (The C<:Exported> attribute means that the method can be called via the
215 URL C</I<table>/supersearch/...>.)
217 Alternatively, you can put the method directly into the specific model
220 sub ProductDatabase::Product::supersearch :Exported { ... }
222 By default, the view class uses Template Toolkit as the template
223 processor, and the model class uses C<Class::DBI>; it may help you to be
224 familiar with these modules before going much further with this,
225 although I expect there to be other subclasses for other templating
226 systems and database abstraction layers as time goes on. The article at
227 C<http://www.perl.com/pub/a/2003/07/15/nocode.html> is a great
228 introduction to the process we're trying to automate.
232 Simon Cozens, C<simon@cpan.org>
236 You may distribute this code under the same terms as Perl itself.