]> git.decadent.org.uk Git - maypole.git/blob - lib/Maypole/Manual/Inheritance.pod
76d76115626194e1f271cecb134edb02e33d1896
[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 Structure of a standard Maypole application\r
34 \r
35 A minimal Maypole application (such as the Beer database example from the\r
36 L<Maypole> synopsis) consists of a custom driver (or controller) class (BeerDB.pm),\r
37 a set of auto-generated model classes, and a 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   Class::DBI::<db_driver>\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 Ouch, that's a lot of inheritence!\r
80 \r
81 Yes, that's a lot of inheritence, fortunately as of 2.12 Maypole uses\r
82 L<Class::C3> to ensure sane method resolution.\r
83 \r
84 =head2 What about Maypole::Application - loading plugins\r
85 \r
86 The main job of L<Maypole::Application> is to insert the plugins into the\r
87 hierarchy. It is also the responsibility of L<Maypole::Application> to decide\r
88 which frontend to use. It builds the list of plugins, then pushes them onto the\r
89 driver's C<@ISA>, then pushes the frontend onto the end of the driver's C<@ISA>.\r
90 So method lookup first searches all the plugins, before searching the frontend\r
91 and finally L<Maypole> itself.\r
92 \r
93 From Maypole 2.11, L<Maypole::Application> makes no appearance in the\r
94 inheritance structure of a Maypole application. (In prior versions,\r
95 L<Maypole::Application> would make itself inherit the plugins, and then insert\r
96 itself in the hierarchy, but this was unnecessary).\r
97 \r
98 =head2 Who builds the model?\r
99 \r
100 First, remember we are talking about the standard, unmodified Maypole here. It\r
101 is possible, and common, to override some or all of this stage and build a\r
102 customised model. See below - An advanced Maypole application - for one\r
103 approach. Also, see L<Maypole's|Maypole> C<setup_model()> method. \r
104 \r
105 The standard model is built in 3 stages. \r
106 \r
107 First, C<Maypole::setup_model> calls C<setup_database> on the Maypole model\r
108 class, in this case L<Maypole::Model::CDBI>. C<setup_database> then uses\r
109 L<Class::DBI::Loader> to autogenerate individual L<Class::DBI> classes for each\r
110 of the tables in the database (C<BeerDB::Beer>, C<BeerDB::Pub> etc).\r
111 L<Class::DBI::Loader> identifies the appropriate L<Class::DBI> subclass and\r
112 inserts it into each of these table classes' C<@ISA> ( C<<\r
113 Class::DBI::<db_driver> >> in the diagrams)..\r
114 \r
115 Next, C<Maypole::setup> B<pushes> L<Maypole::Model::CDBI> onto the C<@ISA> \r
116 array of each of these classes. \r
117 \r
118 Finally, the relationships among these tables are set up. Either do this\r
119 manually, using the standard L<Class::DBI> syntax for configuring table\r
120 relationships, or try L<Class::DBI::Relationship> (which you can use via\r
121 L<Maypole::Plugin::Relationship>). If you use the plugin, you need to set up the\r
122 relationships configuration before calling C<setup()>. Be aware that some people\r
123 like the convenience of L<Class::DBI::Relationship>, others dislike the\r
124 abstraction. YMMV. \r
125 \r
126 =head1 An advanced Maypole application\r
127 \r
128 We'll call it C<BeerDB2>.\r
129 \r
130 Maypole is a framework, and you can replace different bits as you wish. So what \r
131 follows is one example of good practice, other people may do things differently. \r
132 \r
133 We assume this application is being built from the ground up, but it will often\r
134 be straightforward to adapt an existing L<Class::DBI> application to this\r
135 general model.\r
136 \r
137 The main idea is that the autogenerated Maypole model is used as a layer on top\r
138 of a separate L<Class::DBI> model. I am going to refer to this model as the\r
139 'Offline' model, and the Maypole classes as the 'Maypole' model. The idea is\r
140 that the Offline model can (potentially or in actuality) be used as part of\r
141 another application, perhaps a command line program or a cron script, whatever.\r
142 The Offline model does not know about the Maypole model, whereas the Maypole\r
143 model does know about the Offline model.\r
144 \r
145 Let's call the offline model C<OfflineBeer>. As a traditional L<Class::DBI>\r
146 application, individual table classes in this model will inherit from a common\r
147 base (C<OfflineBeer>), which inherits from L<Class::DBI>).\r
148 \r
149 One advantage of this approach is that you can still use Maypole's autogenerated\r
150 model. Another is that you do not mix online and offline code in the same\r
151 packages.\r
152 \r
153 =head2 Building it\r
154 \r
155 Build a driver in a similar way as for the basic app, calling C<setup()> after\r
156 setting up all the configuration. \r
157 \r
158 It is a good habit to use a custom Maypole model class for each application, as\r
159 it's a likely first target for customisation. Start it like this:\r
160 \r
161     package BeerDB2::Maypole::Model;\r
162     use strict;\r
163     use warnings;\r
164     use base 'Maypole::Model::CDBI';\r
165     1;\r
166     \r
167 You can add methods which should be shared by all table classes to this package \r
168 as and when required.\r
169     \r
170 Configure it like this, before the C<setup()> call in the driver class:\r
171 \r
172     # in package BeerDB2\r
173     __PACKAGE__->config->model('BeerDB2::Maypole::Model');\r
174     __PACKAGE__->setup;\r
175 \r
176 The C<setup()> call will ensure your custom model is loaded via C<require>.\r
177 \r
178 B<Note>: by default, this will create Maypole/CDBI classes for all the tables in\r
179 the database. You can control this by passing options for L<Class::DBI::Loader>\r
180 in the call to C<setup()>.\r
181 \r
182 For each class in the model, you need to create a separate file. So for\r
183 C<BeerDB2::Beer>, you would write:\r
184 \r
185     package BeerDB2::Beer;\r
186     use strict;\r
187     use warnings;\r
188     use base 'OfflineBeer::Beer';\r
189     1;\r
190     \r
191 From Maypole 2.11, this package will be loaded automatically during C<setup()>,\r
192 and C<BeerDB2::Maypole::Model> is B<pushed> onto it's C<@ISA>.\r
193 \r
194 Configure relationships either in the individual C<OfflineBeer::*> classes, or\r
195 else all together in C<OfflineBeer> itself i.e. not in the Maypole model. This \r
196 way, you only define the relationships in one place.\r
197 \r
198 The resulting model looks like this:\r
199 \r
200                                        Class::DBI\r
201     MAYPOLE 'MODEL'                       |\r
202                                           |\r
203    Maypole::Model::Base                   |\r
204            +                              |\r
205            |       +-----------------+----+-----------------+\r
206            |       |                 |                      |\r
207            |       |                 |                      |\r
208      Maypole::Model::CDBI            |                      |     OFFLINE\r
209              +                       |                      |        MODEL\r
210              |                       |                      |\r
211      BeerDB2::Maypole::Model  Class::DBI::<db_driver>  OfflineBeer\r
212        +                             +                      +\r
213        |                             |                      |\r
214        +-----------------------------+                      |\r
215        |                                                    |\r
216        +--- BeerDB2::Pub --------+ OfflineBeer::Pub --------+\r
217        |                           beers();                 |\r
218        |                                                    |\r
219        |                           OfflineBeer::Handpump ---+\r
220        |                           beer();                  |\r
221        |                           pub();                   |\r
222        |                                                    |\r
223        +--- BeerDB2::Beer -------+ OfflineBeer::Beer -------+\r
224        |                           pubs();                  |\r
225        |                           brewery();               |\r
226        |                           style();                 |\r
227        |                                                    |\r
228        +--- BeerDB2::Style ------+ OfflineBeer::Style ------+\r
229        |                           beers();                 |\r
230        |                                                    |\r
231        +--- BeerDB2::Brewery ----+ OfflineBeer::Brewery ----+\r
232                                    beers();\r
233 \r
234 \r
235 \r
236 =head3 Features\r
237 \r
238 *REWRITE BASED ON C3 and push instead of shift*\r
239 \r
240 1. Non-Maypole applications using the Offline model are completely isolated from\r
241 the Maypole application, and need not know it exists at all.\r
242 \r
243 2. Methods defined in the Maypole table classes, override methods defined in the\r
244 Offline table classes, because C<BeerDB2::Maypole::Model> was pushed onto the\r
245 end of each Maypole table class's C<@ISA>. Perl's depth first,\r
246 left-to-right method lookup from e.g. C<BeerDB2::Beer> starts in\r
247 C<BeerDB2::Beer>, then C<BeerDB2::Maypole::Model>, C<Maypole::Model::CDBI>,\r
248 C<Maypole::Model::Base>, and C<Class::DBI>, before moving on to\r
249 C<OfflineBeer::Beer> and finally C<OfflineBeer>.\r
250 \r
251 B<CAVEAT> - if your Offline model overrides L<Class::DBI> methods, these methods\r
252 will B<not> be overridden when called from the Maypole application, because the\r
253 Maypole model provides an alternative path to L<Class::DBI> which is searched\r
254 first. The solution is to place such methods in a separate package, e.g.\r
255 C<OfflineBeer::CDBI>. Place this B<first> in the C<@ISA> of both\r
256 C<BeerDB2::Maypole::Model> and C<OfflineBeer>. Note that C<OfflineBeer::CDBI>\r
257 does not itself need to inherit from L<Class::DBI>.\r
258 \r
259 3. Methods defined in the Maypole model base class (C<BeerDB2::Maypole::Model>),\r
260 override methods in the individual Offline table classes, and in the Offline\r
261 model base class (C<Offline>). \r
262 \r
263 4. Relationships defined in the Offline classes are inherited by the Maypole\r
264 model.\r
265 \r
266 5. The Maypole model has full access to the underlying Offline model. \r
267 \r
268 =head3 Theory \r
269 \r
270 This layout illustrates more clearly why the Maypole model may be thought of as\r
271 part of the controller, rather than part of the model of MVC. Its function is to \r
272 mediate web requests, translating them into method calls on the Offline model, \r
273 munging the results, and returning them via the Maypole request object. \r
274 \r
275 Another way of thinking about it is that Maypole implements a two-layer\r
276 controller. The first layer translates a raw request into a single method call\r
277 on the Maypole model layer, which then translates that call into one or more\r
278 calls on the underlying model.\r
279 \r
280 Whatever label you prefer to use, this approach provides for clear separation of\r
281 concerns between the underlying model and the web/user interface, and that's\r
282 what it's all about.\r
283 \r
284 =head1 Advanced applications - building the model by hand ** TODO\r
285 \r
286 - using Maypole::Model::CDBI::Plain or Maypole::FormBuilder::Model::Plain\r
287 - setup_model() and load_model_subclass()\r
288 - cutting out all those separate paths to CDBI - they're confusing \r
289 \r
290 \r
291 =head1 Method inheritance ** TODO\r
292 \r
293 More description of Perl's left-to-right, depth-first method lookup, and where\r
294 it's particularly important in Maypole.\r
295 \r
296 \r
297           \r
298 =head1 AUTHOR\r
299 \r
300 David Baird, C<< <cpan@riverside-cms.co.uk> >>\r
301 \r
302 =head1 COPYRIGHT & LICENSE\r
303 \r
304 Copyright 2005 David Baird, All Rights Reserved.\r
305 \r
306 This text is free documentation; you can redistribute it and/or modify it\r
307 under the same terms as the Perl documentation itself.\r
308 \r
309 =cut\r
310 \r