]> git.decadent.org.uk Git - maypole.git/commitdiff
Loads more documentation hacking.
authorSimon Cozens <simon@simon-cozens.org>
Thu, 8 Apr 2004 15:09:29 +0000 (15:09 +0000)
committerSimon Cozens <simon@simon-cozens.org>
Thu, 8 Apr 2004 15:09:29 +0000 (15:09 +0000)
git-svn-id: http://svn.maypole.perl.org/Maypole/trunk@125 48953598-375a-da11-a14b-00016c27c3ee

doc/About.pod
doc/Beer.pod
doc/Model.pod
doc/Request.pod

index 87ef1958fd74ee92b5b09d987bdef7cbb9ada6a2..b5cf3e7a2774f89ffb36875eef06f2ba0ae9c032 100644 (file)
@@ -92,8 +92,7 @@ you need to write for a simple database front-end:
 
     package BeerDB;
     use base 'Apache::MVC';
-    use Class::DBI::Loader::Relationship;
-    BeerDB->set_database("dbi:SQLite:t/beerdb.db");
+    BeerDB->setup("dbi:SQLite:t/beerdb.db");
     BeerDB->config->{uri_base} = "http://localhost/beerdb/";
     BeerDB->config->{rows_per_page} = 10;
     BeerDB->config->{display_tables} = [qw[beer brewery pub style]];
@@ -104,6 +103,8 @@ you need to write for a simple database front-end:
         integer => [qw/style brewery score/],
         date => [ qw/date/],
     );
+
+    use Class::DBI::Loader::Relationship;
     BeerDB->config->{loader}->relationship($_) for (
         "a brewery produces beers",
         "a style defines beers",
index fa8fe8455b78b37879551013dbe2ceadaf441938..4c4093fa3c31e93f0e367672a63e4f3754c08e7e 100644 (file)
 =head1 The Beer Database, Twice
 
 We briefly introduced the "beer database" example in the L<About.pod>
-material; 
+material, where we presented its driver class as a fait accompli. Where
+did all that code come from, and what does it actually mean?
 
 =head2 The big beer problem
 
+I have a seriously bad habit. This is not the beer problem; this is a
+programming problem. The bad habit is that when I approach a problem I
+want to solve, I get sidetracked deeper and deeper trying to solve more
+and more generic problems, and then, satisfied with solving the generic
+problem, I never get around to solving the specific problem. I always
+write libraries for people writing libraries, and never write
+applications.
+
+The thing with really good beer is that it commands you to drink more of
+it, and then by the morning you can't remember whether it was any good
+or not. After buying several bottles of some random central African
+lager on a dim recollection that it was really good and having it turn
+out to be abysmal, this really became a problem. If only I could have a
+database that I updated every time I buy a new beer, I'd be able to tell
+whether or not I should buy that Lithuanian porter again or whether it
+would be quicker just to flush my money down the toilet and cut out the
+middle-man.
+
+The only problem with databases on Unix is that there isn't really a
+nice way to get data into them. There isn't really a Microsoft Access
+equivalent which can put a simple forms-based front-end onto an
+arbitrary database, and if there is, I either didn't like it or couldn't
+find it, and after a few brews, you really don't want to be trying to type
+in your tasting notes in raw SQL.
+
+So you see a generic problem arising out of a specific problem here. I
+didn't want to solve the specific problem of the beer database, because
+I'd already had another idea for a database that needed a front-end. So
+for two years, I sat on this great idea of having a database of tasting
+notes for beer. I even bought that damned African beer again. Enough was
+enough. I wrote Maypole.
+
 =head2 The easy way
 
+The first Maypole application was the beer database. We've already met
+it; it looks like this.
+
+    package BeerDB;
+    use base 'Apache::MVC';
+    BeerDB->set_database("dbi:SQLite:t/beerdb.db");
+    BeerDB->config->{uri_base} = "http://localhost/beerdb/";
+    BeerDB->config->{rows_per_page} = 10;
+    BeerDB->config->{display_tables} = [qw[beer brewery pub style]];
+    BeerDB::Brewery->untaint_columns( printable => [qw/name notes url/] );
+    BeerDB::Style->untaint_columns( printable => [qw/name notes/] );
+    BeerDB::Beer->untaint_columns(
+        printable => [qw/abv name price notes/],
+        integer => [qw/style brewery score/],
+        date => [ qw/date/],
+    );
+
+    use Class::DBI::Loader::Relationship;
+    BeerDB->config->{loader}->relationship($_) for (
+        "a brewery produces beers",
+        "a style defines beers",
+        "a pub has beers on handpumps");
+    1;
+
+Now, we can split this into four sections. Let's look at them one
+at a time. 
+
+=head3 Driver setup
+
+Here's the first section:
+
+    package BeerDB;
+    use base 'Apache::MVC';
+    BeerDB->setup("dbi:SQLite:t/beerdb.db");
+
+This is actually all you need for a functional database front-end. Everything
+else is configuration details. This says three things: we're an application
+called C<BeerDB>. This package is called the B<driver class>, because
+it's a relatively small class which defines how the whole application is
+going to run. 
+
+The second line says that our front-end is going to be C<Apache::MVC>,
+which is the Apache C<mod_perl> based version of Maypole; there's also
+a CGI version, C<CGI::Maypole>, and a command-line version for
+debugging, C<Maypole::CLI>, but C<Apache::MVC> is  usually the one you
+want.
+
+Thirdly we're going to need to set up our database with the given DBI
+connection string. Now the core of Maypole itself doesn't know about
+DBI; as we explained in L<Model.pod>, this argument is passed to our
+model class wholesale. As we haven't said anything about a model
+class, we get the default one, C<Maypole::Model::CDBI>, which takes a
+DBI connect string. So this one line declares that we're using a C<CDBI>
+model class and it sets up the database for us. In the same way, we
+don't say that we want a particular view class, so we get the default
+C<Maypole::View::TT>.
+
+At this point, everything is in place; we have our driver class, it uses
+a front-end, we have a model class and a view class, and we have a data
+source.
+
+=head3 Application configuration
+
+The next of our four sections is the configuration for the application itself.
+
+    BeerDB->config->{uri_base} = "http://localhost/beerdb/";
+    BeerDB->config->{rows_per_page} = 10;
+    BeerDB->config->{display_tables} = [qw[beer brewery pub style]];
+
+Maypole provides a method called C<config> which returns a hash reference
+of the application's whole configuration. We can use this to set some
+parameters; the C<uri_base> is used as the canonical URL of the base
+of this application, and Maypole uses it to construct links.
+
+By defining C<rows_per_page>, we say that any listings we do with the
+C<list> and C<search> templates should be arranged in sets of pages, with
+a maximum of 10 items on each page. If we didn't declare that, C<list>
+would try to put all the objects on one page, which could well be bad.
+
+Finally, we declare which tables we want our Maypole front-end to
+reference. If you remember from the schema, there's a table called
+C<handpump> which acts as a linking table in a many-to-many relationship
+between the C<pub> and C<beer> tables. As it's only a linking table, we
+don't want people poking with it directly, so we exclude it from the
+list of C<display_tables>.
+
+=head3 Editability
+
+The next section is the following set of lines:
+
+    BeerDB::Brewery->untaint_columns( printable => [qw/name notes url/] );
+    BeerDB::Style->untaint_columns( printable => [qw/name notes/] );
+    BeerDB::Beer->untaint_columns(
+        printable => [qw/abv name price notes/],
+        integer => [qw/style brewery score/],
+        date => [ qw/date/],
+    );
+
+As explained in L<StandardTemplates.pod>, this is an set of instructions to
+C<Class::DBI::FromCGI> regarding how the given columns should be edited.
+If we didn't have this section, we'd be able to view and delete records,
+but adding and editing them wouldn't work. It took me ages to work that
+one out.
+
+=head3 Relationships
+
+Finally, we want to explain to Maypole how the various tables relate to
+each other. This is done so that, for instance, when displaying a beer,
+the brewery does not appear as an integer like "2" but as the name of
+the brewery from the C<brewery> table with an ID of 2.
+
+The usual C<Class::DBI> way to do this involves the C<has_a> and
+C<has_many> methods, but I can never remember how to use them, so I came
+up with the C<Class::DBI::Loader::Relationship> module; this was another
+yak that needed shaving on the way to the beer database:
+
+    use Class::DBI::Loader::Relationship;
+    BeerDB->config->{loader}->relationship($_) for (
+        "a brewery produces beers",
+        "a style defines beers",
+        "a pub has beers on handpumps");
+    1;
+
+C<CDBIL::Relationship> acts on a C<Class::DBI::Loader> object and
+defines relationships between tables in a fairly free-form style.
+The equivalent in ordinary C<Class::DBI> would be:
+
+       BeerDB::Brewery->has_many(beers => "BeerDB::Beer");
+       BeerDB::Beer->has_a(brewery => "BeerDB::Brewery");
+       BeerDB::Style->has_many(beers => "BeerDB::Beer");
+       BeerDB::Beer->has_a(style => "BeerDB::Style");
+
+       BeerDB::Handpump->has_a(beer => "BeerDB::Beer");
+       BeerDB::Handpump->has_a(pub => "BeerDB::Pub");
+       BeerDB::Pub->has_many(beers => [ BeerDB::Handpump => 'beer' ]);
+       BeerDB::Beer->has_many(pubs => [ BeerDB::Handpump => 'pub' ]);
+
+Maypole's default templates will use this information to display, for
+instance, a list of a brewery's beers on the brewery view page.
+
+This is the complete beer database application; Maypole's default templates
+and the actions in the view class do the rest. But what if we want to a
+little more. How would we begin to extend this application?
+
 =head2 The hard way
index c5f5b18ec9a3db77fd09d8742899433967887750..bc54ffdb8105ef81a9e57e260c820816fbb445f1 100644 (file)
@@ -4,8 +4,106 @@
 
 =head2 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");
+
+C<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 C<Maypole::Model::CDBI>, the C<Class::DBI>-based model
+expects. C<Maypole::Model::CDBI> has a method called C<setup_database>
+that creates all the C<Class::DBI> table classes after connecting to the
+database with that connect string. It does this by using
+C<Class::DBI::Loader>, a utility module which asks a database
+about its schema and sets up classes such as C<BeerDB::Beer> to inherit
+from C<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 C<BeerDB::Beer> now inherits
+both from C<Class::DBI> and C<Maypole::Model::CDBI>.
+
+This is useful for two reasons. The first reason is that
+C<Maypole::Model::CDBI> is stuffed full of C<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 L<StandardTemplates.pod>, where we
+explain how C<Maypole::Model::CDBI> works.
+
+The second reason why we want our table classes to inherit from
+C<Maypole::Model::CDBI> is because C<CDBI> provides a useful set of 
+default actions. So what's an action, and why are they useful?
+
 =head2 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
+C<BeerDB::Brewery-E<gt>delete(20)>. Now, it's slightly more complex than
+that. Firstly because it doesn't actually pass the parameter C<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 B<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 C</beerdb/beer/drink> over the web.
+An exported method accompanied with a template to render its output is
+sometimes called an B<action>. 
+
+Maypole model classes like C<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
+C<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 C<Class::DBI::Plugin::RetrieveAll> module's
+C<retrieve_all_sorted_by> mixin to get the top five scoring beers, and
+puts these in the C<objects> slot of the request of object. Next, the
+view class will come along and process the C<top_five> template with
+these five beers.
+
+We'll look more at how to put together actions in the
+L<StandardTemplates.pod> chapter and our case studies.
+
 =head2 What Maypole wants from a model
 
 =head2 Building your own model class
index 80e4037cd9bd216f3bf707880efa213327ff456f..96f7c552ae954e378d696f61bfaf507654ed2211 100644 (file)
@@ -50,6 +50,24 @@ These hacks deal primarily with the presentation of data to the user,
 modifying the C<view> template or changing the way that the results of
 particular actions are displayed.
 
+=head3 Null Action
+
+You need an "action" which doesn't really do anything, but just formats
+up a template.
+
+B<Solution>: There are two ways to do this, depending on what precisely
+you need. If you just need to display a template, C<Apache::Template>
+style, with no Maypole objects in it, then you don't need to write any
+code; just create your template, and it will be available in the usual
+way.
+
+If, on the other hand, you want to display some data, and what you're
+essentially doing is a variant of the C<view> action, then you need to
+ensure that you have an exported action, as described in
+L<StandardTemplates.pod>:
+
+    sub my_view :Exported { }
+
 =head3 Template Switcheroo
 
 An action doesn't have any data of its own to display, but needs to display