1Maypole::Manual::Beer(3U)ser Contributed Perl DocumentatiMoanypole::Manual::Beer(3)
2
3
4

NAME

6       Maypole::Manual::Beer - The Beer Database, Twice
7

DESCRIPTION

9       We briefly introduced the "beer database" example in the Introduction
10       to Maypole chapter, where we presented its driver class, "BeerDB.pm",
11       as a fait accompli. Where did all that code come from, and what does it
12       actually mean?
13
14       The big beer problem
15
16       I have a seriously bad habit. This is not the beer problem; this is a
17       programming problem. The bad habit is that when I approach a problem I
18       want to solve, I get sidetracked deeper and deeper trying to solve more
19       and more generic problems, and then, satisfied with solving the generic
20       problem, I never get around to solving the specific problem. I always
21       write libraries for people writing libraries, and never write applica‐
22       tions.
23
24       The thing with really good beer is that it commands you to drink more
25       of it, and then by the morning you can't remember whether it was any
26       good or not. After buying several bottles of some random central
27       African lager on a dim recollection that it was really good and having
28       it turn out to be abysmal, this really became a problem. If only I
29       could have a database that I updated every time I buy a new beer, I'd
30       be able to tell whether or not I should buy that Lithuanian porter
31       again or whether it would be quicker just to flush my money down the
32       toilet and cut out the middle-man.
33
34       The only problem with databases on Unix is that there isn't really a
35       nice way to get data into them. There isn't really a Microsoft Access
36       equivalent which can put a simple forms-based front-end onto an arbi‐
37       trary database, and if there is, I either didn't like it or couldn't
38       find it, and after a few brews, you really don't want to be trying to
39       type in your tasting notes in raw SQL.
40
41       So you see a generic problem arising out of a specific problem here. I
42       didn't want to solve the specific problem of the beer database, because
43       I'd already had another idea for a database that needed a front-end. So
44       for two years, I sat on this great idea of having a database of tasting
45       notes for beer. I even bought that damned African beer again. Enough
46       was enough. I wrote Maypole.
47
48       The easy way
49
50       The first Maypole application was the beer database. We've already met
51       it; it looks like this.
52
53           package BeerDB;
54           use Maypole::Application;
55           BeerDB->setup("dbi:SQLite:t/beerdb.db");
56           BeerDB->config->uri_base("http://localhost/beerdb");
57           BeerDB->config->template_root("/path/to/templates");
58           BeerDB->config->rows_per_page(10);
59           BeerDB->config->display_tables([qw[beer brewery pub style]]);
60           BeerDB::Brewery->untaint_columns( printable => [qw/name notes url/] );
61           BeerDB::Style->untaint_columns( printable => [qw/name notes/] );
62           BeerDB::Beer->untaint_columns(
63               printable => [qw/abv name price notes/],
64               integer => [qw/style brewery score/],
65               date => [ qw/date/],
66           );
67
68           use Class::DBI::Loader::Relationship;
69           BeerDB->config->{loader}->relationship($_) for (
70               "a brewery produces beers",
71               "a style defines beers",
72               "a pub has beers on handpumps");
73           1;
74
75       Now, we can split this into four sections. Let's look at them one at a
76       time.
77
78       Driver setup
79
80       Here's the first section:
81
82           package BeerDB;
83           use Maypole::Application;
84           BeerDB->setup("dbi:SQLite:t/beerdb.db");
85
86       This is actually all you need for a functional database front-end.
87       Everything else is configuration details. This says three things: we're
88       an application called "BeerDB". This package is called the driver
89       class, because it's a relatively small class which defines how the
90       whole application is going to run.
91
92       The second line says that our front-end is going to be Maypole::Appli‐
93       cation, it automatically detects if you're using mod_perl or CGI and
94       loads everything necessary for you.
95
96       Thirdly we're going to need to set up our database with the given DBI
97       connection string. Now the core of Maypole itself doesn't know about
98       DBI; as we explained in the Model chapter, this argument is passed to
99       our model class wholesale. As we haven't said anything about a model
100       class, we get the default one, Maypole::Model::CDBI, which takes a DBI
101       connect string. So this one line declares that we're using a "CDBI"
102       model class and it sets up the database for us. In the same way, we
103       don't say that we want a particular view class, so we get the default
104       Maypole::View::TT.
105
106       At this point, everything is in place; we have our driver class, it
107       uses a front-end, we have a model class and a view class, and we have a
108       data source.
109
110       Application configuration
111
112       The next of our four sections is the configuration for the application
113       itself.
114
115           BeerDB->config->uri_base("http://localhost/beerdb");
116           BeerDB->config->template_root("/path/to/templates");
117           BeerDB->config->rows_per_page(10);
118           BeerDB->config->display_tables([qw[beer brewery pub style]]);
119
120       Maypole provides a method called "config" which returns an object that
121       holds the application's whole configuration. We can use this to set
122       some parameters; the "uri_base" is used as the canonical URL of the
123       base of this application, and Maypole uses it to construct links.
124
125       We also tell Maypole where we keep our template files, using "tem‐
126       plate_root".
127
128       By defining "rows_per_page", we say that any listings we do with the
129       "list" and "search" templates should be arranged in sets of pages, with
130       a maximum of 10 items on each page. If we didn't declare that, "list"
131       would try to put all the objects on one page, which could well be bad.
132
133       Finally, we declare which tables we want our Maypole front-end to ref‐
134       erence. If you remember from the schema, there's a table called "hand‐
135       pump" which acts as a linking table in a many-to-many relationship
136       between the "pub" and "beer" tables. As it's only a linking table, we
137       don't want people poking with it directly, so we exclude it from the
138       list of "display_tables".
139
140       Editability
141
142       The next section is the following set of lines:
143
144           BeerDB::Brewery->untaint_columns( printable => [qw/name notes url/] );
145           BeerDB::Style->untaint_columns( printable => [qw/name notes/] );
146           BeerDB::Beer->untaint_columns(
147               printable => [qw/abv name price notes/],
148               integer => [qw/style brewery score/],
149               date => [ qw/date/],
150           );
151
152       As explained in the Standard Templates chapter, this is an set of
153       instructions to Class::DBI::FromCGI regarding how the given columns
154       should be edited.  If we didn't have this section, we'd be able to view
155       and delete records, but adding and editing them wouldn't work. It took
156       me ages to work that one out.
157
158       Relationships
159
160       Finally, we want to explain to Maypole how the various tables relate to
161       each other. This is done so that, for instance, when displaying a beer,
162       the brewery does not appear as an integer like "2" but as the name of
163       the brewery from the "brewery" table with an ID of 2.
164
165       The usual Class::DBI way to do this involves the "has_a" and "has_many"
166       methods, but I can never remember how to use them, so I came up with
167       the Class::DBI::Loader::Relationship module; this was another yak that
168       needed shaving on the way to the beer database:
169
170           use Class::DBI::Loader::Relationship;
171           BeerDB->config->{loader}->relationship($_) for (
172               "a brewery produces beers",
173               "a style defines beers",
174               "a pub has beers on handpumps");
175           1;
176
177       "CDBIL::Relationship" acts on a Class::DBI::Loader object and defines
178       relationships between tables in a fairly free-form style.  The equiva‐
179       lent in ordinary "Class::DBI" would be:
180
181              BeerDB::Brewery->has_many(beers => "BeerDB::Beer");
182              BeerDB::Beer->has_a(brewery => "BeerDB::Brewery");
183              BeerDB::Style->has_many(beers => "BeerDB::Beer");
184              BeerDB::Beer->has_a(style => "BeerDB::Style");
185
186              BeerDB::Handpump->has_a(beer => "BeerDB::Beer");
187              BeerDB::Handpump->has_a(pub => "BeerDB::Pub");
188              BeerDB::Pub->has_many(beers => [ 'BeerDB::Handpump' => 'beer' ]);
189              BeerDB::Beer->has_many(pubs => [ 'BeerDB::Handpump' => 'pub' ]);
190
191       Maypole's default templates will use this information to display, for
192       instance, a list of a brewery's beers on the brewery view page.
193
194       Note the quoting in 'BeerDB::Handpump' => 'beer', if you forget to
195       quote the left side when using strict you will get compilation errors.
196
197       This is the complete beer database application; Maypole's default tem‐
198       plates and the actions in the view class do the rest. But what if we
199       want to do a little more. How would we begin to extend this applica‐
200       tion?
201
202       The hard way
203
204       Maypole was written because I don't like writing more Perl code than is
205       necessary. I also don't like writing HTML. In fact, I don't really get
206       on this whole computer thing, to be honest. But we'll start by ways
207       that we can customize the beer application simply by adding methods or
208       changing properties of the Perl driver code.
209
210       The first thing we ought to look into is the names of the columns; most
211       of them are fine, but that "Abv" column stands out. I'd rather that was
212       "A.B.V.". Maypole uses the "column_names" method to map between the
213       names of the columns in the database to the names it displays in the
214       default templates. This is provided by Maypole::Model::Base, and nor‐
215       mally, it does a pretty good job; it turns "model_number" into "Model
216       Number", for instance, but there was no way it could guess that "abv"
217       was an abbreviation. Since it returns a hash, the easiest way to cor‐
218       rect it is to construct a hash consisting of the bits it got right, and
219       then override the bits it got wrong:
220
221           package BeerDB::Beer;
222           sub column_names {
223               (shift->SUPER::column_names(), abv => "A.B.V.")
224           }
225
226       There's something to be aware of here: where are you going to type that
227       code? You can just put it in BeerDB.pm. Perl will be happy with that,
228       though you might want to put an extra pair of braces around it to limit
229       the scope of that package declaration. Alternatively, you might think
230       it's neater to put it in a file called BeerDB/Beer.pm, which is the
231       natural home for the package. This would certainly be a good idea if
232       you have a lot of other code to add to the "BeerDB::Beer" package. But
233       if you do that, you will have to tell Perl to load the BeerDB/Beer.pm
234       file by adding a line to BeerDB.pm:
235
236           BeerDB::Beer->require;
237
238       For another example of customization, the order of columns is a bit
239       wonky. We can fix this by overriding the "display_columns" method; this
240       is also a good way to hide away any columns we don't want to have dis‐
241       played, in the same way as declaring the "display_tables" configuration
242       parameter let us hide away tables we weren't using:
243
244           sub display_columns {
245               ("name", "brewery", "style", "price", "score", "abv", "notes")
246           }
247
248       Hey, have you noticed that we haven't done anything with the
249       beers/handpumps/pubs thing yet? Good, I was hoping that you hadn't.
250       Anyway, this is because Maypole can't tell easily that a "BeerDB::Beer"
251       object can call "pubs" to get a list of pubs. Not yet, at least; we're
252       working on it. In the interim, we can explicitly tell Maypole which
253       accessors are related to the "BeerDB::Beer" class like so:
254
255           sub related { "pubs" }
256
257       Now when we view a beer, we'll have a list of the pubs that it's on at.
258
259       Links
260
261       Contents, Next The Request Cookbook, Previous Maypole's Request Work‐
262       flow
263
264
265
266perl v5.8.8                       2005-11-23          Maypole::Manual::Beer(3)
Impressum