Maypole::Manual::InherUsercContributed Perl DocMaypole::Manual::Inheritance(3)NAMEMaypole::Manual::Inheritance - structure of a Maypole application
DESCRIPTION
Discusses the inheritance structure of a basic and a more advanced
Maypole application.
CONVENTIONS
inheritance
+
|
+- -+
|
+
notes
target *-------- note about the target
association
source ------> target
Structure of a standard Maypole application
A minimal Maypole application (such as the Beer database example from
the Maypole synopsis) consists of a custom driver (or controller) class
(BeerDB.pm), a set of auto-generated model classes, and a view class:
THE DRIVER
+------- init() is a factory method,
1 Maypole | it sets up the view
Maypole::Config <----- config(); | classes
model(); init(); *-------+ THE VIEW
| view_object(); -------+
| +--------------* setup(); | Maypole::View::Base
| | + | +
| | | | 1 |
| | PLUGINS Apache::MVC *-----+ +-----> Maypole::View::TT
| | + + | (or another view class)
| | | | |
| | +-----+-----+ |
| | | |
| | BeerDB +----- or CGI::Maypole
| | or MasonX:::Maypole
| |
| setup() is a factory method,
| it sets up the model
| classes
|
| THE MODEL
|
| Maypole::Model::Base Class::DBI
| + + +
| | | |
+-------> Maypole::Model::CDBI Class::DBI::<db_driver>
+ +
| |
+------------+--------+-------+---------+
| | | | |
BeerDB::Pub | BeerDB::Beer | BeerDB::Brewery
beers(); | pubs(); | beers();
| brewery(); |
| style(); |
BeerDB::Handpump |
pub(); BeerDB::Style
beer(); beers();
Ouch, that's a lot of inheritence!
Yes, that's a lot of inheritence, at some point in the future -
probably Maypole 3.x we will move to Class::C3
What about Maypole::Application - loading plugins
The main job of Maypole::Application is to insert the plugins into the
hierarchy. It is also the responsibility of Maypole::Application to
decide which frontend to use. It builds the list of plugins, then
pushes them onto the driver's @ISA, then pushes the frontend onto the
end of the driver's @ISA. So method lookup first searches all the
plugins, before searching the frontend and finally Maypole itself.
From Maypole 2.11, Maypole::Application makes no appearance in the
inheritance structure of a Maypole application. (In prior versions,
Maypole::Application would make itself inherit the plugins, and then
insert itself in the hierarchy, but this was unnecessary).
Who builds the model?
First, remember we are talking about the standard, unmodified Maypole
here. It is possible, and common, to override some or all of this stage
and build a customised model. See below - An advanced Maypole
application - for one approach. Also, see Maypole's "setup_model()"
method.
The standard model is built in 3 stages.
First, "Maypole::setup_model" calls "setup_database" on the Maypole
model class, in this case Maypole::Model::CDBI. "setup_database" then
uses Class::DBI::Loader to autogenerate individual Class::DBI classes
for each of the tables in the database ("BeerDB::Beer", "BeerDB::Pub"
etc). Class::DBI::Loader identifies the appropriate Class::DBI
subclass and inserts it into each of these table classes' @ISA (
"Class::DBI::<db_driver>" in the diagrams)..
Next, "Maypole::setup" pushes Maypole::Model::CDBI onto the @ISA array
of each of these classes.
Finally, the relationships among these tables are set up. Either do
this manually, using the standard Class::DBI syntax for configuring
table relationships, or try Class::DBI::Relationship (which you can use
via Maypole::Plugin::Relationship). If you use the plugin, you need to
set up the relationships configuration before calling "setup()". Be
aware that some people like the convenience of
Class::DBI::Relationship, others dislike the abstraction. YMMV.
An advanced Maypole application
We'll call it "BeerDB2".
Maypole is a framework, and you can replace different bits as you wish.
So what follows is one example of good practice, other people may do
things differently.
We assume this application is being built from the ground up, but it
will often be straightforward to adapt an existing Class::DBI
application to this general model.
The main idea is that the autogenerated Maypole model is used as a
layer on top of a separate Class::DBI model. I am going to refer to
this model as the 'Offline' model, and the Maypole classes as the
'Maypole' model. The idea is that the Offline model can (potentially or
in actuality) be used as part of another application, perhaps a command
line program or a cron script, whatever. The Offline model does not
know about the Maypole model, whereas the Maypole model does know about
the Offline model.
Let's call the offline model "OfflineBeer". As a traditional Class::DBI
application, individual table classes in this model will inherit from a
common base ("OfflineBeer"), which inherits from Class::DBI).
One advantage of this approach is that you can still use Maypole's
autogenerated model. Another is that you do not mix online and offline
code in the same packages.
Building it
Build a driver in a similar way as for the basic app, calling "setup()"
after setting up all the configuration.
It is a good habit to use a custom Maypole model class for each
application, as it's a likely first target for customisation. Start it
like this:
package BeerDB2::Maypole::Model;
use strict;
use warnings;
use base 'Maypole::Model::CDBI';
1;
You can add methods which should be shared by all table classes to this
package as and when required.
Configure it like this, before the "setup()" call in the driver class:
# in package BeerDB2
__PACKAGE__->config->model('BeerDB2::Maypole::Model');
__PACKAGE__->setup;
The "setup()" call will ensure your custom model is loaded via
"require".
Note: by default, this will create Maypole/CDBI classes for all the
tables in the database. You can control this by passing options for
Class::DBI::Loader in the call to "setup()".
For each class in the model, you need to create a separate file. So for
"BeerDB2::Beer", you would write:
package BeerDB2::Beer;
use strict;
use warnings;
use base 'OfflineBeer::Beer';
1;
From Maypole 2.11, this package will be loaded automatically during
"setup()", and "BeerDB2::Maypole::Model" is pushed onto it's @ISA.
Configure relationships either in the individual "OfflineBeer::*"
classes, or else all together in "OfflineBeer" itself i.e. not in the
Maypole model. This way, you only define the relationships in one
place.
The resulting model looks like this:
Class::DBI
MAYPOLE 'MODEL' |
|
Maypole::Model::Base |
+ |
| +-----------------+----+-----------------+
| | | |
| | | |
Maypole::Model::CDBI | | OFFLINE
+ | | MODEL
| | |
BeerDB2::Maypole::Model Class::DBI::<db_driver> OfflineBeer
+ + +
| | |
+-----------------------------+ |
| |
+--- BeerDB2::Pub --------+ OfflineBeer::Pub --------+
| beers(); |
| |
| OfflineBeer::Handpump ---+
| beer(); |
| pub(); |
| |
+--- BeerDB2::Beer -------+ OfflineBeer::Beer -------+
| pubs(); |
| brewery(); |
| style(); |
| |
+--- BeerDB2::Style ------+ OfflineBeer::Style ------+
| beers(); |
| |
+--- BeerDB2::Brewery ----+ OfflineBeer::Brewery ----+
beers();
Features
1. Non-Maypole applications using the Offline model are completely
isolated from the Maypole application, and need not know it exists at
all.
2. Methods defined in the Maypole table classes, override methods
defined in the Offline table classes, because "BeerDB2::Maypole::Model"
was pushed onto the end of each Maypole table class's @ISA. Perl's
depth first, left-to-right method lookup from e.g. "BeerDB2::Beer"
starts in "BeerDB2::Beer", then "BeerDB2::Maypole::Model",
"Maypole::Model::CDBI", "Maypole::Model::Base", and "Class::DBI",
before moving on to "OfflineBeer::Beer" and finally "OfflineBeer".
CAVEAT - if your Offline model overrides Class::DBI methods, these
methods will not be overridden when called from the Maypole
application, because the Maypole model provides an alternative path to
Class::DBI which is searched first. The solution is to place such
methods in a separate package, e.g. "OfflineBeer::CDBI". Place this
first in the @ISA of both "BeerDB2::Maypole::Model" and "OfflineBeer".
Note that "OfflineBeer::CDBI" does not itself need to inherit from
Class::DBI.
3. Methods defined in the Maypole model base class
("BeerDB2::Maypole::Model"), override methods in the individual Offline
table classes, and in the Offline model base class ("Offline").
4. Relationships defined in the Offline classes are inherited by the
Maypole model.
5. The Maypole model has full access to the underlying Offline model.
Theory
This layout illustrates more clearly why the Maypole model may be
thought of as part of the controller, rather than part of the model of
MVC. Its function is to mediate web requests, translating them into
method calls on the Offline model, munging the results, and returning
them via the Maypole request object.
Another way of thinking about it is that Maypole implements a two-layer
controller. The first layer translates a raw request into a single
method call on the Maypole model layer, which then translates that call
into one or more calls on the underlying model.
Whatever label you prefer to use, this approach provides for clear
separation of concerns between the underlying model and the web/user
interface, and that's what it's all about.
Advanced applications - building the model by hand ** TODO
- using Maypole::Model::CDBI::Plain or
Maypole::FormBuilder::Model::Plain - setup_model() and
load_model_subclass() - cutting out all those separate paths to CDBI -
they're confusing
Method inheritance ** TODO
More description of Perl's left-to-right, depth-first method lookup,
and where it's particularly important in Maypole.
AUTHOR
David Baird, "<cpan@riverside-cms.co.uk>"
COPYRIGHT & LICENSE
Copyright 2005 David Baird, All Rights Reserved.
This text is free documentation; you can redistribute it and/or modify
it under the same terms as the Perl documentation itself.
perl v5.14.1 2007-05-18 Maypole::Manual::Inheritance(3)