]> git.decadent.org.uk Git - maypole.git/blobdiff - doc/Beer.pod
Loads more documentation hacking.
[maypole.git] / doc / Beer.pod
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