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