]> git.decadent.org.uk Git - maypole.git/blob - lib/Maypole/Manual/Beer.pod
a1c85f20297ec793cafaf12eb43fdc75fb40ea75
[maypole.git] / lib / Maypole / Manual / Beer.pod
1 =head1 The Beer Database, Twice
2
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?
6
7 =head2 The big beer problem
8
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
15 applications.
16
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
25 middle-man.
26
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.
33
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.
40
41 =head2 The easy way
42
43 The first Maypole application was the beer database. We've already met
44 it; it looks like this.
45
46     package BeerDB;
47     use Maypole::Application;
48     BeerDB->set_database("dbi:SQLite:t/beerdb.db");
49     BeerDB->config->{uri_base} = "http://localhost/beerdb";
50     BeerDB->config->{template_root} = "/path/to/templates";
51     BeerDB->config->{rows_per_page} = 10;
52     BeerDB->config->{display_tables} = [qw[beer brewery pub style]];
53     BeerDB::Brewery->untaint_columns( printable => [qw/name notes url/] );
54     BeerDB::Style->untaint_columns( printable => [qw/name notes/] );
55     BeerDB::Beer->untaint_columns(
56         printable => [qw/abv name price notes/],
57         integer => [qw/style brewery score/],
58         date => [ qw/date/],
59     );
60
61     use Class::DBI::Loader::Relationship;
62     BeerDB->config->{loader}->relationship($_) for (
63         "a brewery produces beers",
64         "a style defines beers",
65         "a pub has beers on handpumps");
66     1;
67
68 Now, we can split this into four sections. Let's look at them one
69 at a time. 
70
71 =head3 Driver setup
72
73 Here's the first section:
74
75     package BeerDB;
76     use Maypole::Application;
77     BeerDB->setup("dbi:SQLite:t/beerdb.db");
78
79 This is actually all you need for a functional database front-end. Everything
80 else is configuration details. This says three things: we're an application
81 called C<BeerDB>. This package is called the B<driver class>, because
82 it's a relatively small class which defines how the whole application is
83 going to run. 
84
85 The second line says that our front-end is going to be
86 C<Maypole::Application>, it automatically detects if you're using
87 mod_perl or CGI and loads everything neccessary for you.
88
89 Thirdly we're going to need to set up our database with the given DBI
90 connection string. Now the core of Maypole itself doesn't know about
91 DBI; as we explained in L<Model.pod>, this argument is passed to our
92 model class wholesale. As we haven't said anything about a model
93 class, we get the default one, C<Maypole::Model::CDBI>, which takes a
94 DBI connect string. So this one line declares that we're using a C<CDBI>
95 model class and it sets up the database for us. In the same way, we
96 don't say that we want a particular view class, so we get the default
97 C<Maypole::View::TT>.
98
99 At this point, everything is in place; we have our driver class, it uses
100 a front-end, we have a model class and a view class, and we have a data
101 source.
102
103 =head3 Application configuration
104
105 The next of our four sections is the configuration for the application itself.
106
107     BeerDB->config->{uri_base} = "http://localhost/beerdb/";
108     BeerDB->config->{rows_per_page} = 10;
109     BeerDB->config->{display_tables} = [qw[beer brewery pub style]];
110
111 Maypole provides a method called C<config> which returns a hash reference
112 of the application's whole configuration. We can use this to set some
113 parameters; the C<uri_base> is used as the canonical URL of the base
114 of this application, and Maypole uses it to construct links.
115
116 By defining C<rows_per_page>, we say that any listings we do with the
117 C<list> and C<search> templates should be arranged in sets of pages, with
118 a maximum of 10 items on each page. If we didn't declare that, C<list>
119 would try to put all the objects on one page, which could well be bad.
120
121 Finally, we declare which tables we want our Maypole front-end to
122 reference. If you remember from the schema, there's a table called
123 C<handpump> which acts as a linking table in a many-to-many relationship
124 between the C<pub> and C<beer> tables. As it's only a linking table, we
125 don't want people poking with it directly, so we exclude it from the
126 list of C<display_tables>.
127
128 =head3 Editability
129
130 The next section is the following set of lines:
131
132     BeerDB::Brewery->untaint_columns( printable => [qw/name notes url/] );
133     BeerDB::Style->untaint_columns( printable => [qw/name notes/] );
134     BeerDB::Beer->untaint_columns(
135         printable => [qw/abv name price notes/],
136         integer => [qw/style brewery score/],
137         date => [ qw/date/],
138     );
139
140 As explained in L<StandardTemplates.pod>, this is an set of instructions to
141 C<Class::DBI::FromCGI> regarding how the given columns should be edited.
142 If we didn't have this section, we'd be able to view and delete records,
143 but adding and editing them wouldn't work. It took me ages to work that
144 one out.
145
146 =head3 Relationships
147
148 Finally, we want to explain to Maypole how the various tables relate to
149 each other. This is done so that, for instance, when displaying a beer,
150 the brewery does not appear as an integer like "2" but as the name of
151 the brewery from the C<brewery> table with an ID of 2.
152
153 The usual C<Class::DBI> way to do this involves the C<has_a> and
154 C<has_many> methods, but I can never remember how to use them, so I came
155 up with the C<Class::DBI::Loader::Relationship> module; this was another
156 yak that needed shaving on the way to the beer database:
157
158     use Class::DBI::Loader::Relationship;
159     BeerDB->config->{loader}->relationship($_) for (
160         "a brewery produces beers",
161         "a style defines beers",
162         "a pub has beers on handpumps");
163     1;
164
165 C<CDBIL::Relationship> acts on a C<Class::DBI::Loader> object and
166 defines relationships between tables in a fairly free-form style.
167 The equivalent in ordinary C<Class::DBI> would be:
168
169        BeerDB::Brewery->has_many(beers => "BeerDB::Beer");
170        BeerDB::Beer->has_a(brewery => "BeerDB::Brewery");
171        BeerDB::Style->has_many(beers => "BeerDB::Beer");
172        BeerDB::Beer->has_a(style => "BeerDB::Style");
173
174        BeerDB::Handpump->has_a(beer => "BeerDB::Beer");
175        BeerDB::Handpump->has_a(pub => "BeerDB::Pub");
176        BeerDB::Pub->has_many(beers => [ BeerDB::Handpump => 'beer' ]);
177        BeerDB::Beer->has_many(pubs => [ BeerDB::Handpump => 'pub' ]);
178
179 Maypole's default templates will use this information to display, for
180 instance, a list of a brewery's beers on the brewery view page.
181
182 This is the complete beer database application; Maypole's default templates
183 and the actions in the view class do the rest. But what if we want to do a
184 little more. How would we begin to extend this application?
185
186 =head2 The hard way
187
188 Maypole was written because I don't like writing more Perl code than is
189 necessary. I also don't like writing HTML. In fact, I don't really get
190 on this whole computer thing, to be honest. But we'll start by ways that
191 we can customize the beer application simply by adding methods or
192 changing properties of the Perl driver code.
193
194 The first thing we ought to look into is the names of the columns; most
195 of them are fine, but that "Abv" column stands out. I'd rather that was
196 "A.B.V.". Maypole uses the C<column_names> method to map between the
197 names of the columns in the database to the names it displays in the
198 default templates. This is provided by C<Maypole::Model::Base>, and
199 normally, it does a pretty good job; it turns C<model_number> into
200 "Model Number", for instance, but there was no way it could guess that
201 C<abv> was an abbreviation. Since it returns a hash, the easiest way
202 to correct it is to construct a hash consisting of the bits it got
203 right, and then override the bits it got wrong:
204
205     package BeerDB::Beer;
206     sub column_names { 
207         (shift->SUPER::column_names(), abv => "A.B.V.")
208     }
209
210 Similarly, the order of columns is a bit wonky. We can fix this by
211 overriding the C<display_columns> method; this is also a good way to
212 hide away any columns we don't want to have displayed, in the same way
213 as declaring the C<display_tables> configuration parameter let us hide
214 away tables we weren't using:
215
216     sub display_columns { 
217         ("name", "brewery", "style", "price", "score", "abv", "notes")
218     }
219
220 Hey, have you noticed that we haven't done anything with the
221 beers/handpumps/pubs thing yet? Good, I was hoping that you hadn't.
222 Ayway, this is because Maypole can't tell easily that a C<BeerDB::Beer>
223 object can call C<pubs> to get a list of pubs. Not yet, at least; we're
224 working on it. In the interim, we can explicitly tell Maypole which
225 accessors are related to the C<BeerDB::Beer> class like so:
226
227     sub related { "pubs" }
228
229 Now when we view a beer, we'll have a list of the pubs that it's on at.