Maypole...ritance(3)
Maypole::Manual::Inheritance π
okay bestie, so this is a page from the User Contributed Perl Documentation β it's the Maypole...ritance(3) one, aka Maypole::Manual::Inheritance. grab a boba, we're doing architecture today π§β¨
πΈ NAME πΈ
Maypole::Manual::Inheritance β the whole vibe of how a Maypole application is structured π¦
β¨ DESCRIPTION β¨
So basically this page walks you through the inheritance structure of two things: a basic Maypole app and a more advanced one. That's it, that's the whole thing. lowkey iconic.
π CONVENTIONS π
okay so before we get into it, here's the legend, the key, the little diagram decoder ring π
inheritance is drawn like this:
inheritance
+
|
+- -+
|
+
notes look like this β a target with a lil asterisk and a note hanging off it:
target *-------- note about the target
association is an arrow pointing from source to target, no cap:
source ------> target
π Structure of a standard Maypole application π
A minimal Maypole app β like the Beer database example from the Maypole synopsis, you know the one π β is literally just three things:
- a custom driver (or controller) class, and in this case it's
BeerDB.pm - a bunch of auto-generated model classes
- and a view class
and here's the whole situation drawn out, no filter:
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();
likeβ¦ look at it. it's giving family tree but make it Perl π
π Ouch, that's a lot of inheritence! π
fr fr, that is a LOT of inheritence. at some point in the future β probably Maypole 3.x β they're gonna move to Class::C3. so yeah. noted. π
π What about Maypole::Application β loading plugins π
okay so Maypole::Application's whole main character job is to insert the plugins into the hierarchy. that's it. that's the job.
it's ALSO its responsibility to decide which frontend to use. here's the order of operations, bestie:
- it builds the list of plugins π οΈ
- then it pushes them onto the driver's
@ISA - then it pushes the frontend onto the end of the driver's
@ISA
which means method lookup searches all the plugins first, before it even thinks about searching the frontend, and finally Maypole itself. periodt.
β¨ from Maypole 2.11 onwards, Maypole::Application makes no appearance at all in the inheritance structure of a Maypole application. (in prior versions it would make itself inherit the plugins, and then insert itself into the hierarchy, but honestly? that was unnecessary. we don't do that anymore π
)
π§ Who builds the model? π§
first things first, remember we're talking about the standard, unmodified Maypole here. it is totally possible β and actually super common β to override some or all of this stage and build a customised model. see below for one approach (that's the An advanced Maypole application section, in part 2 bestie, patience π«Ά). also go peek at Maypole's setup_model() method.
the standard model gets built in 3 stages:
- first,
Maypole::setup_modelcallssetup_databaseon the Maypole model class β in this caseMaypole::Model::CDBI.setup_databasethen usesClass::DBI::Loaderto autogenerate individualClass::DBIclasses for each of the tables in the database (BeerDB::Beer,BeerDB::Pubetc). andClass::DBI::Loaderfigures out the appropriateClass::DBIsubclass and inserts it into each of these table classes'@ISA(that's theClass::DBI::<db_driver>you see in the diagrams) π₯ - next,
Maypole::setuppushesMaypole::Model::CDBIonto the@ISAarray of each of these classes. - finally, the relationships among these tables get set up. you can either do this manually, using the standard
Class::DBIsyntax for configuring table relationships, or you can tryClass::DBI::Relationship(which you can use viaMaypole::Plugin::Relationship). if you go the plugin route, you need to set up the relationships configuration before callingsetup().
β οΈ heads up: some people love the convenience of Class::DBI::Relationship, and other people genuinely cannot stand the abstraction. YMMV. π
more chaos incoming in part 2, don't go anywhere π¦β¨
An advanced Maypole application
okay bestie, level two π
we're calling this one BeerDB2.
Maypole is a framework, which means you can swap out different bits whenever you want. So what follows is one example of good practice β other people may do things differently and that is completely fine.
We're assuming this application is being built from the ground up, but ngl it will often be straightforward to adapt an existing Class::DBI application to this general model.
The main character idea here: the autogenerated Maypole model is used as a layer on top of a separate Class::DBI model. I'm gonna refer to that separate one as the 'Offline' model, and the Maypole classes as the 'Maypole' model. The whole point is that the Offline model can (potentially, or in actuality) be used as part of another application β maybe a command line program, maybe a cron script, whatever. π¦
The Offline model does not know about the Maypole model. The Maypole model does know about the Offline model. One-way street, no cap.
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 forClass::DBI::Loaderin the call tosetup().
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
- Non-Maypole applications using the Offline model are completely isolated from the Maypole application, and need not know it exists at all. π§
- Methods defined in the Maypole table classes override methods defined in the Offline table classes, because
BeerDB2::Maypole::Modelwas pushed onto the end of each Maypole table class's@ISA. Perl's depth first, left-to-right method lookup from e.g.BeerDB2::Beerstarts inBeerDB2::Beer, thenBeerDB2::Maypole::Model,Maypole::Model::CDBI,Maypole::Model::Base, andClass::DBI, before moving on toOfflineBeer::Beerand finallyOfflineBeer.
CAVEAT β if your Offline model overridesClass::DBImethods, these methods will not be overridden when called from the Maypole application, because the Maypole model provides an alternative path toClass::DBIwhich is searched first. The solution is to place such methods in a separate package, e.g.OfflineBeer::CDBI. Place this first in the@ISAof bothBeerDB2::Maypole::ModelandOfflineBeer. Note thatOfflineBeer::CDBIdoes not itself need to inherit fromClass::DBI. π
- 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). - Relationships defined in the Offline classes are inherited by the Maypole model.
- 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::PlainorMaypole::FormBuilder::Model::Plain setup_model()andload_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.
and that's a wrap, bestie π
this whole thing is from perl v5.42.0, dated 2025-07-25, and it's page Maypole...ritance(3) β go forth and inherit responsibly, mwah π
π¦ where this page comes from
- fedora 43: perl-Maypole 1:2.13-46.fc43 Β· GPL-1.0-or-later OR Artistic-1.0-Perl
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms