]> git.decadent.org.uk Git - maypole.git/blob - lib/Maypole/Manual/Inheritance.pod
a20740578a0cde8813474e368439245200592354
[maypole.git] / lib / Maypole / Manual / Inheritance.pod
1 \r
2 =head1 NAME\r
3 \r
4 Maypole::Manual::Inheritance - structure of a Maypole application\r
5 \r
6 =head1 DESCRIPTION\r
7 \r
8 Discusses the inheritance structure of a basic and a more advanced Maypole\r
9 application.\r
10 \r
11 =head1 CONVENTIONS\r
12           \r
13 =over 4\r
14 \r
15 =item inheritance\r
16 \r
17         +\r
18         |\r
19      +-   -+\r
20         |\r
21         +\r
22         \r
23 =item notes\r
24 \r
25     target *-------- note about the target\r
26 \r
27 =item association\r
28 \r
29     source ------> target\r
30 \r
31 =back\r
32 \r
33 =head1 A standard Maypole application\r
34 \r
35 A minimal Maypole application (such as the Beer database) consists of a \r
36 custom driver class (BeerDB.pm), a set of auto-generated model classes, and a \r
37 view class: \r
38 \r
39 \r
40                   THE DRIVER\r
41                                           +------- init() is a factory method,\r
42                    1      Maypole         |           it sets up the view\r
43    Maypole::Config <----- config();       |              classes\r
44    model();               init(); *-------+                           THE VIEW\r
45     |                     view_object(); -------+\r
46     |    +--------------* setup();              |      Maypole::View::Base\r
47     |    |                   +                  |              +\r
48     |    |                   |                  |     1        |\r
49     |    |    PLUGINS    Apache::MVC *-----+    +-----> Maypole::View::TT\r
50     |    |       +           +             |             (or another view class)\r
51     |    |       |           |             |\r
52     |    |       +-----+-----+             |\r
53     |    |             |                   |\r
54     |    |           BeerDB                +----- or CGI::Maypole\r
55     |    |                                         or MasonX:::Maypole\r
56     |    |\r
57     |   setup() is a factory method,\r
58     |     it sets up the model\r
59     |         classes\r
60     |\r
61     |                                 THE MODEL\r
62     |\r
63     |  Maypole::Model::Base   Class::DBI\r
64     |             +             +\r
65     |             |             |\r
66     +-------> Maypole::Model::CDBI\r
67                       +\r
68                       |\r
69            +------------+--------+-------+---------+\r
70            |            |        |       |         |\r
71        BeerDB::Pub      |   BeerDB::Beer | BeerDB::Brewery\r
72        beers();         |   pubs();      | beers();\r
73                         |   brewery();   |\r
74                         |   style();     |\r
75           BeerDB::Handpump               |\r
76           pub();                      BeerDB::Style\r
77           beer();                     beers();\r
78 \r
79 =head2 What about Maypole::Application - loading plugins\r
80 \r
81 The main job of L<Maypole::Application> is to insert the plugins into the\r
82 hierarchy. It is also the responsibility of L<Maypole::Application> to decide\r
83 which frontend to use. It builds the list of plugins, then pushes them onto the\r
84 driver's C<@ISA>, then pushes the frontend onto the end of the driver's C<@ISA>.\r
85 So method lookup first searches all the plugins, before searching the frontend\r
86 and finally L<Maypole> itself.\r
87 \r
88 From Maypole 2.11, L<Maypole::Application> makes no appearance in the\r
89 inheritance structure of a Maypole application. (In prior versions,\r
90 L<Maypole::Application> would make itself inherit the plugins, and then insert\r
91 itself in the hierarchy, but this was unnecessary).\r
92 \r
93 =head2 Who builds the model?\r
94 \r
95 First, remember we are talking about the standard, unmodified Maypole here. \r
96 It is possible, and common, to override some or all of this stage and build a \r
97 customised model.\r
98 \r
99 The standard model is built in 3 stages. \r
100 \r
101 First, C<Maypole::setup> calls C<setup_database> on the Maypole model class, in\r
102 this case L<Maypole::Model::CDBI>. C<setup_database> then uses\r
103 L<Class::DBI::Loader> to autogenerate individual L<Class::DBI> classes for each\r
104 of the tables in the database (C<BeerDB::Beer>, C<BeerDB::Pub> etc).\r
105 \r
106 Next, C<Maypole::setup> B<unshifts> L<Maypole::Model::CDBI> onto the C<@ISA> \r
107 array of each of these classes. \r
108 \r
109 Finally, the relationships among these tables are set up. Either do this\r
110 manually, perhaps with the help of L<Class::DBI::Relationship>, or use\r
111 L<Maypole::Plugin::Relationship>. In the latter case, you need to set up the\r
112 relationships configuration before calling C<setup()>.\r
113 \r
114 \r
115 =head1 An advanced Maypole application\r
116 \r
117 We'll call it C<BeerDB2>.\r
118 \r
119 Maypole is a framework, and you can replace different bits as you wish. So what \r
120 follows is one example of good practice, other people may do things differently. \r
121 \r
122 We assume this application is being built from the ground up, but it will often\r
123 be straightforward to adapt an existing L<Class::DBI> application to this\r
124 general model.\r
125 \r
126 The main idea is that the autogenerated Maypole model is used as a layer on top\r
127 of a separate L<Class::DBI> model. I am going to refer to this model as the\r
128 'Offline' model, and the Maypole classes as the 'Maypole' model. The idea is\r
129 that the Offline model can (potentially or in actuality) be used as part of\r
130 another application, perhaps a command line program or a cron script, whatever.\r
131 The Offline model does not know about the Maypole model, whereas the Maypole\r
132 model does know about the Offline model.\r
133 \r
134 Let's call the offline model C<OfflineBeer>. As a traditional L<Class::DBI>\r
135 application, individual table classes in this model will inherit from a common\r
136 base (C<OfflineBeer>), which inherits from L<Class::DBI>).\r
137 \r
138 One advantage of this approach is that you can still use Maypole's autogenerated\r
139 model. Another is that you do not mix online and offline code in the same\r
140 packages.\r
141 \r
142 =head2 Building it\r
143 \r
144 Build a driver in a similar way as for the basic app, calling C<setup()> after\r
145 setting up all the configuration. \r
146 \r
147 It is a good habit to use a custom Maypole model class for each application, as\r
148 it's a likely first target for customisation. Start it like this:\r
149 \r
150     package BeerDB2::Maypole::Model;\r
151     use strict;\r
152     use warnings;\r
153     use base 'Maypole::Model::CDBI';\r
154     1;\r
155     \r
156 You can add methods which should be shared by all table classes to this package \r
157 as and when required.\r
158     \r
159 Configure it like this, before the C<setup()> call in the driver class:\r
160 \r
161     # in package BeerDB2\r
162     __PACKAGE__->config->model('BeerDB2::Maypole::Model');\r
163     __PACKAGE__->setup;\r
164 \r
165 The C<setup()> call will ensure your custom model is loaded via C<require>.\r
166 \r
167 B<Note>: by default, this will create Maypole/CDBI classes for all the tables in\r
168 the database. You can control this by passing options for L<Class::DBI::Loader>\r
169 in the call to C<setup()>.\r
170 \r
171 For each class in the model, you need to create a separate file. So for\r
172 C<BeerDB2::Beer>, you would write:\r
173 \r
174     package BeerDB2::Beer;\r
175     use strict;\r
176     use warnings;\r
177     use base 'OfflineBeer::Beer';\r
178     1;\r
179     \r
180 This package will be loaded automatically during C<setup()>, and\r
181 C<BeerDB2::Maypole::Model> is B<unshifted> onto it's C<@ISA>. \r
182 \r
183 Configure relationships either in the individual C<OfflineBeer::*> classes, or\r
184 else all together in C<OfflineBeer> itself i.e. not in the Maypole model. This \r
185 way, you only define the relationships in one place.\r
186 \r
187 The resulting model looks like this:\r
188 \r
189                                        Class::DBI\r
190     MAYPOLE 'MODEL'                       |\r
191                                           |\r
192    Maypole::Model::Base                   |\r
193            +                              |\r
194            |       +----------------------+-----------------+\r
195            |       |                                        |\r
196            |       |                                        |\r
197      Maypole::Model::CDBI                                   |     OFFLINE\r
198              +                                              |        MODEL\r
199              |                                              |\r
200      BeerDB2::Maypole::Model                           OfflineBeer\r
201        +                                                    +\r
202        |                                                    |\r
203        |                                                    |\r
204        +--- BeerDB2::Pub --------+ OfflineBeer::Pub --------+\r
205        |                           beers();                 |\r
206        |                                                    |\r
207        |                           OfflineBeer::Handpump ---+\r
208        |                           beer();                  |\r
209        |                           pub();                   |\r
210        |                                                    |\r
211        +--- BeerDB2::Beer -------+ OfflineBeer::Beer -------+\r
212        |                           pubs();                  |\r
213        |                           brewery();               |\r
214        |                           style();                 |\r
215        |                                                    |\r
216        +--- BeerDB2::Style ------+ OfflineBeer::Style ------+\r
217        |                           beers();                 |\r
218        |                                                    |\r
219        +--- BeerDB2::Brewery ----+ OfflineBeer::Brewery ----+\r
220                                    beers();\r
221 \r
222 \r
223 \r
224 =head3 Features\r
225 \r
226 Non-Maypole applications using the Offline model are completely isolated from\r
227 the Maypole application, and need not know it exists at all.\r
228 \r
229 Methods defined in the Maypole table classes, override methods defined in the\r
230 Offline table classes, because C<BeerDB2::Maypole::Model> was unshifted onto the\r
231 beginning of each Maypole table class's C<@ISA>. Perl's depth first,\r
232 left-to-right method lookup from e.g. C<BeerDB2::Beer> starts in\r
233 C<BeerDB2::Beer>, then C<BeerDB2::Maypole::Model>, C<Maypole::Model::CDBI>,\r
234 C<Maypole::Model::Base>, and C<Class::DBI>, before moving on to\r
235 C<OfflineBeer::Beer> and finally C<OfflineBeer>.\r
236 \r
237 B<CAVEAT> - if your Offline model overrides L<Class::DBI> methods, these methods\r
238 will B<not> be overridden when called from the Maypole application, because the\r
239 Maypole model provides an alternative path to L<Class::DBI> which is searched\r
240 first. The solution is to place such methods in a separate package, e.g.\r
241 C<OfflineBeer::CDBI>. Place this B<first> in the C<@ISA> of both\r
242 C<BeerDB2::Maypole::Model> and C<OfflineBeer>. Note that C<OfflineBeer::CDBI>\r
243 does not itself need to inherit from L<Class::DBI>.\r
244 \r
245 Methods defined in the Maypole model base class (C<BeerDB2::Maypole::Model>),\r
246 override methods in the individual Offline table classes, and in the Offline\r
247 model base class (C<Offline>). \r
248 \r
249 Relationships defined in the Offline classes are inherited by the Maypole model.\r
250 \r
251 The Maypole model has full access to the underlying Offline model. \r
252 \r
253 =head3 Theory \r
254 \r
255 This layout illustrates more clearly why the Maypole model may be thought of as\r
256 part of the controller, rather than part of the model of MVC. Its function is to \r
257 mediate web requests, translating them into method calls on the Offline model, \r
258 munging the results, and returning them via the Maypole request object. \r
259 \r
260 Another way of thinking about it is that Maypole implements a two-layer\r
261 controller. The first layer translates a raw request into a single method call\r
262 on the Maypole model layer, which then translates that call into one or more\r
263 calls on the underlying model.\r
264 \r
265 Whatever label you prefer to use, this approach provides for clear separation of\r
266 concerns between the underlying model and the web/user interface, and that's\r
267 what it's all about.\r
268           \r
269 =head1 AUTHOR\r
270 \r
271 David Baird, C<< <cpan@riverside-cms.co.uk> >>\r
272 \r
273 =head1 COPYRIGHT & LICENSE\r
274 \r
275 Copyright 2005 David Baird, All Rights Reserved.\r
276 \r
277 This text is free documentation; you can redistribute it and/or modify it\r
278 under the same terms as the Perl documentation itself.\r
279 \r
280 =cut\r
281 \r