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