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