The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

Maypole Model Classes

Class::DBI

Maypole::Model::CDBI

Maypole model classes are pretty evil. The very first thing a Maypole model class will do in a Maypole application is to cause a load of table-based classes to come into being, and then assimilate them.

What I mean by this is that when you set up a Maypole application, in your driver class, you'll say something like this:

    BeerDB->setup("dbi:mysql:beerdb");

setup is a Maypole method, and it hands its parameter to the model class. In our case, the model class is a DBI connect string, because that's what Maypole::Model::CDBI, the Class::DBI-based model expects. Maypole::Model::CDBI has a method called setup_database that creates all the Class::DBI table classes after connecting to the database with that connect string. It does this by using Class::DBI::Loader, a utility module which asks a database about its schema and sets up classes such as BeerDB::Beer to inherit from Class::DBI. This is just doing automatically what we did manually in our examples above.

Now it gets interesting. The names of these classes are stashed away in the application's configuration, and then Maypole forcibly has these classes inherit from the model class. Our BeerDB::Beer now inherits both from Class::DBI and Maypole::Model::CDBI.

This is useful for two reasons. The first reason is that Maypole::Model::CDBI is stuffed full of Class::DBI goodies that make writing Maypole applications a lot easier:

    package Maypole::Model::CDBI;
    use base qw(Maypole::Model::Base Class::DBI);
    use Class::DBI::AsForm;
    use Class::DBI::FromCGI;
    use Class::DBI::Loader;
    use Class::DBI::AbstractSearch;
    use Class::DBI::Plugin::RetrieveAll;
    use Class::DBI::Pager;

We'll meet most of these goodies in StandardTemplates.pod, where we explain how Maypole::Model::CDBI works.

The second reason why we want our table classes to inherit from Maypole::Model::CDBI is because CDBI provides a useful set of default actions. So what's an action, and why are they useful?

Extending a model class with actions

Maypole operates primarily by turning URLs into method calls on a model class. All that the model stage of Maypole's operation does, when it comes down to it, is maintain a mapping of tables to classes, and despatch a HTTP request to a method call on the relevant table class. This means that if you request a URL such as

    http://localhost/beerdb/brewery/delete/20

Maypole's first stage of operation is to turn that into BeerDB::Brewery->delete(20). Now, it's slightly more complex than that. Firstly because it doesn't actually pass the parameter 20, but it passes an object representing row 20 in the database, but we can gloss over that for the second. No, the real issue is that Maypole does not allow you to call any method in the table class; that would be somewhat insecure.

Instead, Maypole makes a distinction between the kind of methods that only the class itself and other Perl code can call, and the kind of methods that anyone can call from a URL. This latter set of methods are called exported methods, and exporting is done with by means of Perl attributes. You define a method to be exported like so:

    sub drink :Exported {

This will allow the user to access /beerdb/beer/drink over the web. An exported method accompanied with a template to render its output is sometimes called an action.

Maypole model classes like Maypole::Model::CDBI come with a relatively handy set of actions which are all you need to set up a CRUD database front-end: viewing a row in a database, editing it, adding a new one, deleting, and so on. The most important thing about Maypole, though, is that it doesn't stop there. You can add your own.

For instance, in our beer database application, we could create a BeerDB::Beer package, and put some additional actions in there.

    package BeerDB::Beer;
    sub top_five :Exported {
        my ($class, $r) = @_;
        $r->{objects} = [ ($r->retrieve_all_sorted_by("score"))[-5..-1] ];
    }

Our action is called as a class method with the Maypole request object. It uses the Class::DBI::Plugin::RetrieveAll module's retrieve_all_sorted_by mixin to get the top five scoring beers, and puts these in the objects slot of the request of object. Next, the view class will come along and process the top_five template with these five beers.

We'll look more at how to put together actions in the StandardTemplates.pod chapter and our case studies.

What Maypole wants from a model

Building your own model class