Commit | Line | Data |
4d583dd8 |
1 | =head1 NAME |
2 | |
64ccd8a8 |
3 | Catalyst::Manual::Tutorial::CatalystBasics - Catalyst Tutorial - Part 2: Catalyst Application Development Basics |
4d583dd8 |
4 | |
5 | |
4d583dd8 |
6 | =head1 OVERVIEW |
7 | |
8 | This is B<Part 2 of 9> for the Catalyst tutorial. |
9 | |
64ccd8a8 |
10 | L<Tutorial Overview|Catalyst::Manual::Tutorial> |
4d583dd8 |
11 | |
12 | =over 4 |
13 | |
14 | =item 1 |
15 | |
16 | L<Introduction|Catalyst::Manual::Tutorial::Intro> |
17 | |
18 | =item 2 |
19 | |
20 | B<Catalyst Basics> |
21 | |
22 | =item 3 |
23 | |
653f4595 |
24 | L<Basic CRUD|Catalyst::Manual::Tutorial::BasicCRUD> |
4d583dd8 |
25 | |
26 | =item 4 |
27 | |
28 | L<Authentication|Catalyst::Manual::Tutorial::Authentication> |
29 | |
30 | =item 5 |
31 | |
32 | L<Authorization|Catalyst::Manual::Tutorial::Authorization> |
33 | |
34 | =item 6 |
35 | |
36 | L<Debugging|Catalyst::Manual::Tutorial::Debugging> |
37 | |
38 | =item 7 |
39 | |
40 | L<Testing|Catalyst::Manual::Tutorial::Testing> |
41 | |
42 | =item 8 |
43 | |
653f4595 |
44 | L<Advanced CRUD|Catalyst::Manual::Tutorial::AdvancedCRUD> |
4d583dd8 |
45 | |
46 | =item 9 |
47 | |
653f4595 |
48 | L<Appendices|Catalyst::Manual::Tutorial::Appendicies> |
4d583dd8 |
49 | |
50 | =back |
51 | |
4d583dd8 |
52 | =head1 DESCRIPTION |
53 | |
64ccd8a8 |
54 | In this part of the tutorial, we will create a very basic Catalyst web |
55 | application. Though simple in many respects, this section will already |
56 | demonstrate a number of powerful capabilities such as: |
4d583dd8 |
57 | |
58 | =over 4 |
59 | |
60 | =item * Helper Scripts |
61 | |
64ccd8a8 |
62 | Catalyst helper scripts that can be used to rapidly bootstrap the |
63 | skeletal structure of an application. |
4d583dd8 |
64 | |
65 | =item * MVC |
66 | |
64ccd8a8 |
67 | Model/View/Controller (MVC) provides an architecture that facilitates a |
68 | clean "separation of control" between the different portions of your |
653f4595 |
69 | application. Given that many other documents cover this subject in |
64ccd8a8 |
70 | detail, MVC will not be discussed in depth here (for an excellent |
71 | introduction to MVC and general Catalyst concepts, please see |
653f4595 |
72 | L<Catalyst::Manual::About>. In short: |
4d583dd8 |
73 | |
74 | =over 4 |
75 | |
76 | =item * Model |
77 | |
653f4595 |
78 | The model usually represents a data store. In most applications, the |
79 | model equates to the objects that are created from and saved to your SQL |
80 | database. |
4d583dd8 |
81 | |
82 | =item * View |
83 | |
64ccd8a8 |
84 | The view takes model objects and renders them into something for the end |
653f4595 |
85 | user to look at. Normally this involves a template-generation tool that |
64ccd8a8 |
86 | creates HTML for the user's web browser, but it could easily be code |
653f4595 |
87 | that generates other forms such as PDF documents, e-mails, or Excel |
88 | spreadsheets. |
4d583dd8 |
89 | |
90 | =item * Controller |
91 | |
64ccd8a8 |
92 | As suggested by its name, the controller takes user requests and routes |
93 | them to the necessary model and view. |
4d583dd8 |
94 | |
95 | =back |
96 | |
97 | =item * ORM |
98 | |
653f4595 |
99 | The use of Object-Relational Mapping (ORM) technology for database |
100 | access. Specifically, ORM provides an automated and standardized means |
101 | to persist and restore objects to/from a relational database. |
4d583dd8 |
102 | |
103 | =back |
104 | |
64ccd8a8 |
105 | B<TIP>: Note that all of the code for this part of the tutorial can be |
106 | pulled from the Catalyst Subversion repository in one step with the |
107 | following command: |
4d583dd8 |
108 | |
109 | svn checkout http://dev.catalyst.perl.org/repos/Catalyst/trunk/examples/Tutorial@### |
110 | IMPORTANT: Does not work yet. Will be completed for final version. |
111 | |
112 | |
4d583dd8 |
113 | =head1 CREATE A CATALYST PROJECT |
114 | |
64ccd8a8 |
115 | Catalyst provides a number of helper scripts that can be used to quickly |
653f4595 |
116 | flesh out the basic structure of your application. All Catalyst projects |
117 | begin with the C<catalyst.pl> helper. |
4d583dd8 |
118 | |
64ccd8a8 |
119 | In the case of this tutorial, use the Catalyst C<catalyst.pl> script to |
120 | initialize the framework for an application called C<MyApp>: |
4d583dd8 |
121 | |
122 | $ catalyst.pl MyApp |
123 | $ cd MyApp |
124 | |
64ccd8a8 |
125 | The C<catalyst.pl> helper script will display the names of the |
126 | directories and files it creates. |
4d583dd8 |
127 | |
653f4595 |
128 | Though it's too early for any significant celebration, we already have a |
129 | functioning application. Run the following command to run this |
130 | application with the built-in development web server: |
4d583dd8 |
131 | |
132 | $ script/myapp_server.pl |
133 | |
64ccd8a8 |
134 | Point your web browser to L<http://localhost:3000> (substituting a |
135 | different hostname or IP address as appropriate) and you should be |
136 | greeted by the Catalyst welcome screen. Press Ctrl-C to break out of |
137 | the development server. |
4d583dd8 |
138 | |
4d583dd8 |
139 | =head1 CREATE A SQLITE DATABASE |
140 | |
64ccd8a8 |
141 | In this step, we make a text file with the required SQL commands to |
142 | create a database table and load some sample data. Open C<myapp01.sql> |
143 | in your editor and enter: |
4d583dd8 |
144 | |
145 | -- |
146 | -- Create a very simple database to hold book and author information |
147 | -- |
148 | CREATE TABLE books ( |
149 | id INTEGER PRIMARY KEY, |
150 | title TEXT , |
151 | rating INTEGER |
152 | ); |
153 | -- 'book_authors' is a many-to-many join table between books & authors |
154 | CREATE TABLE book_authors ( |
155 | book_id INTEGER, |
156 | author_id INTEGER, |
157 | PRIMARY KEY (book_id, author_id) |
158 | ); |
159 | CREATE TABLE authors ( |
160 | id INTEGER PRIMARY KEY, |
161 | first_name TEXT, |
162 | last_name TEXT |
163 | ); |
164 | --- |
165 | --- Load some sample data |
166 | --- |
167 | INSERT INTO books VALUES (1, 'CCSP SNRS Exam Certification Guide', 5); |
168 | INSERT INTO books VALUES (2, 'TCP/IP Illustrated, Volume 1', 5); |
169 | INSERT INTO books VALUES (3, 'Internetworking with TCP/IP Vol.1', 4); |
170 | INSERT INTO books VALUES (4, 'Perl Cookbook', 5); |
171 | INSERT INTO books VALUES (5, 'Designing with Web Standards', 5); |
172 | INSERT INTO authors VALUES (1, 'Greg', 'Bastien'); |
173 | INSERT INTO authors VALUES (2, 'Sara', 'Nasseh'); |
174 | INSERT INTO authors VALUES (3, 'Christian', 'Degu'); |
175 | INSERT INTO authors VALUES (4, 'Richard', 'Stevens'); |
176 | INSERT INTO authors VALUES (5, 'Douglas', 'Comer'); |
177 | INSERT INTO authors VALUES (6, 'Tom', 'Christiansen'); |
178 | INSERT INTO authors VALUES (7, ' Nathan', 'Torkington'); |
179 | INSERT INTO authors VALUES (8, 'Jeffrey', 'Zeldman'); |
180 | INSERT INTO book_authors VALUES (1, 1); |
181 | INSERT INTO book_authors VALUES (1, 2); |
182 | INSERT INTO book_authors VALUES (1, 3); |
183 | INSERT INTO book_authors VALUES (2, 4); |
184 | INSERT INTO book_authors VALUES (3, 5); |
185 | INSERT INTO book_authors VALUES (4, 6); |
186 | INSERT INTO book_authors VALUES (4, 7); |
187 | INSERT INTO book_authors VALUES (5, 8); |
188 | |
64ccd8a8 |
189 | B<TIP>: See Appendix 1 for tips on removing the leading spaces when |
653f4595 |
190 | cutting and pasting example code from POD documents. |
4d583dd8 |
191 | |
192 | Then use the following command to build a C<myapp.db> SQLite database: |
193 | |
194 | $ sqlite3 myapp.db < myapp01.sql |
195 | |
64ccd8a8 |
196 | If you need to create the database more than once, you probably want to |
197 | issue the C<rm myapp.db> command to delete the database before you use |
198 | the C<sqlite3 myapp.db < myapp01.sql> command. |
4d583dd8 |
199 | |
64ccd8a8 |
200 | Once the C<myapp.db> database file has been created and initialized, you |
201 | can use the SQLite command line environment to do a quick dump of the |
202 | database contents: |
4d583dd8 |
203 | |
204 | $ sqlite3 myapp.db |
205 | SQLite version 3.2.2 |
206 | Enter ".help" for instructions |
207 | sqlite> select * from books; |
208 | 1|CCSP SNRS Exam Certification Guide|5 |
209 | 2|TCP/IP Illustrated, Volume 1|5 |
210 | 3|Internetworking with TCP/IP Vol.1|4 |
211 | 4|Perl Cookbook|5 |
212 | 5|Designing with Web Standards|5 |
213 | sqlite> .q |
214 | $ |
215 | |
216 | Or: |
217 | |
218 | $ sqlite3 myapp.db "select * from books" |
219 | 1|CCSP SNRS Exam Certification Guide|5 |
220 | 2|TCP/IP Illustrated, Volume 1|5 |
221 | 3|Internetworking with TCP/IP Vol.1|4 |
222 | 4|Perl Cookbook|5 |
223 | 5|Designing with Web Standards|5 |
224 | |
64ccd8a8 |
225 | As with most other SQL tools, if you are using the full "interactive" |
226 | environment you need to terminate your SQL commands with a ";" (it's not |
227 | required if you do a single SQL statement on the command line). Use |
228 | ".q" to exit from SQLite from the SQLite interactive mode and return to |
229 | your OS command prompt. |
4d583dd8 |
230 | |
231 | |
4d583dd8 |
232 | =head1 EDIT THE LIST OF CATALYST PLUGINS |
233 | |
64ccd8a8 |
234 | One of the greatest benefits of Catalyst is that it has such a large |
235 | library of plugins available. Plugins are used to seamlessly integrate |
236 | existing Perl modules into the overall Catalyst framework. In general, |
237 | they do this by adding additional methods to the C<context> object |
238 | (generally written as C<$c>) that Catalyst passes to every component |
239 | throughout the framework. |
4d583dd8 |
240 | |
241 | By default, Catalyst enables three plugins/flags: |
242 | |
243 | =over 4 |
244 | |
245 | =item * |
246 | |
247 | C<-Debug> Flag |
248 | |
64ccd8a8 |
249 | Enables the Catalyst debug output you saw when we started the |
250 | C<script/myapp_server.pl> development server earlier. You can remove |
251 | this plugin when you place your application into production. |
4d583dd8 |
252 | |
64ccd8a8 |
253 | As you may have noticed, C<-Debug> is not a plugin, but a I<flag>. |
254 | Although most of the items specified on the C<use Catalyst> line of your |
255 | application class will be plugins, Catalyst supports a limited number of |
256 | flag options (of these, C<-Debug> is the most common). |
4d583dd8 |
257 | |
64ccd8a8 |
258 | If you prefer, you can use the C<$c-E<gt>debug> method to enable debug |
259 | messages. |
4d583dd8 |
260 | |
261 | =item * |
262 | |
263 | L<Catalyst::Plugin::ConfigLoader|Catalyst::Plugin::ConfigLoader> |
264 | |
653f4595 |
265 | C<ConfigLoader> provides an automatic way to load configurable |
64ccd8a8 |
266 | parameters for your application from a central YAML file (versus having |
267 | the values hard-coded inside your Perl modules). If you have not been |
268 | exposed to YAML before, it is a human-readable data serialization format |
269 | that can be used to read (and write) values to/from text files. We will |
270 | see how to use this feature of Catalyst during the authentication and |
271 | authorization sections (Part 4 and Part 5). |
4d583dd8 |
272 | |
4d583dd8 |
273 | =item * |
274 | |
653f4595 |
275 | L<Catalyst::Plugin::Static::Simple> |
4d583dd8 |
276 | |
64ccd8a8 |
277 | C<Static::Simple> provides an easy method of serving static content such |
278 | as images and CSS files under the development server. |
4d583dd8 |
279 | |
280 | =back |
281 | |
64ccd8a8 |
282 | To modify the list of plugins, edit C<lib/MyApp.pm> (this file is |
283 | generally referred to as your I<application class>) and delete the line |
284 | with: |
4d583dd8 |
285 | |
286 | use Catalyst qw/-Debug ConfigLoader Static::Simple/; |
287 | |
288 | Replace it with: |
289 | |
290 | use Catalyst qw/ |
291 | -Debug |
292 | ConfigLoader |
293 | Static::Simple |
294 | |
295 | Dumper |
296 | StackTrace |
297 | DefaultEnd |
298 | /; |
299 | |
300 | This tells Catalyst to start using three new plugins: |
301 | |
302 | =over 4 |
303 | |
304 | =item * |
305 | |
653f4595 |
306 | L<Catalyst::Plugin::Dumper> |
4d583dd8 |
307 | |
653f4595 |
308 | Allows you to easily use L<Data::Dumper> to dump variables |
64ccd8a8 |
309 | to the logs, for example: |
4d583dd8 |
310 | |
311 | $c->log->dumper($myvar); |
312 | |
64ccd8a8 |
313 | When running your application under the development server, the logs |
314 | will be printed to your screen along with the other debug information |
315 | generated by the C<-Debug> flag. |
4d583dd8 |
316 | |
317 | =item * |
318 | |
319 | L<Catalyst::Plugin::StackTrace|Catalyst::Plugin::StackTrace> |
320 | |
64ccd8a8 |
321 | Adds a stack trace to the standard Catalyst "debug screen" (this is the |
322 | screen Catalyst sends to your browser when an error occurs). |
4d583dd8 |
323 | |
653f4595 |
324 | Note: L<Dumper|Catalyst::Plugin::Dumper> output appears on the console |
325 | window where you issue the C<script/myapp_server.pl> command. |
326 | L<StackTrace|Catalyst::Plugin::StackTrace> output appears in your |
327 | browser. |
4d583dd8 |
328 | |
329 | =item * |
330 | |
331 | L<Catalyst::Plugin::DefaultEnd|Catalyst::Plugin::DefaultEnd> |
332 | |
64ccd8a8 |
333 | Automatically provides a Catalyst "end action" that invokes your view at |
334 | the end of each request. Also allows you to add "dump_info=1" (precede |
335 | with "?" or "&" depending on where it is in the URL) to I<force> the |
336 | debug screen at the end of the Catalyst request processing cycle. |
4d583dd8 |
337 | |
653f4595 |
338 | B<TIP>: Many Catalyst-related documents predate |
64ccd8a8 |
339 | L<DefaultEnd|Catalyst::Plugin::DefaultEnd> and suggest that you add an |
340 | C<end> action to your application class (C<MyApp.pm>) or Root.pm |
341 | (C<MyApp/Controller/Root.pm>). In most of these cases, you can convert |
342 | to L<DefaultEnd|Catalyst::Plugin::DefaultEnd> by deleting the C<end> |
343 | action and using the plugin instead. |
4d583dd8 |
344 | |
345 | =back |
346 | |
64ccd8a8 |
347 | Note that when specifying plugins on the C<use Catalyst> line, you can |
348 | omit C<Catalyst::Plugin> from the name. Additionally, you can spread |
349 | the plugin names across multiple lines as shown here, or place them all |
350 | on one (or more) lines as with the default configuration. |
4d583dd8 |
351 | |
4d583dd8 |
352 | =head1 DATABASE ACCESS WITH C<DBIx::Class> |
353 | |
64ccd8a8 |
354 | Catalyst can be used with virtually any form of persistent datastore |
355 | available via Perl. For example, |
356 | L<Catalyst::Model::DBI|Catalyst::Model::DBI> can be used to easily |
357 | access databases through the traditional Perl DBI interface. However, |
358 | most Catalyst applications use some form of ORM technology to |
359 | automatically create and save model objects as they are used. Although |
360 | Tony Bowden's L<Class::DBI|Class::DBI> has been the traditional Perl ORM |
361 | engine, Matt Trout's L<DBIx::Class|DBIx::Class> (abbreviated as "DBIC") |
362 | has rapidly emerged as the Perl-based ORM technology of choice. Most |
363 | new Catalyst applications rely on DBIC, as will this tutorial. |
4d583dd8 |
364 | |
64ccd8a8 |
365 | Note: See L<Catalyst::Model::CDBI| Catalyst:: Model::CDBI > for more |
366 | information on using Catalyst with L<Class::DBI|Class::DBI>. Catalyst |
367 | can also be used with "plain old DBI"; see L<Catalyst::Model::DBI| |
368 | Catalyst::Model::DBI>. |
4d583dd8 |
369 | |
370 | |
371 | =head2 Create a DBIC Schema File |
372 | |
64ccd8a8 |
373 | DBIx::Class uses a schema file to load other classes that represent the |
374 | tables in your database (DBIC refers to these "table objects" as "result |
375 | sources," see L<DBIx::Class::ResultSource|DBIx::Class::ResultSource>). |
376 | In this case, we want to load the model object for the C<books>, |
377 | C<book_authors>, and C<authors> tables created in the previous step. |
4d583dd8 |
378 | |
379 | Open C<lib/MyAppDB.pm> in your editor and insert: |
380 | |
381 | package MyAppDB; |
382 | |
383 | =head1 NAME |
384 | |
385 | MyAppDB -- DBIC Schema Class |
386 | |
387 | =cut |
388 | |
389 | # Our schema needs to inherit from 'DBIx::Class::Schema' |
390 | use base qw/DBIx::Class::Schema/; |
391 | |
392 | # Need to load the DB Model classes here. |
393 | # You can use this syntax if you want: |
394 | # __PACKAGE__->load_classes(qw/Book BookAuthor Author/); |
395 | # Also, if you simply want to load all of the classes in a directory |
396 | # of the same name as your schema class (as we do here) you can use: |
397 | # __PACKAGE__->load_classes(qw//); |
398 | # But the variation below is more flexible in that it can be used to |
399 | # load from multiple namespaces. |
400 | __PACKAGE__->load_classes({ |
401 | MyAppDB => [qw/Book BookAuthor Author/] |
402 | }); |
403 | |
404 | 1; |
405 | |
64ccd8a8 |
406 | B<Note:> C<__PACKAGE__> is just a shorthand way of referencing the name |
407 | of the package where it is used. Therefore, in C<MyAppDB.pm>, |
408 | C<__PACKAGE> is equivalent to C<MyAppDB> |
4d583dd8 |
409 | |
410 | |
411 | =head2 Create the DBIC "Result Source" Files |
412 | |
64ccd8a8 |
413 | In this step, we create "table classes" (again, these are called a |
414 | "result source" classes in DBIC) that acts as model objects for the |
415 | C<books>, C<book_authors>, and C<authors> tables in our database. |
4d583dd8 |
416 | |
417 | First, create a directory to hold the class: |
418 | |
419 | $ mkdir lib/MyAppDB |
420 | |
421 | Then open C<lib/MyAppDB/Book.pm> in your editor and enter: |
422 | |
423 | package MyAppDB::Book; |
424 | |
425 | use base qw/DBIx::Class/; |
426 | |
427 | # Load required DBIC stuff |
428 | __PACKAGE__->load_components(qw/PK::Auto Core/); |
429 | # Set the table name |
430 | __PACKAGE__->table('books'); |
431 | # Set columns in table |
432 | __PACKAGE__->add_columns(qw/id title rating/); |
433 | # Set the primary key for the table |
434 | __PACKAGE__->set_primary_key(qw/id/); |
435 | |
436 | # |
437 | # Set relationships: |
438 | # |
439 | |
440 | # has_many(): |
441 | # args: |
442 | # 1) Name of relationship, DBIC will create accessor with this name |
443 | # 2) Name of the model class referenced by this relationship |
444 | # 3) Column name in *foreign* table |
445 | __PACKAGE__->has_many(book_authors => 'MyAppDB::BookAuthor', 'book_id'); |
446 | |
447 | # many_to_many(): |
448 | # args: |
449 | # 1) Name of relationship, DBIC will create accessor with this name |
450 | # 2) Name of has_many() relationship this many_to_many() is shortcut for |
451 | # 3) Name of belongs_to() relationship in model class of has_many() above |
452 | # You must already have the has_many() defined to use a many_to_many(). |
453 | __PACKAGE__->many_to_many(authors => 'book_authors', 'author'); |
454 | |
455 | |
456 | =head1 NAME |
457 | |
458 | MyAppDB::Book - A model object representing a book. |
459 | |
460 | =head1 DESCRIPTION |
461 | |
462 | This is an object that represents a row in the 'books' table of your application |
463 | database. It uses DBIx::Class (aka, DBIC) to do ORM. |
464 | |
465 | For Catalyst, this is designed to be used through MyApp::Model::MyAppDB. |
466 | Offline utilities may wish to use this class directly. |
467 | |
468 | =cut |
469 | |
470 | 1; |
471 | |
64ccd8a8 |
472 | This defines both a C<has_many> and a C<many_to_many> relationship. The |
473 | C<many_to_many> relationship is optional, but it makes it easier to map |
474 | a book to its collection of authors. Without it, we would have to |
475 | "walk" though the C<book_authors> table as in |
476 | C<$book-E<gt>book_authors-E<gt>first-E<gt>author-E<gt>last_name> (we |
477 | will see examples on how to use DBIC objects in your code soon, but note |
478 | that because C<$book-E<gt>book_authors> can return multiple authors, we |
479 | have to use C<first> to display a single author). C<many_to_many> |
480 | allows us to use the shorter |
481 | C<$book-E<gt>authors-E<gt>first-E<gt>last_name>. Note that you cannot |
482 | define a C<many_to_many> relationship without also having the |
483 | C<has_many> relationship in place. |
4d583dd8 |
484 | |
485 | Next, open C<lib/MyAppDB/Author.pm> in your editor and enter: |
486 | |
487 | package MyAppDB::Author; |
488 | |
489 | use base qw/DBIx::Class/; |
490 | |
491 | # Load required DBIC stuff |
492 | __PACKAGE__->load_components(qw/PK::Auto Core/); |
493 | # Set the table name |
494 | __PACKAGE__->table('authors'); |
495 | # Set columns in table |
496 | __PACKAGE__->add_columns(qw/id first_name last_name/); |
497 | # Set the primary key for the table |
498 | __PACKAGE__->set_primary_key(qw/id/); |
499 | |
500 | # |
501 | # Set relationships: |
502 | # |
503 | |
504 | # has_many(): |
505 | # args: |
506 | # 1) Name of relationship, DBIC will create accessor with this name |
507 | # 2) Name of the model class referenced by this relationship |
508 | # 3) Column name in *foreign* table |
509 | __PACKAGE__->has_many(book_author => 'MyAppDB::BookAuthor', 'author_id'); |
510 | |
511 | # many_to_many(): |
512 | # args: |
513 | # 1) Name of relationship, DBIC will create accessor with this name |
514 | # 2) Name of has_many() relationship this many_to_many() is shortcut for |
515 | # 3) Name of belongs_to() relationship in model class of has_many() above |
516 | # You must already have the has_many() defined to use a many_to_many(). |
517 | __PACKAGE__->many_to_many(books => 'book_author', 'book'); |
518 | |
519 | |
520 | =head1 NAME |
521 | |
522 | MyAppDB::Author - A model object representing an author of a book (if a book has |
523 | multiple authors, each will be represented be separate Author object). |
524 | |
525 | =head1 DESCRIPTION |
526 | |
527 | This is an object that represents a row in the 'authors' table of your application |
528 | database. It uses DBIx::Class (aka, DBIC) to do ORM. |
529 | |
530 | For Catalyst, this is designed to be used through MyApp::Model::MyAppDB. |
531 | Offline utilities may wish to use this class directly. |
532 | |
533 | =cut |
534 | |
535 | 1; |
536 | |
537 | Finally, open C<lib/MyAppDB/BookAuthor.pm> in your editor and enter: |
538 | |
539 | package MyAppDB::BookAuthor; |
540 | |
541 | use base qw/DBIx::Class/; |
542 | |
543 | # Load required DBIC stuff |
544 | __PACKAGE__->load_components(qw/PK::Auto Core/); |
545 | # Set the table name |
546 | __PACKAGE__->table('book_authors'); |
547 | # Set columns in table |
548 | __PACKAGE__->add_columns(qw/book_id author_id/); |
549 | # Set the primary key for the table |
550 | __PACKAGE__->set_primary_key(qw/book_id author_id/); |
551 | |
552 | # |
553 | # Set relationships: |
554 | # |
555 | |
556 | # belongs_to(): |
557 | # args: |
558 | # 1) Name of relationship, DBIC will create accessor with this name |
559 | # 2) Name of the model class referenced by this relationship |
560 | # 3) Column name in *this* table |
561 | __PACKAGE__->belongs_to(book => 'MyAppDB::Book', 'book_id'); |
562 | |
563 | # belongs_to(): |
564 | # args: |
565 | # 1) Name of relationship, DBIC will create accessor with this name |
566 | # 2) Name of the model class referenced by this relationship |
567 | # 3) Column name in *this* table |
568 | __PACKAGE__->belongs_to(author => 'MyAppDB::Author', 'author_id'); |
569 | |
570 | |
571 | =head1 NAME |
572 | |
573 | MyAppDB::BookAuthor - A model object representing the JOIN between an author and |
574 | a book. |
575 | |
576 | =head1 DESCRIPTION |
577 | |
578 | This is an object that represents a row in the 'book_authors' table of your |
579 | application database. It uses DBIx::Class (aka, DBIC) to do ORM. |
580 | |
581 | You probably won't need to use this class directly -- it will be automatically |
582 | used by DBIC where joins are needed. |
583 | |
584 | For Catalyst, this is designed to be used through MyApp::Model::MyAppDB. |
585 | Offline utilities may wish to use this class directly. |
586 | |
587 | =cut |
588 | |
589 | 1; |
590 | |
64ccd8a8 |
591 | B<Note:> This sample application uses a plural form for the database |
592 | tables (e.g., C<books> and C<authors>) and a singular form for the model |
593 | objects (e.g., C<Book> and C<Author>); however, Catalyst places no |
594 | restrictions on the naming conventions you wish to use. |
4d583dd8 |
595 | |
596 | |
597 | =head2 Use C<Catalyst::Model::DBIC::Schema> To Load The Model Class |
598 | |
64ccd8a8 |
599 | When L<Catalyst::Model::DBIC::Schema|Catalyst::Model::DBIC::Schema> is |
600 | in use, Catalyst essentially reads an existing copy of your database |
601 | model and creates a new set of objects under C<MyApp::Model> for use |
602 | inside of Catalyst. |
4d583dd8 |
603 | |
64ccd8a8 |
604 | B<Note:> With |
605 | L<Catalyst::Model::DBIC::Schema|Catalyst::Model::DBIC::Schema> you |
606 | essentially end up with two sets of model classes (only one of which you |
607 | write... the other set is created automatically in memory when your |
608 | Catalyst application initializes). For this tutorial application, the |
609 | important points to remember are: you write the I<result source> files |
610 | in C<MyAppDB>, but I<within Catalyst> you use the I<automatically |
611 | created model classes> in C<MyApp::Model>. |
4d583dd8 |
612 | |
64ccd8a8 |
613 | Use the L<Catalyst::Helper::Model::DBIC::Schema| |
614 | Catalyst::Helper::Model::DBIC::Schema > helper script to create the |
615 | model class that loads up the model we created in the previous step: |
4d583dd8 |
616 | |
617 | $ script/myapp_create.pl model MyAppDB DBIC::Schema MyAppDB dbi:SQLite:myapp.db '' '' '{ AutoCommit => 1 }' |
618 | |
64ccd8a8 |
619 | Where the first C<MyAppDB> is the name of the class to be created by the |
620 | helper in C<lib/MyApp/Model> and the second C<MyAppDB> is the name of |
621 | existing schema file we created (in C<lib/MyAppDB.pm>). You can see |
622 | that the helper creates a model file under C<lib/MyApp/Model> (Catalyst |
623 | has a separate directory under C<lib/MyApp> for each of the three parts |
624 | of MVC: C<Model>, C<View>, and C<Controller> [although older Catalyst |
625 | applications often use the directories C<M>, C<V>, and C<C>]). |
4d583dd8 |
626 | |
627 | |
628 | |
629 | =head1 CREATE A CATALYST CONTROLLER |
630 | |
631 | Controllers are where you write methods that respond to C<GET> and C<POST> messages from the user's web browser. |
632 | |
633 | Use the Catalyst C<create> script to add a controller for book-related actions: |
634 | |
635 | $ script/myapp_create.pl controller Books |
636 | |
64ccd8a8 |
637 | Then edit C<lib/MyApp/Controller/Books.pm> and add the following method |
638 | to the controller: |
4d583dd8 |
639 | |
640 | =head2 list |
641 | |
642 | Fetch all book objects and pass to books/list.tt2 in stash to be displayed |
643 | |
644 | =cut |
645 | |
646 | sub list : Local { |
647 | # Retrieve the usual perl OO '$self' for this object. $c is the Catalyst |
648 | # 'Context' that's used to 'glue together' the various components |
649 | # that make up the application |
650 | my ($self, $c) = @_; |
651 | |
652 | # Retrieve all of the book records as book model objects and store in the |
653 | # stash where they can be accessed by the TT template |
654 | $c->stash->{books} = [$c->model('MyAppDB::Book')->all]; |
655 | |
656 | # Set the TT template to use. You will almost always want to do this |
657 | # in your action methods. |
658 | $c->stash->{template} = 'books/list.tt2'; |
659 | } |
660 | |
64ccd8a8 |
661 | B<Note:> Programmers experienced with object-oriented Perl should |
662 | recognize C<$self> as a reference to the object where this method was |
663 | called. On the other hand, C<$c> will be new to many Perl programmers |
664 | who have not used Catalyst before (it's sometimes written as |
665 | C<$context>). The Context object is automatically passed to all |
666 | Catalyst components. It is used to pass information between components |
667 | and provide access to Catalyst and plugin functionality. |
4d583dd8 |
668 | |
64ccd8a8 |
669 | B<TIP>: You may see the C<$c-E<gt>model('MyAppDB::Book')> used above |
670 | written as C<$c-E<gt>model('MyAppDB')-E<gt>resultset('Book)>. The two |
671 | are equivalent. |
4d583dd8 |
672 | |
64ccd8a8 |
673 | B<Note:> Catalyst actions are regular Perl methods, but they make use of |
674 | Nicholas Clark's C<attributes> module to provide additional information |
675 | to the Catalyst dispatcher logic. |
4d583dd8 |
676 | |
677 | |
678 | =head1 CATALYST VIEWS |
679 | |
64ccd8a8 |
680 | Views are where you render output for display in the user's web browser |
681 | (or possibly using other display technology). As with virtually every |
682 | aspect of Catalyst, options abound when it comes to the specific view |
683 | technology you adopt inside your application. However, most Catalyst |
684 | applications use the Template Toolkit, known as TT (for more information |
685 | on TT, see L<http://www.template-toolkit.org>). Other popular View |
686 | technologies include Mason (L<http://www.masonhq.com> and |
687 | L<http://www.masonbook.com>) and L<HTML::Template|HTML::Template> |
688 | (L<http://html-template.sourceforge.net>). |
4d583dd8 |
689 | |
690 | |
691 | =head2 Create a Catalyst View Using C<TTSITE> |
692 | |
693 | When using TT for the Catalyst view, there are two main helper scripts: |
694 | |
695 | =over 4 |
696 | |
697 | =item * |
698 | |
699 | L<Catalyst::Helper::View::TT|Catalyst::Helper::View::TT> |
700 | |
701 | =item * |
702 | |
703 | L<Catalyst::Helper::View::TTSite|Catalyst::Helper::View::TTSite> |
704 | |
705 | =back |
706 | |
64ccd8a8 |
707 | Both are similar, but C<TT> merely creates the C<lib/MyApp/View/TT.pm> |
708 | file and leaves the creation of any hierarchical template organization |
709 | entirely up to you (it also creates a C<t/view_TT.t> file for testing; |
710 | test cases will be discussed in Part 7). Conversely, the C<TTSite> |
711 | helper creates a modular and hierarchical view layout with separate |
712 | Template Toolkit (TT) files for common header and footer information, |
713 | configuration values, a CSS stylesheet, etc. |
4d583dd8 |
714 | |
64ccd8a8 |
715 | Enter the following command to enable the C<TTSite> style of view |
716 | rendering for the tutorial: |
4d583dd8 |
717 | |
718 | $ script/myapp_create.pl view TT TTSite |
719 | |
64ccd8a8 |
720 | This puts a number of files in the C<root/lib> and C<root/src> |
721 | directories that can be used to customize the look and feel of your |
722 | application. Also take a look at C<lib/MyApp/View/TT.pm> for config |
723 | values set by the C<TTSite> helper. |
724 | |
725 | B<TIP>: Note that TTSite does one thing that could confuse people who |
726 | are used to the normal C<TT> Catalyst View: it redefines the Catalyst |
727 | context object in templates from its usual C<c> to C<Catalyst>. Also |
728 | keep this in mind when looking at other Catalyst examples (they almost |
729 | always use C<c>). Note that Catalyst and TT I<do not complain> when you |
730 | use the wrong name to access the context... it simply outputs blanks for |
731 | that bogus logic. Finally, be aware that this change in name I<only> |
732 | applies to how the context object is accessed inside your TT templates, |
733 | your controllers will continue to use C<$c> (or whatever name you use |
734 | when fetching the reference from C<@_> inside your methods). (You can |
735 | change back to the "default" behavior be removing the C<CATALYST_VAR> |
736 | line from C<lib/MyApp/View/TT.pm>, but you will also have to edit |
737 | C<root/lib/config/main> and C<root/lib/config/url>. If you do this, be |
738 | careful not to have a collision between your own C<c> variable and the |
739 | Catalyst C<c> variable.) |
4d583dd8 |
740 | |
741 | |
742 | |
743 | =head2 Globally Customize Every View |
744 | |
64ccd8a8 |
745 | When using TTSite, files in the subdirectories of C<root/lib> can be |
746 | used to make changes that will appear in every view. For example, to |
747 | display optional status and error messages in every view, edit |
748 | C<root/lib/site/layout> update it to match the following (the two HTML |
749 | C<span> elements are new): |
4d583dd8 |
750 | |
751 | <div id="header">[% PROCESS site/header %]</div> |
752 | |
753 | <div id="content"> |
754 | <span class="message">[% status_msg %]</span> |
755 | <span class="error">[% error_msg %]</span> |
756 | [% content %] |
757 | </div> |
758 | |
759 | <div id="footer">[% PROCESS site/footer %]</div> |
760 | |
64ccd8a8 |
761 | If we set either message in the Catalyst stash (e.g., |
762 | C<$c-E<gt>stash-E<gt>{status_msg} = 'Hello world'>) it will be displayed |
763 | whenever any view used by that request is rendered. The C<message> and |
764 | C<error> CSS styles are automatically defined in C<root/src/ttsite.css> |
765 | and can be customized to suit your needs. |
4d583dd8 |
766 | |
64ccd8a8 |
767 | B<Note:> The Catalyst stash only lasts for a single HTTP request. If |
768 | you need to retain information across requests you can use |
769 | L<Catalyst::Plugin::Session|Catalyst::Plugin::Session> (we will use |
770 | Catalyst sessions in the Authentication part). |
4d583dd8 |
771 | |
772 | |
773 | =head2 Create a TT Template Page |
774 | |
64ccd8a8 |
775 | To add a new page of content to the TTSite view hierarchy, just create a |
776 | new C<.tt2> file in C<root/src>. Only include HTML markup that goes |
777 | inside the HTML <body> and </body> tags, TTSite will use the contents of |
778 | C<root/lib/site> to add the top and bottom. |
4d583dd8 |
779 | |
780 | First create a directory for book-related TT templates: |
781 | |
782 | $ mkdir root/src/books |
783 | |
784 | Then open C<root/src/books/list.tt2> in your editor and enter: |
785 | |
786 | [% # This is a TT comment. The '-' at the end "chomps" the newline. You won't -%] |
787 | [% # see this "chomping" in your browser because HTML ignores blank lines, but -%] |
788 | [% # it WILL eliminate a blank line if you view the HTML source. It's purely -%] |
789 | [%- # optional, but both the beginning and the ending TT tags support chomping. -%] |
790 | |
791 | [% # Provide a title to root/lib/site/header -%] |
792 | [% META title = 'Book List' -%] |
793 | |
794 | <table> |
795 | <tr><th>Title</th><th>Rating</th><th>Author(s)</th></tr> |
796 | [% # Display each book in a table row %] |
797 | [% FOREACH book IN books -%] |
798 | <tr> |
799 | <td>[% book.title %]</td> |
800 | <td>[% book.rating %]</td> |
801 | <td> |
802 | [% # Print author count in parens. 'book.authors' uses the 'many_to_many' -%] |
803 | [% # relationship to retrieve all of the authors of a book. 'size' is a -%] |
804 | [% # TT VMethod to get the number of elements in a list. -%] |
805 | ([% book.authors.size %]) |
806 | [% # Use an alternate form of a FOREACH loop to display authors. -%] |
807 | [% # _ below is the TT string concatenation operator. -%] |
808 | [% author.last_name _' ' FOREACH author = book.authors %] |
809 | [% # Note: if many_to_many relationship not used in Authors.pm, you could -%] |
810 | [% # have used the following to 'walk' through the 'join table objects' -%] |
811 | [% # bk_author.author.last_name _' ' FOREACH bk_author = book.book_authors %] |
812 | </td> |
813 | </tr> |
814 | [% END -%] |
815 | </table> |
816 | |
64ccd8a8 |
817 | As indicated by the inline comments above, the C<META title> line uses |
818 | TT's META feature to provide a title to C<root/lib/site/header>. |
819 | Meanwhile, the outer C<FOREACH> loop iterates through each C<book> model |
820 | object and prints the C<title> and C<rating> fields. An inner |
821 | C<FOREACH> loop prints the last name of each author in a single table |
822 | cell (a simple space is used between the names; in reality you would |
823 | probably want to modify the code to use a comma as a separator). |
824 | |
825 | If you are new to TT, the [% and %] tags are used to delimit "variable |
826 | text". TT supports a wide variety of directives for "calling" other |
827 | files, looping, conditional logic, etc. In general, TT simplifies the |
828 | usual range of Perl operators down to the single dot (C<.>) operator. |
829 | This applies to operations as diverse as method calls, hash lookups, and |
830 | list index values (see |
831 | L<http://www.template-toolkit.org/docs/default/Manual/Variables.html> |
832 | for details and examples). In addition to the usual C<Template> module |
833 | Pod documentation, you can access the TT manual at |
834 | L<http://www.template-toolkit.org/docs/default/>. |
835 | |
836 | B<NOTE>: The C<TTSite> helper creates several TT files using an |
837 | extension of C<.tt2>. Most other Catalyst and TT examples use an |
838 | extension of C<.tt>. You can use either extension (or no extension at |
839 | all) with TTSite and TT, just be sure to use the appropriate extension |
840 | for both the file itself I<and> the C<$c-E<gt>stash-E<gt>{template} = |
841 | ...> line in your controller. This document will use C<.tt2> for |
842 | consistency with the files already created by the C<TTSite> helper. |
4d583dd8 |
843 | |
844 | |
845 | |
846 | =head1 RUN THE APPLICATION |
847 | |
64ccd8a8 |
848 | First, let's enable an environment variable option that causes |
849 | DBIx::Class to dump the SQL statements it's using to access the database |
850 | (this option can provide extremely helpful troubleshooting information): |
4d583dd8 |
851 | |
852 | $ export DBIX_CLASS_STORAGE_DBI_DEBUG=1 |
853 | |
64ccd8a8 |
854 | B<NOTE>: You can also set this in your code using |
855 | C<$class-E<gt>storage-E<gt>debug(1);>. See |
856 | L<DBIx::Class::Manual::Troubleshooting|DBIx::Class::Manual::Troubleshooting> |
857 | for details (including options to log to file vs. the Catalyst |
858 | development server log. |
4d583dd8 |
859 | |
860 | Then run the Catalyst "demo server" script: |
861 | |
862 | $ script/myapp_server.pl |
863 | |
864 | You should get something like this: |
865 | |
866 | $ script/myapp_server.pl |
867 | [Tue May 16 12:51:33 2006] [catalyst] [debug] Debug messages enabled |
868 | [Tue May 16 12:51:33 2006] [catalyst] [debug] Loaded plugins: |
869 | .------------------------------------------------------------------------------. |
870 | | Catalyst::Plugin::ConfigLoader 0.07 | |
871 | | Catalyst::Plugin::Static::Simple 0.14 | |
872 | | Catalyst::Plugin::Dumper 0.000002 | |
873 | | Catalyst::Plugin::StackTrace 0.04 | |
874 | | Catalyst::Plugin::DefaultEnd 0.06 | |
875 | '------------------------------------------------------------------------------' |
876 | |
877 | [Tue May 16 12:51:33 2006] [catalyst] [debug] Loaded dispatcher "Catalyst::Dispatcher" |
878 | [Tue May 16 12:51:33 2006] [catalyst] [debug] Loaded engine "Catalyst::Engine::HTTP" |
879 | [Tue May 16 12:51:33 2006] [catalyst] [debug] Found home "/home/me/MyApp" |
880 | [Tue May 16 12:51:37 2006] [catalyst] [debug] Loaded components: |
881 | .-------------------------------------------------------------------+----------. |
882 | | Class | Type | |
883 | +-------------------------------------------------------------------+----------+ |
884 | | MyApp::Controller::Books | instance | |
885 | | MyApp::Controller::Root | instance | |
886 | | MyApp::Model::MyAppDB | instance | |
887 | | MyApp::Model::MyAppDB::Author | class | |
888 | | MyApp::Model::MyAppDB::Book | class | |
889 | | MyApp::Model::MyAppDB::BookAuthor | class | |
890 | | MyApp::View::TT | instance | |
891 | '-------------------------------------------------------------------+----------' |
892 | |
893 | [Tue May 16 12:51:37 2006] [catalyst] [debug] Loaded Private actions: |
894 | .----------------------+----------------------------------------+--------------. |
895 | | Private | Class | Method | |
896 | +----------------------+----------------------------------------+--------------+ |
897 | | /default | MyApp::Controller::Root | default | |
898 | | /end | MyApp | end | |
899 | | /books/list | MyApp::Controller::Books | list | |
900 | '----------------------+----------------------------------------+--------------' |
901 | |
902 | [Tue May 16 12:51:37 2006] [catalyst] [debug] Loaded Path actions: |
903 | .--------------------------------------+---------------------------------------. |
904 | | Path | Private | |
905 | +--------------------------------------+---------------------------------------+ |
906 | | /books/list | /books/list | |
907 | '--------------------------------------+---------------------------------------' |
908 | |
909 | [Tue May 16 12:51:37 2006] [catalyst] [info] MyApp powered by Catalyst 5.6902 |
910 | You can connect to your server at http://localhost:3000 |
911 | |
912 | Some things you should note in the output above: |
913 | |
914 | =over 4 |
915 | |
916 | =item * |
917 | |
64ccd8a8 |
918 | Catalyst::Model::DBIC::Schema took our C<MyAppDB::Book> and made it |
919 | C<MyApp::Model::MyAppDB::Book> (and similar actions were performed on |
920 | C<MyAppDB::Author> and C<MyAppDB::BookAuthor>). |
4d583dd8 |
921 | |
922 | =item * |
923 | |
64ccd8a8 |
924 | The "list" action in our Books controller showed up with a path of |
925 | C</books/list>. |
4d583dd8 |
926 | |
927 | =back |
928 | |
929 | |
64ccd8a8 |
930 | Point your browser to L<http://localhost:3000> and you should still get |
931 | the Catalyst welcome page. |
4d583dd8 |
932 | |
64ccd8a8 |
933 | Next, to view the book list, change the URL in your browser to |
934 | L<http://localhost:3000/books/list>. You should get a list of the five |
935 | books loaded by the C<myapp01.sql> script above, with TTSite providing |
936 | the formatting for the very simple output we generated in our template. |
937 | The count and space-separated list of author last names appear on the |
938 | end of each row. |
4d583dd8 |
939 | |
64ccd8a8 |
940 | Also notice in the output of the C<script/myapp_server.pl> that DBIC |
941 | used the following SQL to retrieve the data: |
4d583dd8 |
942 | |
943 | SELECT me.id, me.title, me.rating FROM books me |
944 | |
64ccd8a8 |
945 | Along with a list of the following commands to retrieve the authors for |
946 | each book (the lines have been "word wrapped" here to improve |
947 | legibility): |
4d583dd8 |
948 | |
949 | SELECT author.id, author.first_name, author.last_name |
950 | FROM book_authors me |
951 | JOIN authors author ON ( author.id = me.author_id ) |
952 | WHERE ( me.book_id = ? ): `1' |
953 | |
64ccd8a8 |
954 | You should see 10 such lines of debug output, two for each of the five |
955 | author_id values (it pulls the data once for the count logic and another |
956 | time to actually display the list). |
4d583dd8 |
957 | |
958 | |
959 | =head1 AUTHOR |
960 | |
961 | Kennedy Clark, C<hkclark@gmail.com> |
962 | |
963 | Please report any errors, issues or suggestions to the author. |
964 | |
965 | Copyright 2006, Kennedy Clark, under Creative Commons License (L<http://creativecommons.org/licenses/by-nc-sa/2.5/>). |
966 | |
967 | Version: .94 |
968 | |