=head1 NAME Maypole::Manual::Inheritance - structure of a Maypole application =head1 DESCRIPTION Discusses the inheritance structure of a basic and a more advanced Maypole application. =head1 CONVENTIONS =over 4 =item inheritance + | +-- --+ | | + =item notes target *-------- note about the target =item association source ------> target =back =head1 A standard Maypole application A minimal Maypole application (such as the Beer database) consists of a custom driver 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 | | | Apache::MVC *-----+ +-----> Maypole::View::TT | | + | (or another view class) | | | | | | PLUGINS | | | + | | | | +----- or CGI::Maypole | | BeerDB or MasonX:::Maypole | | | setup() is a factory method, | it sets up the model | classes | | THE MODEL | | Maypole::Model::Base Class::DBI | + + | | | +-------> Maypole::Model::CDBI + | +------------+--------+-------+---------+ | | | | | BeerDB::Pub | BeerDB::Beer | BeerDB::Brewery beers(); | pubs(); | beers(); | brewery(); | | style(); | BeerDB::Handpump | pub(); BeerDB::Style beer(); beers(); =head2 What about Maypole::Application - loading plugins The main job of L is to insert the plugins into the hierarchy. It ensures that L inherits from the first plugin, which inherits from the next, etc., until the last plugin inherits from the frontend. It is also the responsibility of L to decide which frontend to use. From Maypole 2.11, L makes no appearance in the inheritance structure of a Maypole application. (In prior versions, L would make itself inherit the plugins, and then insert itself in the hierarchy, but this was unnecessary). The order of inheritance is the same as the order in which plugins are supplied in the C statement. =head2 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. The standard model is built in 3 stages. First, C calls C on the Maypole model class, in this case L. C then uses L to autogenerate individual L classes for each of the tables in the database (C, C etc). Next, C B L onto the C<@ISA> array of each of these classes. Finally, the relationships among these tables are set up. Either do this manually, perhaps with the help of L, or use L. In the latter case, you need to set up the relationships configuration before calling C. =head1 An advanced Maypole application We'll call it C. 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 L application to this general model. The main idea is that the autogenerated Maypole model is used as a layer on top of a separate L 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 C. As a traditional L application, individual table classes in this model will inherit from a common base (C), which inherits from L). 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. =head2 Building it Build a driver in a similar way as for the basic app, calling C 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 C call in the driver class: # in package BeerDB2 __PACKAGE__->config->model('BeerDB2::Maypole::Model'); __PACKAGE__->setup; The C call will ensure your custom model is loaded via C. B: by default, this will create Maypole/CDBI classes for all the tables in the database. You can control this by passing options for L in the call to C. For each class in the model, you need to create a separate file. So for C, you would write: package BeerDB2::Beer; use strict; use warnings; use base 'OfflineBeer::Beer'; 1; This package will be loaded automatically during C, and C is B onto it's C<@ISA>. Configure relationships either in the individual C classes, or else all together in C 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 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(); =head3 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 C was unshifted onto the beginning of each Maypole table class's C<@ISA>. Perl's depth first, left-to-right method lookup from e.g. C starts in C, then C, C, C, and C, before moving on to C and finally C. Methods defined in the Maypole model base class (C), override methods in the individual Offline table classes, and in the Offline model base class (C). Relationships defined in the Offline classes are inherited by the Maypole model. The Maypole model has full access to the underlying Offline model. =head3 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.