X-Git-Url: https://git.decadent.org.uk/gitweb/?a=blobdiff_plain;f=doc%2FBeer.pod;h=4c4093fa3c31e93f0e367672a63e4f3754c08e7e;hb=2c2a72adcc01c1260b410103fa6bab06623f04be;hp=fa8fe8455b78b37879551013dbe2ceadaf441938;hpb=2c68753ecffdb68833e8dd65bde7d2248e33d317;p=maypole.git diff --git a/doc/Beer.pod b/doc/Beer.pod index fa8fe84..4c4093f 100644 --- a/doc/Beer.pod +++ b/doc/Beer.pod @@ -1,10 +1,187 @@ =head1 The Beer Database, Twice We briefly introduced the "beer database" example in the L -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. This package is called the B, 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, +which is the Apache C based version of Maypole; there's also +a CGI version, C, and a command-line version for +debugging, C, but C 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, 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, which takes a +DBI connect string. So this one line declares that we're using a C +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. + +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 which returns a hash reference +of the application's whole configuration. We can use this to set some +parameters; the C is used as the canonical URL of the base +of this application, and Maypole uses it to construct links. + +By defining C, we say that any listings we do with the +C and C templates should be arranged in sets of pages, with +a maximum of 10 items on each page. If we didn't declare that, C +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 which acts as a linking table in a many-to-many relationship +between the C and C 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. + +=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, this is an set of instructions to +C 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 table with an ID of 2. + +The usual C way to do this involves the C and +C methods, but I can never remember how to use them, so I came +up with the C 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 acts on a C object and +defines relationships between tables in a fairly free-form style. +The equivalent in ordinary C 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