1 =head1 The Beer Database, Twice
3 We briefly introduced the "beer database" example in the L<About.pod>
4 material, where we presented its driver class as a fait accompli. Where
5 did all that code come from, and what does it actually mean?
7 =head2 The big beer problem
9 I have a seriously bad habit. This is not the beer problem; this is a
10 programming problem. The bad habit is that when I approach a problem I
11 want to solve, I get sidetracked deeper and deeper trying to solve more
12 and more generic problems, and then, satisfied with solving the generic
13 problem, I never get around to solving the specific problem. I always
14 write libraries for people writing libraries, and never write
17 The thing with really good beer is that it commands you to drink more of
18 it, and then by the morning you can't remember whether it was any good
19 or not. After buying several bottles of some random central African
20 lager on a dim recollection that it was really good and having it turn
21 out to be abysmal, this really became a problem. If only I could have a
22 database that I updated every time I buy a new beer, I'd be able to tell
23 whether or not I should buy that Lithuanian porter again or whether it
24 would be quicker just to flush my money down the toilet and cut out the
27 The only problem with databases on Unix is that there isn't really a
28 nice way to get data into them. There isn't really a Microsoft Access
29 equivalent which can put a simple forms-based front-end onto an
30 arbitrary database, and if there is, I either didn't like it or couldn't
31 find it, and after a few brews, you really don't want to be trying to type
32 in your tasting notes in raw SQL.
34 So you see a generic problem arising out of a specific problem here. I
35 didn't want to solve the specific problem of the beer database, because
36 I'd already had another idea for a database that needed a front-end. So
37 for two years, I sat on this great idea of having a database of tasting
38 notes for beer. I even bought that damned African beer again. Enough was
39 enough. I wrote Maypole.
43 The first Maypole application was the beer database. We've already met
44 it; it looks like this.
47 use base 'Apache::MVC';
48 BeerDB->set_database("dbi:SQLite:t/beerdb.db");
49 BeerDB->config->{uri_base} = "http://localhost/beerdb/";
50 BeerDB->config->{rows_per_page} = 10;
51 BeerDB->config->{display_tables} = [qw[beer brewery pub style]];
52 BeerDB::Brewery->untaint_columns( printable => [qw/name notes url/] );
53 BeerDB::Style->untaint_columns( printable => [qw/name notes/] );
54 BeerDB::Beer->untaint_columns(
55 printable => [qw/abv name price notes/],
56 integer => [qw/style brewery score/],
60 use Class::DBI::Loader::Relationship;
61 BeerDB->config->{loader}->relationship($_) for (
62 "a brewery produces beers",
63 "a style defines beers",
64 "a pub has beers on handpumps");
67 Now, we can split this into four sections. Let's look at them one
72 Here's the first section:
75 use base 'Apache::MVC';
76 BeerDB->setup("dbi:SQLite:t/beerdb.db");
78 This is actually all you need for a functional database front-end. Everything
79 else is configuration details. This says three things: we're an application
80 called C<BeerDB>. This package is called the B<driver class>, because
81 it's a relatively small class which defines how the whole application is
84 The second line says that our front-end is going to be C<Apache::MVC>,
85 which is the Apache C<mod_perl> based version of Maypole; there's also
86 a CGI version, C<CGI::Maypole>, and a command-line version for
87 debugging, C<Maypole::CLI>, but C<Apache::MVC> is usually the one you
90 Thirdly we're going to need to set up our database with the given DBI
91 connection string. Now the core of Maypole itself doesn't know about
92 DBI; as we explained in L<Model.pod>, this argument is passed to our
93 model class wholesale. As we haven't said anything about a model
94 class, we get the default one, C<Maypole::Model::CDBI>, which takes a
95 DBI connect string. So this one line declares that we're using a C<CDBI>
96 model class and it sets up the database for us. In the same way, we
97 don't say that we want a particular view class, so we get the default
100 At this point, everything is in place; we have our driver class, it uses
101 a front-end, we have a model class and a view class, and we have a data
104 =head3 Application configuration
106 The next of our four sections is the configuration for the application itself.
108 BeerDB->config->{uri_base} = "http://localhost/beerdb/";
109 BeerDB->config->{rows_per_page} = 10;
110 BeerDB->config->{display_tables} = [qw[beer brewery pub style]];
112 Maypole provides a method called C<config> which returns a hash reference
113 of the application's whole configuration. We can use this to set some
114 parameters; the C<uri_base> is used as the canonical URL of the base
115 of this application, and Maypole uses it to construct links.
117 By defining C<rows_per_page>, we say that any listings we do with the
118 C<list> and C<search> templates should be arranged in sets of pages, with
119 a maximum of 10 items on each page. If we didn't declare that, C<list>
120 would try to put all the objects on one page, which could well be bad.
122 Finally, we declare which tables we want our Maypole front-end to
123 reference. If you remember from the schema, there's a table called
124 C<handpump> which acts as a linking table in a many-to-many relationship
125 between the C<pub> and C<beer> tables. As it's only a linking table, we
126 don't want people poking with it directly, so we exclude it from the
127 list of C<display_tables>.
131 The next section is the following set of lines:
133 BeerDB::Brewery->untaint_columns( printable => [qw/name notes url/] );
134 BeerDB::Style->untaint_columns( printable => [qw/name notes/] );
135 BeerDB::Beer->untaint_columns(
136 printable => [qw/abv name price notes/],
137 integer => [qw/style brewery score/],
141 As explained in L<StandardTemplates.pod>, this is an set of instructions to
142 C<Class::DBI::FromCGI> regarding how the given columns should be edited.
143 If we didn't have this section, we'd be able to view and delete records,
144 but adding and editing them wouldn't work. It took me ages to work that
149 Finally, we want to explain to Maypole how the various tables relate to
150 each other. This is done so that, for instance, when displaying a beer,
151 the brewery does not appear as an integer like "2" but as the name of
152 the brewery from the C<brewery> table with an ID of 2.
154 The usual C<Class::DBI> way to do this involves the C<has_a> and
155 C<has_many> methods, but I can never remember how to use them, so I came
156 up with the C<Class::DBI::Loader::Relationship> module; this was another
157 yak that needed shaving on the way to the beer database:
159 use Class::DBI::Loader::Relationship;
160 BeerDB->config->{loader}->relationship($_) for (
161 "a brewery produces beers",
162 "a style defines beers",
163 "a pub has beers on handpumps");
166 C<CDBIL::Relationship> acts on a C<Class::DBI::Loader> object and
167 defines relationships between tables in a fairly free-form style.
168 The equivalent in ordinary C<Class::DBI> would be:
170 BeerDB::Brewery->has_many(beers => "BeerDB::Beer");
171 BeerDB::Beer->has_a(brewery => "BeerDB::Brewery");
172 BeerDB::Style->has_many(beers => "BeerDB::Beer");
173 BeerDB::Beer->has_a(style => "BeerDB::Style");
175 BeerDB::Handpump->has_a(beer => "BeerDB::Beer");
176 BeerDB::Handpump->has_a(pub => "BeerDB::Pub");
177 BeerDB::Pub->has_many(beers => [ BeerDB::Handpump => 'beer' ]);
178 BeerDB::Beer->has_many(pubs => [ BeerDB::Handpump => 'pub' ]);
180 Maypole's default templates will use this information to display, for
181 instance, a list of a brewery's beers on the brewery view page.
183 This is the complete beer database application; Maypole's default templates
184 and the actions in the view class do the rest. But what if we want to a
185 little more. How would we begin to extend this application?