[Catalyst-commits] r10277 - in
Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial: .
09_AdvancedCRUD
hkclark at dev.catalyst.perl.org
hkclark at dev.catalyst.perl.org
Sun May 24 22:30:48 GMT 2009
Author: hkclark
Date: 2009-05-24 22:30:47 +0000 (Sun, 24 May 2009)
New Revision: 10277
Added:
Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/01_Intro.pod
Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/02_CatalystBasics.pod
Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/03_MoreCatalystBasics.pod
Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/04_BasicCRUD.pod
Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/05_Authentication.pod
Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/06_Authorization.pod
Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/07_Debugging.pod
Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/08_Testing.pod
Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/09_AdvancedCRUD.pod
Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/09_AdvancedCRUD/
Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/09_AdvancedCRUD/09_FormBuilder.pod
Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/09_AdvancedCRUD/09_FormFu.pod
Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/10_Appendices.pod
Removed:
Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/09_AdvancedCRUD/FormBuilder.pod
Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/09_AdvancedCRUD/FormFu.pod
Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/AdvancedCRUD.pod
Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/AdvancedCRUD/
Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/Appendices.pod
Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/Authentication.pod
Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/Authorization.pod
Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/BasicCRUD.pod
Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/CatalystBasics.pod
Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/Debugging.pod
Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/Intro.pod
Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/MoreCatalystBasics.pod
Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/Testing.pod
Log:
Add numbers back to names of chapters
Copied: Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/01_Intro.pod (from rev 10276, Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/Intro.pod)
===================================================================
--- Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/01_Intro.pod (rev 0)
+++ Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/01_Intro.pod 2009-05-24 22:30:47 UTC (rev 10277)
@@ -0,0 +1,642 @@
+=head1 NAME
+
+Catalyst::Manual::Tutorial::01_Intro - Catalyst Tutorial - Chapter 1: Introduction
+
+
+=head1 OVERVIEW
+
+This is B<Chapter 1 of 10> for the Catalyst tutorial.
+
+L<Tutorial Overview|Catalyst::Manual::Tutorial>
+
+=over 4
+
+=item 1
+
+B<01_Introduction>
+
+=item 2
+
+L<Catalyst Basics|Catalyst::Manual::Tutorial::02_CatalystBasics>
+
+=item 3
+
+L<More Catalyst Basics|Catalyst::Manual::Tutorial::03_MoreCatalystBasics>
+
+=item 4
+
+L<Basic CRUD|Catalyst::Manual::Tutorial::04_BasicCRUD>
+
+=item 5
+
+L<Authentication|Catalyst::Manual::Tutorial::05_Authentication>
+
+=item 6
+
+L<Authorization|Catalyst::Manual::Tutorial::06_Authorization>
+
+=item 7
+
+L<Debugging|Catalyst::Manual::Tutorial::07_Debugging>
+
+=item 8
+
+L<Testing|Catalyst::Manual::Tutorial::08_Testing>
+
+=item 9
+
+L<Advanced CRUD|Catalyst::Manual::Tutorial::09_AdvancedCRUD>
+
+=item 10
+
+L<Appendices|Catalyst::Manual::Tutorial::10_Appendices>
+
+=back
+
+
+=head1 DESCRIPTION
+
+This tutorial provides a multi-part introduction to the Catalyst web
+framework. It seeks to provide a rapid overview of many of its most
+commonly used features. The focus is on the real-world best practices
+required in the construction of nearly all Catalyst applications.
+
+Although the primary target of the tutorial is users new to the Catalyst
+framework, experienced users may wish to review specific sections (for
+example, how to use DBIC for their model classes, how to add
+authentication and authorization to an existing application, or form
+management).
+
+You can obtain the code for all the tutorial examples from the
+catalyst subversion repository by issuing the command:
+
+ svn co http://dev.catalyst.perl.org/repos/Catalyst/trunk/examples/Tutorial/ CatalystTutorial
+
+This will download the most recent tarball for each chapter of the
+tutorial into the CatalystTutorial directory on your machine.
+
+B<These reference implementations are provided so that when you follow
+the tutorial, you can use the code from the subversion repository to
+ensure that your system is set up correctly, and that you have not
+inadvertently made any typographic errors, or accidentally skipped
+part of the tutorial.>
+
+B<NOTE: You can use any Perl-supported OS and environment to run
+Catalyst.> It should make little or no difference to Catalyst's
+operation, B<but this tutorial has been written using the Debian 5
+live CD> because that represents a quick and easy for most people to
+try out Catalyst with virtually zero setup time and hassles. Also,
+the tutorial has been tested to work correctly with the versions of
+Catalyst and all the supporting modules in Debian 5 (see "VERSIONS
+AND CONVENTIONS USED IN THIS TUTORIAL" below for the specific versions
+for some of the key modules), so B<if you think you might be running
+into an issue related to versions> (for example, a module changed its
+behavior in a newer version or a bug was introduced), B<it might be
+worth giving Debian 5 a try>. See the "CATALYST INSTALLATION"
+section below for more information.
+
+If you're reading this manual online, you can download the example
+program and all the necessary dependencies to your local machine by
+installing the C<Task::Catalyst::Tutorial> distribution from CPAN:
+
+ cpan Task::Catalyst::Tutorial
+
+This will also test to make sure the dependencies are working. If you
+have trouble installing these, please ask for help on the #catalyst
+IRC channel, or the Catalyst mailing list.
+
+Subjects covered by the tutorial include:
+
+=over 4
+
+=item *
+
+A simple application that lists and adds books.
+
+=item *
+
+The use of L<DBIx::Class|DBIx::Class> (DBIC) for the model (including
+some of the more advanced techniques you will probably want to use in
+your applications).
+
+=item *
+
+How to write CRUD (Create, Read, Update, and Delete) operations in
+Catalyst.
+
+=item *
+
+Authentication ("auth").
+
+=item *
+
+Role-based authorization ("authz").
+
+=item *
+
+Attempts to provide an example showing current (5.8XXX) Catalyst
+practices. For example, the use of
+L<Catalyst::Action::RenderView|Catalyst::Action::RenderView>,
+DBIC, L<Catalyst::Plugin::ConfigLoader|Catalyst::Plugin::ConfigLoader>
+with C<myapp.conf>, the use of C<lib/MyApp/Controller/Root.pm>
+vs. C<lib/MyApp.pm>, etc.
+
+=item *
+
+The use of Template Toolkit (TT).
+
+=item *
+
+Useful techniques for troubleshooting and debugging Catalyst
+applications.
+
+=item *
+
+The use of SQLite as a database (with code also provided for MySQL and
+PostgreSQL).
+
+=item *
+
+The use of L<HTML::FormFu|HTML::FormFu> for automated form processing
+and validation.
+
+=back
+
+This tutorial makes the learning process its main priority. For
+example, the level of comments in the code found here would likely be
+considered excessive in a "normal project." Because of their contextual
+value, this tutorial will generally favor inline comments over a
+separate discussion in the text. It also deliberately tries to
+demonstrate multiple approaches to various features (in general, you
+should try to be as consistent as possible with your own production
+code).
+
+Furthermore, this tutorial tries to minimize the number of controllers,
+models, TT templates, and database tables. Although this does result in
+things being a bit contrived at times, the concepts should be applicable
+to more complex environments. More complete and complicated example
+applications can be found in the C<examples> area of the Catalyst
+Subversion repository at
+L<http://dev.catalyst.perl.org/repos/Catalyst/trunk/examples/>.
+
+
+=head1 VERSIONS AND CONVENTIONS USED IN THIS TUTORIAL
+
+This tutorial was built using the following resources. Please note that
+you may need to make adjustments for different environments and
+versions:
+
+=over 4
+
+=item *
+
+Debian 5 (Lenny)
+
+=item *
+
+Catalyst v5.80004
+
+=item *
+
+Catalyst::Devel v1.10
+
+=item *
+
+DBIx::Class v0.08102
+
+=item *
+
+Catalyst Plugins
+
+The plugins used in this tutorial all have sufficiently stable APIs that
+you shouldn't need to worry about versions. However, there could be
+cases where the tutorial is affected by what version of plugins you
+use. This tutorial has been tested against the following set of plugins:
+
+=over 4
+
+=item *
+
+Catalyst::Plugin::Authentication -- v0.10011
+
+=item *
+
+Catalyst::Plugin::Authorization::Roles -- v0.07
+
+=item *
+
+Catalyst::Plugin::ConfigLoader -- v0.22
+
+=item *
+
+Catalyst::Plugin::Session -- v0.20
+
+=item *
+
+Catalyst::Plugin::Session::State::Cookie -- v0.10
+
+=item *
+
+Catalyst::Plugin::Session::Store::FastMmap -- v0.07
+
+=item *
+
+Catalyst::Plugin::StackTrace -- v0.09
+
+=item *
+
+Catalyst::Plugin::Static::Simple -- v0.21
+
+=back
+
+=item *
+
+B<NOTE:> You can check the versions you have installed with the
+following command:
+
+ perl -M<_mod_name_> -e '"print $<_mod_name_>::VERSION\n"'
+
+For example:
+ perl -MCatalyst::Plugin::StackTrace -e 'print "$Catalyst::Plugin::StackTrace::VERSION\n"'
+
+Since the web browser is being used on the same box where Perl and the
+Catalyst development server is running, the URL of
+C<http://localhost:3000> will be used (the Catalyst development server
+defaults to port 3000). If you are running Perl on a different box than
+where your web browser is located (or using a different port number via
+the C<-p> I<port_number> option to the development server), then you
+will need to update the URL you use accordingly.
+
+=item *
+
+Depending on the web browser you are using, you might need to hit
+C<Shift+Reload> or C<Ctrl+Reload> to pull a fresh page when testing
+your application at various points (see
+L<http://en.wikipedia.org/wiki/Bypass_your_cache> for a comprehensive
+list of options for each browser). Also, the C<-k> keepalive option
+to the development server can be necessary with some browsers
+(especially Internet Explorer).
+
+=back
+
+
+=head1 CATALYST INSTALLATION
+
+Although Catalyst installation has been a challenge in the past, the
+good news is that there are a growing number of options to eliminate
+(or at least dramatically simplify) this concern. Although a
+compelling strength of Catalyst is that it makes use of many of the
+modules in the vast repository that is CPAN, this can complicate the
+installation process if you approach it in the wrong way. Consider
+the following suggestions on the most common ways to get started with
+a Catalyst development environment:
+
+=over 4
+
+=item *
+
+Debian
+
+The Debian 5 live CD represents a great way for newcomers to
+experiment with Catalyst. As a "live CD," you can simple boot from
+the CD, run a few commands, and in a matter of minutes you should have
+a fully function environment in which do this tutorial. B<The tutorial
+was fully tested to work under Debian 5. Although it SHOULD work
+under any Catalyst installation method you might choose, it can be
+hard to guarantee this.>
+
+=over 4
+
+=item *
+
+Download one of the ISO files from
+L<http://cdimage.debian.org/cdimage/release/current-live/i386/iso-cd/>.
+You can pick any one of the live CD variations will work, but
+you may wish to consider the following points:
+
+=over 4
+
+=item *
+
+"C<debian-live-500-i386-rescue.iso>" is probably the best all-around
+option for most people because it includes many extra tools such as
+the GCC compiler, therefore saving RAM (every package you need to
+install when running from live CD consumes memory because RAM disk is
+being used in lieu of real disk space). When initially booting under
+this image, you may see some cryptic warning messages having to do
+with various diagnostic tools it tries to load or enable, but you
+should be able to safely ignore these.
+
+=item *
+
+"C<debian-live-500-i386-standard.iso>" is a great option because of
+its compact size, but you will probably need approximately 1 GB of RAM
+in the computer where you will run the tutorial. Because the
+"standard" live CD comes with with a minimal set of tools, we will
+have to install extra packages (such as the GCC compiler), all of
+which will require RAM when running from a live CD.
+
+=item *
+
+The other ISO images include different flavors of X-Windows desktop
+managers. You can select one of these if you don't mind the larger
+download size and prefer a graphical environment. Be aware that these
+disks do not come with the extra tools found on the "rescue" image, so
+you will need adequate RAM to be able to install them just as you
+would under the "standard" image. B<Use one of the "graphical" ISO
+images if you want a graphical web browser on the same machine as
+where you will run the tutorial.> (If you are using one of the non-
+graphical images discussed above, you can still use a graphical web
+browser from another machine and point it to your Catalyst development
+machine.)
+
+=back
+
+=item *
+
+Boot off the CD.
+
+=item *
+
+Select "C<Live>" from the initial boot menu.
+
+=item *
+
+Once the system has booted to a "C<user at debian:~$>" prompt, enter the
+following command to add the more current "unstable" package
+repository:
+
+ sudo vi /etc/apt/sources.list
+
+Add the following line to the bottom of this file:
+
+ deb http://ftp.us.debian.org/debian/ unstable main
+
+If you are not familiar with VI, you can move to the bottom of this
+file and press the "o" key to insert a new line and type the line
+above. Then press the "Esc" key followed by a colon (":"), the
+letters "wq" and then the "Enter" key. The rest of the tutorial will
+assume that you know how to use some editor that is available from the
+Linux command-line environment.
+
+=item *
+
+Install Catalyst:
+
+ sudo aptitude update
+ sudo aptitude -y install sqlite3 libdbd-sqlite3-perl libcatalyst-perl \
+ libcatalyst-modules-perl libconfig-general-perl libsql-translator-perl \
+ libdatetime-perl libdatetime-format-mysql-perl libio-all-perl \
+ libperl6-junction-perl libmoosex-emulate-class-accessor-fast-perl
+
+Let it install (normally about a 30-second operaton) and you are
+done.
+
+If you are using an image other than the "rescue" ISO, you will also need
+to run the following command to install additional packages:
+
+ sudo aptitude -y install gcc make libc6-dev
+
+If you are running from the Live CD, you probably also want to free up
+some RAM disk space with the following:
+
+ sudo aptitude clean
+
+NOTE: While the instructions above mention the Live CD because that
+makes it easy for people new to Linux, you can obviously pick a
+different Debian ISO image and install it to your hard drive.
+Although there are many different ways to download and install Debian,
+the "netinst" ISO image (such as "C<debian-500-i386-netinst.iso>"
+represents a great option because it keeps your initial download small
+(but still let's you install anything you want "over the network").
+
+Here are some tips if you are running from a live CD and are running
+out of disk space (which really means you are running out of RAM):
+
+=over 4
+
+=item *
+
+Always run "C<aptitude clean>" after you install new packages to
+delete the original .deb files (the files installed B<by> the .deb
+package B<will> remain available, just the .deb package itself is
+deleted).
+
+=item *
+
+If you are installing modules from CPAN, you can free up some space
+with "C<rm -rf /root/.cpan/*>".
+
+=item *
+
+If necessary, you can remove the cached package information with the
+command "C<rm -f /var/lib/apt/lists/*>". You can later pull this
+information again via the command "C<aptitude update>".
+
+=item *
+
+You can save a small amount of space by commenting out the lines in
+C</etc/apt/sources.list> that reference "deb-src" and
+"security.debian.org". If you have already done an "C<aptitude
+update>" with these repositories enabled, you can use the tip in the
+previous bullet to free the space up (and then do another "C<aptitude
+update>").
+
+=item *
+
+Although you can free up space by removing packages you installed
+since you last booted (check out "C<aptitude remove _pkg_name>"),
+don't bother trying to remove packages already available at the time
+of boot. Instead of freeing up space, it will actual I<consume> some
+space. (The live CD uses these "burn in" packages right from the CD
+disk vs. first loading them on the virtual RAM disk. However, if you
+remove them, the system has to update various files, something that
+I<does> consume some space on the virtual RAM disk.)
+
+=back
+
+=back
+
+=item *
+
+Ubuntu
+
+Ubuntu is an extremely popular offshoot of Debian. It provides
+cutting edge versions of many common tools, application and libraries
+in an easy-to-run live CD configuration (and because a single download
+option can be used for both live CD and install-to-disk usage, it
+keeps your download options nice and simple). As with Debian 5, you
+should be able to generate a fully function Catalyst environment in a
+matter of minutes. Here are quick instructions on how to use Ubuntu
+to prepare for the tutorial:
+
+=over 4
+
+=item *
+
+Download the Ubuntu Desktop edition and boot from the CD and/or image
+file, select your language, and then "Try Ubuntu without any changes
+to your computer."
+
+=item *
+
+Open a terminal session (click "Applications" in the upper-left
+corner, then "Accessories," then "Terminal").
+
+=item *
+
+Add the 'universe' repositories:
+
+ sudo gedit /etc/apt/sources.list
+
+And remove the comments from the lines under the comments about the
+'universe' repositories.
+
+=item *
+
+Install Catalyst:
+
+ sudo aptitude update
+ sudo aptitude install libdbd-sqlite3-perl libcatalyst-perl libcatalyst-modules-perl libconfig-general-perl
+
+Accept all of the dependencies. Done.
+
+If you are running from the Live CD, you probably also want to free up
+some disk space with the following:
+
+ sudo aptitude clean
+
+NOTE: While the instructions above mention the live CD because that
+makes it easy for people new to Linux, you can obviously also use one
+of the options to install Ubuntu on your drive.
+
+=back
+
+=item *
+
+Matt Trout's C<cat-install>
+
+Available at L<http://www.shadowcatsystems.co.uk/static/cat-install>,
+C<cat-install> can be a fairly painless way to get Catalyst up and
+running. Just download the script from the link above and type C<perl
+cat-install>. Depending on the speed of your Internet connection and
+your computer, it will probably take 30 to 60 minutes to install because
+it downloads, makes, compiles, and tests every module. But this is an
+excellent way to automate the installation of all the latest modules
+used by Catalyst from CPAN.
+
+
+=item *
+
+Other Possibilities
+
+=over 4
+
+=item *
+
+OpenBSD Packages
+
+The 2008 Advent Day 4 entry has more information on using OpenBSD
+packages to quickly build a system:
+L<http://www.catalystframework.org/calendar/2008/4>.
+
+=item *
+
+NetBSD Package Collection on Solaris
+
+The 2008 Advent Day 15 entry has more information on using C<pkgsrc> and
+NetBSD packages on Solaris:
+L<http://www.catalystframework.org/calendar/2008/15>.
+
+=item *
+
+CatInABox
+
+You can get more information at
+L<http://www.catalystframework.org/calendar/2008/7>
+or L<Perl::Dist::CatInABox|Perl::Dist::CatInABox>.
+
+=item *
+
+Frank Speiser's Amazon EC2 Catalyst SDK
+
+There are currently two flavors of publicly available Amazon Machine
+Images (AMI) that include all the elements you'd need to begin
+developing in a fully functional Catalyst environment within minutes.
+See L<Catalyst::Manual::Installation|Catalyst::Manual::Installation>
+for more details.
+
+=back
+
+=back
+
+For additional information and recommendations on Catalyst installation,
+please refer to
+L<Catalyst::Manual::Installation|Catalyst::Manual::Installation>.
+
+
+=head1 DATABASES
+
+This tutorial will primarily focus on SQLite because of its simplicity
+of installation and use; however, modifications in the script required
+to support MySQL and PostgreSQL will be presented in Appendix.
+
+B<Note:> One of the advantages of the MVC design patterns is that
+applications become much more database independent. As such, you will
+notice that only the C<.sql> files used to initialize the database
+change between database systems: the Catalyst code generally remains the
+same.
+
+
+=head1 WHERE TO GET WORKING CODE
+
+Each chapter of the tutorial has complete code available as a tarball in
+the main Catalyst Subversion repository (see the note at the beginning
+of each part for the appropriate svn command to use).
+
+B<NOTE:> You can run the test cases for the final code through Chapter 8
+with the following commands:
+
+ sudo cpan Catalyst::Model::DBIC::Schema Time::Warp DBICx::TestDatabase \
+ DBIx::Class::DynamicDefault DBIx::Class::TimeStamp DBIx::Class::EncodedColumn
+ wget http://dev.catalyst.perl.org/repos/Catalyst/trunk/examples/Tutorial/MyApp_Chapter8.tgz
+ tar zxvf MyApp_Chapter8.tgz
+ cd MyApp
+ CATALYST_DEBUG=0 prove --lib lib t
+
+If you wish to include the L<HTML::FormFu|HTML::FormFu> section in
+your tests, substitute C<MyApp_Chapter9_FormFu.tgz> for
+C<MyApp_Chapter8.tgz> in the URL above. However, you will also need to
+run the following additional commands:
+
+ sudo aptitude -y install libhtml-formfu-perl libmoose-perl \
+ libregexp-assemble-perl libhtml-formfu-model-dbic-perl
+ sudo aptitude clean
+ sudo cpan Catalyst::Component::InstancePerContext Catalyst::Controller::HTML::FormFu
+
+You can also fire up the application under the development server that is conveniently
+built in to Catalyst. Just issue this command from the C<MyApp> directory where you
+ran the test suite above:
+
+ script/myapp_server.pl
+
+And the application will start. You can try out the application by
+pulling up C<http://localhost:3000> in your web browser (as mentioned
+earlier, change C<localhost> to a different IP address or DNS name if
+you are running your web browser and your Catalyst development on
+different boxes). We will obviously see more about how to use the
+application as we go through the remaining chapters of the tutorial, but
+for now you can log in using the username "test01" and a password of
+"mypass".
+
+
+=head1 AUTHOR
+
+Kennedy Clark, C<hkclark at gmail.com>
+
+Please report any errors, issues or suggestions to the author. The
+most recent version of the Catalyst Tutorial can be found at
+L<http://dev.catalyst.perl.org/repos/Catalyst/Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/>.
+
+Copyright 2006-2008, Kennedy Clark, under Creative Commons License
+(L<http://creativecommons.org/licenses/by-sa/3.0/us/>).
Copied: Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/02_CatalystBasics.pod (from rev 10275, Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/CatalystBasics.pod)
===================================================================
--- Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/02_CatalystBasics.pod (rev 0)
+++ Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/02_CatalystBasics.pod 2009-05-24 22:30:47 UTC (rev 10277)
@@ -0,0 +1,462 @@
+=head1 NAME
+
+Catalyst::Manual::Tutorial::02_CatalystBasics - Catalyst Tutorial - Chapter 2: Catalyst Application Development Basics
+
+
+=head1 OVERVIEW
+
+This is B<Chapter 2 of 10> for the Catalyst tutorial.
+
+L<Tutorial Overview|Catalyst::Manual::Tutorial>
+
+=over 4
+
+=item 1
+
+L<Introduction|Catalyst::Manual::Tutorial::01_Intro>
+
+=item 2
+
+B<02_Catalyst Basics>
+
+=item 3
+
+L<More Catalyst Basics|Catalyst::Manual::Tutorial::03_MoreCatalystBasics>
+
+=item 4
+
+L<Basic CRUD|Catalyst::Manual::Tutorial::04_BasicCRUD>
+
+=item 5
+
+L<Authentication|Catalyst::Manual::Tutorial::05_Authentication>
+
+=item 6
+
+L<Authorization|Catalyst::Manual::Tutorial::06_Authorization>
+
+=item 7
+
+L<Debugging|Catalyst::Manual::Tutorial::07_Debugging>
+
+=item 8
+
+L<Testing|Catalyst::Manual::Tutorial::08_Testing>
+
+=item 9
+
+L<Advanced CRUD|Catalyst::Manual::Tutorial::09_AdvancedCRUD>
+
+=item 10
+
+L<Appendices|Catalyst::Manual::Tutorial::10_Appendices>
+
+=back
+
+
+=head1 DESCRIPTION
+
+In this chapter of the tutorial, we will create a very basic Catalyst
+web application, demonstrating a number of powerful capabilities, such
+as:
+
+=over 4
+
+=item * Helper Scripts
+
+Catalyst helper scripts that can be used to rapidly bootstrap the
+skeletal structure of an application.
+
+=item * MVC
+
+Model/View/Controller (MVC) provides an architecture that facilitates a
+clean "separation of control" between the different portions of your
+application. Given that many other documents cover this subject in
+detail, MVC will not be discussed in depth here (for an excellent
+introduction to MVC and general Catalyst concepts, please see
+L<Catalyst::Manual::About|Catalyst::Manual::About>). In short:
+
+=over 4
+
+=item * Model
+
+The model usually represents a data store. In most applications, the
+model equates to the objects that are created from and saved to your SQL
+database.
+
+=item * View
+
+The view takes model objects and renders them into something for the end
+user to look at. Normally this involves a template-generation tool that
+creates HTML for the user's web browser, but it could easily be code
+that generates other forms such as PDF documents, e-mails, spreadsheets,
+or even "behind the scenes" formats such as XML and JSON.
+
+=item * Controller
+
+As suggested by its name, the controller takes user requests and routes
+them to the necessary model and view.
+
+=back
+
+=item * ORM
+
+The use of Object-Relational Mapping (ORM) technology for database
+access. Specifically, ORM provides an automated and standardized means
+to persist and restore objects to/from a relational database.
+
+=back
+
+You can checkout the source code for this example from the catalyst
+subversion repository as per the instructions in
+L<Catalyst::Manual::Tutorial::01_Intro|Catalyst::Manual::Tutorial::01_Intro>.
+
+
+=head1 CREATE A CATALYST PROJECT
+
+Catalyst provides a number of helper scripts that can be used to
+quickly flesh out the basic structure of your application. All
+Catalyst projects begin with the C<catalyst.pl> helper (see
+L<Catalyst::Helper|Catalyst::Helper> for more information on helpers).
+Also note that as of Catalyst 5.7000, you will not have the helper
+scripts unless you install both L<Catalyst::Runtime|Catalyst::Runtime>
+and L<Catalyst::Devel|Catalyst::Devel>.
+
+In this first chapter of the tutorial, use the Catalyst C<catalyst.pl>
+script to initialize the framework for an application called C<Hello>:
+
+ $ catalyst.pl Hello
+ created "Hello"
+ created "Hello/script"
+ created "Hello/lib"
+ created "Hello/root"
+ ...
+ created "Hello/script/hello_create.pl"
+ $ cd Hello
+
+The C<catalyst.pl> helper script will display the names of the
+directories and files it creates:
+
+ Changes # Record of application changes
+ lib # Lib directory for your app's Perl modules
+ Hello # Application main code directory
+ Controller # Directory for Controller modules
+ Model # Directory for Models
+ View # Directory for Views
+ Hello.pm # Base application module
+ Makefile.PL # Makefile to build application
+ hello.conf # Application configuration file
+ README # README file
+ root # Equiv of htdocs, dir for templates, css, javascript
+ favicon.ico
+ static # Directory for static files
+ images # Directory for image files used in welcome screen
+ script # Directory for Perl scripts
+ hello_cgi.pl # To run your app as a cgi (not recommended)
+ hello_create.pl # To create models, views, controllers
+ hello_fastcgi.pl # To run app as a fastcgi program
+ hello_server.pl # The normal development server
+ hello_test.pl # Test your app from the command line
+ t # Directory for tests
+ 01app.t # Test scaffold
+ 02pod.t
+ 03podcoverage.t
+
+
+Catalyst will "auto-discover" modules in the Controller, Model, and
+View directories. When you use the hello_create.pl script it will
+create Perl module scaffolds in those directories, plus test files in
+the "t" directory. The default location for templates is in the "root"
+directory. The scripts in the script directory will always start with
+the lowercased version of your application name. If your app is
+MaiTai, then the create script would be "maitai_create.pl".
+
+Though it's too early for any significant celebration, we already have
+a functioning application. We can use the Catalyst supplied script to
+start up a development server and view the default Catalyst page in
+your browser. All scripts in the script directory should be run from
+the base directory of your application, so change to the Hello
+directory.
+
+Run the following command to start up the built-in development web
+server (make sure you didn't forget the "C<cd Hello>" from the
+previous step):
+
+ $ script/hello_server.pl
+ [debug] Debug messages enabled
+ [debug] Statistics enabled
+ [debug] Loaded plugins:
+ .----------------------------------------------------------------------------.
+ | Catalyst::Plugin::ConfigLoader 0.20 |
+ | Catalyst::Plugin::Static::Simple 0.20 |
+ '----------------------------------------------------------------------------'
+
+ [debug] Loaded dispatcher "Catalyst::Dispatcher"
+ [debug] Loaded engine "Catalyst::Engine::HTTP"
+ [debug] Found home "/home/me/Hello"
+ [debug] Loaded Config "/home/me/Hello/hello.conf"
+ [debug] Loaded components:
+ .-----------------------------------------------------------------+----------.
+ | Class | Type |
+ +-----------------------------------------------------------------+----------+
+ | Hello::Controller::Root | instance |
+ '-----------------------------------------------------------------+----------'
+
+ [debug] Loaded Private actions:
+ .----------------------+--------------------------------------+--------------.
+ | Private | Class | Method |
+ +----------------------+--------------------------------------+--------------+
+ | /default | Hello::Controller::Root | default |
+ | /end | Hello::Controller::Root | end |
+ | /index | Hello::Controller::Root | index |
+ '----------------------+--------------------------------------+--------------'
+
+ [debug] Loaded Path actions:
+ .-------------------------------------+--------------------------------------.
+ | Path | Private |
+ +-------------------------------------+--------------------------------------+
+ | / | /default |
+ | / | /index |
+ '-------------------------------------+--------------------------------------'
+
+ [info] Hello powered by Catalyst 5.80003
+ You can connect to your server at http://debian:3000
+
+Point your web browser to L<http://localhost:3000> (substituting a
+different hostname or IP address as appropriate) and you should be
+greeted by the Catalyst welcome screen (if you get some other welcome
+screen or an "Index" screen, you probably forgot to specify port 3000
+in your URL). Information similar to the following should be appended
+to the logging output of the development server:
+
+ [info] *** Request 1 (0.005/s) [20712] [Sun Mar 8 15:49:09 2009] ***
+ [debug] "GET" request for "/" from "1.1.1.98"
+ [info] Request took 0.007342s (136.203/s)
+ .----------------------------------------------------------------+-----------.
+ | Action | Time |
+ +----------------------------------------------------------------+-----------+
+ | /index | 0.000491s |
+ | /end | 0.000595s |
+ '----------------------------------------------------------------+-----------'
+
+Press Ctrl-C to break out of the development server.
+
+
+=head1 HELLO WORLD
+
+=head2 The Simplest Way
+
+The Root.pm controller is a place to put global actions that usually
+execute on the root URL. Open the C<lib/Hello/Controller/Root.pm> file in
+your editor. You will see the "index" subroutine, which is
+responsible for displaying the welcome screen that you just saw in
+your browser. Later on you'll want to change that to something more
+reasonable, such as a "404" message or a redirect, but for now just
+leave it alone.
+
+ sub index :Path :Args(0) {
+ my ( $self, $c ) = @_;
+
+ # Hello World
+ $c->response->body( $c->welcome_message );
+ }
+
+The "C<$c>" here refers to the Catalyst context, which is used to
+access the Catalyst application. In addition to many other things,
+the Catalyst context provides access to "response" and "request"
+objects. (See L<Catalyst|Catalyst>,
+L<Catalyst::Response|Catalyst::Response>, and
+L<Catalyst::Request|Catalyst::Request>)
+
+C<$c-E<gt>response-E<gt>body> sets the HTTP response (see
+L<Catalyst::Response|Catalyst::Response>), while C<$c-E<gt>welcome_message>
+is a special method that returns the welcome message that you saw in
+your browser.
+
+The ":Path :Args(0)" after the method name are attributes which determine
+which URLs will be dispatched to this method. (Depending on your version of
+Catalyst, it used to say "Private" but using that with 'default' or 'index'
+is currently deprecated.)
+
+Some MVC frameworks handle dispatching in a central place. Catalyst,
+by policy, prefers to handle URL dispatching with attributes on
+controller methods. There is a lot of flexibility in specifying which
+URLs to match. This particular method will match all URLs, because it
+doesn't specify the path (nothing comes after "Path"), but will only
+accept a single args because of the ":Args(0)".
+
+The default is to map URLs to controller names, and because of
+the way that Perl handles namespaces through package names,
+it is simple to create hierarchical structures in
+Catalyst. This means that you can create controllers with deeply
+nested actions in a clean and logical way.
+
+For example, the URL C<http://hello.com/admin/articles/create> maps
+to the package C<Hello::Controller::Admin::Articles>, and the C<create>
+method.
+
+Add the following subroutine to your C<lib/Hello/Controller/Root.pm>
+file:
+
+ sub hello : Global {
+ my ( $self, $c ) = @_;
+
+ $c->response->body("Hello, World!");
+ }
+
+B<TIP>: See Appendix 1 for tips on removing the leading spaces when
+cutting and pasting example code from POD-based documents.
+
+Here you're sending your own string to the webpage.
+
+Save the file, start the server (stop and restart it if it's still
+up), and go to L<http://localhost:3000/hello> to
+see "Hello, World!"
+
+
+=head2 Hello, World! Using a View and a Template
+
+In the Catalyst world a "View" is not a page of XHTML or a template
+designed to present a page to a browser. It is the module that
+determines the I<type> of view -- HTML, pdf, XML, etc. For the
+thing that generates the I<content> of that view, (such as the
+default Toolkit Template) the actual templates go under the
+"root" directory.
+
+To create a TT view, run:
+
+ $ script/hello_create.pl view TT TT
+
+This creates the C<lib/Hello/View/TT.pm> module, which is a subclass of
+C<Catalyst::View::TT>.
+
+=over 4
+
+=item *
+
+The "view" keyword tells the create script that you are creating a view.
+
+=item *
+
+The first "TT" tells the script to name the View module "TT.pm", which is a
+commonly used name for TT views. (You can name it anything you want, such as
+"HTML.pm".)
+
+=item *
+
+The final "TT" tells it that you are creating a Template Toolkit view.
+
+=back
+
+If you look at C<lib/Hello/View/TT.pm> you will find that it only contains a
+config statement to set the TT extension to ".tt".
+
+Now that the TT.pm "View" exists, Catalyst will autodiscover it and be
+able to use it to display the view templates, using the "process"
+method that it inherits from the C<Catalyst::View::TT class>.
+
+Template Toolkit is a very full featured template facility, with
+excellent documentation at L<http://template-toolkit.org/>,
+but since this is not a TT tutorial, we'll stick to only basic TT
+usage here (and explore some of the more common TT features in later
+chapters of the tutorial).
+
+Create a C<root/hello.tt> template file (put it in the C<root> under
+the C<Hello> directory that is the base of your application). Here is
+a simple sample:
+
+ <p>
+ This is a TT view template, called '[% template.name %]'.
+ </p>
+
+[% and %] are markers for the TT parts of the template. Inside you can
+access Perl variables and classes, and use TT directives. In this
+case, we're using a special TT variable that defines the name of the
+template file (C<hello.tt>). The rest of the template is normal HTML.
+
+Change the hello method in C<lib/Hello/Controller/Root.pm> to the
+following:
+
+ sub hello : Global {
+ my ( $self, $c ) = @_;
+
+ $c->stash->{template} = 'hello.tt';
+ }
+
+This time, instead of doing C<$c-E<gt>response-E<gt>body()>, you are setting
+the value of the "template" hash key in the Catalyst "stash", an area
+for putting information to share with other parts of your application.
+The "template" key determines which template will be displayed at the
+end of the method. Catalyst controllers have a default "end" action
+for all methods which causes the first (or default) view to be
+rendered (unless there's a C<$c-E<gt>response-E<gt>body()> statement). So your
+template will be magically displayed at the end of your method.
+
+After saving the file, restart the development server, and look at
+L<http://localhost:3000/hello> again. You should
+see the template that you just made.
+
+
+=head1 CREATE A SIMPLE CONTROLLER AND AN ACTION
+
+Create a controller named "Site" by executing the create script:
+
+ $ script/hello_create.pl controller Site
+
+This will create a C<lib/Hello/Controller/Site.pm> file (and a test
+file). Bring Site.pm up in your editor, and you can see that there's
+not much there. Most people probably don't bother to use the create
+script to make controllers after they're used to using Catalyst.
+
+In C<lib/Hello/Controller/Site.pm>, add the following method:
+
+ sub test : Local {
+ my ( $self, $c ) = @_;
+
+ $c->stash->{username} = "John";
+ $c->stash->{template} = 'site/test.tt';
+ }
+
+Notice the "Local" attribute on the C<test> method. This will cause
+the C<test> action (now that we have assigned an action type to the
+method it appears as a controller "action" to Catalyst) to be executed
+on the "controller/method" URL, or, in this case, "site/test". We
+will see additional information on controller actions throughout the
+rest of the tutorial, but if you are curious take a look at
+L<Catalyst::Manual::Intro/Actions>.
+
+It's not actually necessary to set the template value as we do here.
+By default TT will attempt to render a template that follows the
+naming pattern "controller/method.tt", and we're following that
+pattern here. However, in other situations you will need to specify
+the template (such as if you've "forwarded" to the method, or if it
+doesn't follow the default naming convention).
+
+We've also put the variable "username" into the stash, for use in the
+template.
+
+Make a subdirectory "site" in the "root" directory. Copy the hello.tt
+file into the directory as C<root/site/test.tt>, or create a new
+template file at that location. Include a line like:
+
+ <p>Hello, [% username %]!</p>
+
+Bring up or restart the server. Notice in the server output that
+C</site/test> is listed in the Loaded Path actions. Go to
+L<http://localhost:3000/site/test> in your browser.
+
+You should see your test.tt file displayed, including the name "John"
+that you set in the controller.
+
+
+=head1 AUTHORS
+
+Gerda Shank, C<gerda.shank at gmail.com>
+Kennedy Clark, C<hkclark at gmail.com>
+
+Please report any errors, issues or suggestions to the author. The
+most recent version of the Catalyst Tutorial can be found at
+L<http://dev.catalyst.perl.org/repos/Catalyst/Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/>.
+
+Copyright 2006-2008, Kennedy Clark & Gerda Shank, under Creative Commons License
+(L<http://creativecommons.org/licenses/by-sa/3.0/us/>).
Copied: Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/03_MoreCatalystBasics.pod (from rev 10275, Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/MoreCatalystBasics.pod)
===================================================================
--- Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/03_MoreCatalystBasics.pod (rev 0)
+++ Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/03_MoreCatalystBasics.pod 2009-05-24 22:30:47 UTC (rev 10277)
@@ -0,0 +1,1498 @@
+=head1 NAME
+
+Catalyst::Manual::Tutorial::03_MoreCatalystBasics - Catalyst Tutorial - Chapter 3: More Catalyst Application Development Basics
+
+
+=head1 OVERVIEW
+
+This is B<Chapter 3 of 10> for the Catalyst tutorial.
+
+L<Tutorial Overview|Catalyst::Manual::Tutorial>
+
+=over 4
+
+=item 1
+
+L<Introduction|Catalyst::Manual::Tutorial::01_Intro>
+
+=item 2
+
+L<Catalyst Basics|Catalyst::Manual::Tutorial::02_CatalystBasics>
+
+=item 3
+
+B<03_More Catalyst Basics>
+
+=item 4
+
+L<Basic CRUD|Catalyst::Manual::Tutorial::04_BasicCRUD>
+
+=item 5
+
+L<Authentication|Catalyst::Manual::Tutorial::05_Authentication>
+
+=item 6
+
+L<Authorization|Catalyst::Manual::Tutorial::06_Authorization>
+
+=item 7
+
+L<Debugging|Catalyst::Manual::Tutorial::07_Debugging>
+
+=item 8
+
+L<Testing|Catalyst::Manual::Tutorial::08_Testing>
+
+=item 9
+
+L<Advanced CRUD|Catalyst::Manual::Tutorial::09_AdvancedCRUD>
+
+=item 10
+
+L<Appendices|Catalyst::Manual::Tutorial::10_Appendices>
+
+=back
+
+
+=head1 DESCRIPTION
+
+This chapter of the tutorial builds on the work done in Chapter 2 to
+explore some features that are more typical of "real world" web
+applications. From this chapter of the tutorial onward, we will be
+building a simple book database application. Although the application
+will be too limited to be of use to anyone, it should provide a basic
+environment where we can explore a variety of features used in
+virtually all web applications.
+
+You can check out the source code for this example from the Catalyst
+Subversion repository as per the instructions in
+L<Catalyst::Manual::Tutorial::01_Intro|Catalyst::Manual::Tutorial::01_Intro>.
+
+Please take a look at
+L<Catalyst::Manual::Tutorial::01_Intro/CATALYST INSTALLATION> before
+doing the rest of this tutorial. Although the tutorial should work
+correctly under most any recent version of Perl running on any
+operating system, the tutorial has been written using Debian 5 and
+tested to be sure it runs correctly in this environment.
+
+
+=head1 CREATE A NEW APPLICATION
+
+The remainder of the tutorial will build an application called C<MyApp>.
+First use the Catalyst C<catalyst.pl> script to initialize the framework
+for the C<MyApp> application (make sure you aren't still inside the
+directory of the C<Hello> application from the previous chapter of the
+tutorial or in a directory that already has a "MyApp" subdirectory):
+
+ $ catalyst.pl MyApp
+ created "MyApp"
+ created "MyApp/script"
+ created "MyApp/lib"
+ created "MyApp/root"
+ ...
+ created "MyApp/script/myapp_create.pl"
+ $ cd MyApp
+
+This creates a similar skeletal structure to what we saw in Chapter 2 of
+the tutorial, except with C<MyApp> and C<myapp> substituted for
+C<Hello> and C<hello>.
+
+
+=head1 EDIT THE LIST OF CATALYST PLUGINS
+
+One of the greatest benefits of Catalyst is that it has such a large
+library of plugins and base classes available. Plugins are used to
+seamlessly integrate existing Perl modules into the overall Catalyst
+framework. In general, they do this by adding additional methods to the
+C<context> object (generally written as C<$c>) that Catalyst passes to
+every component throughout the framework.
+
+By default, Catalyst enables three plugins/flags:
+
+=over 4
+
+=item *
+
+C<-Debug> Flag
+
+Enables the Catalyst debug output you saw when we started the
+C<script/myapp_server.pl> development server earlier. You can remove
+this item when you place your application into production.
+
+As you may have noticed, C<-Debug> is not a plugin, but a I<flag>.
+Although most of the items specified on the C<__PACKAGE__-E<gt>setup>
+line of your application class will be plugins, Catalyst supports a
+limited number of flag options (of these, C<-Debug> is the most
+common). See the documentation for C<Catalyst.pm> to get details on
+other flags (currently C<-Engine>, C<-Home>, and C<-Log>).
+
+If you prefer, you can use the C<$c-E<gt>debug> method to enable debug
+messages.
+
+B<TIP>: Depending on your needs, it can be helpful to permanently
+remove C<-Debug> from C<lib/MyApp.pm> and then use the C<-d> option
+to C<script/myapp_server.pl> to re-enable it just for the development
+server. We will not be using that approach in the tutorial, but feel
+free to make use of it in your own projects.
+
+=item *
+
+L<Catalyst::Plugin::ConfigLoader|Catalyst::Plugin::ConfigLoader>
+
+C<ConfigLoader> provides an automatic way to load configurable
+parameters for your application from a central
+L<Config::General|Config::General> file (versus having the values
+hard-coded inside your Perl modules). Config::General uses syntax
+very similar to Apache configuration files. We will see how to use
+this feature of Catalyst during the authentication and authorization
+sections (Chapter 5 and Chapter 6).
+
+B<IMPORTANT NOTE:> If you are using a version of
+L<Catalyst::Devel|Catalyst::Devel> prior to version 1.06, be aware
+that Catalyst changed the default format from YAML to the more
+straightforward C<Config::General> style. This tutorial uses the
+newer C<myapp.conf> file for C<Config::General>. However, Catalyst
+supports both formats and will automatically use either C<myapp.conf>
+or C<myapp.yml> (or any other format supported by
+L<Catalyst::Plugin::ConfigLoader|Catalyst::Plugin::ConfigLoader> and
+L<Config::Any|Config::Any>). If you are using a version of
+Catalyst::Devel prior to 1.06, you can convert to the newer format by
+simply creating the C<myapp.conf> file manually and deleting
+C<myapp.yml>. The default contents of the C<myapp.conf> you create
+should only consist of one line:
+
+ name MyApp
+
+B<TIP>: This script can be useful for converting between configuration
+formats:
+
+ perl -Ilib -e 'use MyApp; use Config::General;
+ Config::General->new->save_file("myapp.conf", MyApp->config);'
+
+=item *
+
+L<Catalyst::Plugin::Static::Simple|Catalyst::Plugin::Static::Simple>
+
+C<Static::Simple> provides an easy way to serve static content, such
+as images and CSS files, from the development server.
+
+=back
+
+For our application, we want to add one new plugin into the mix. To
+do this, edit C<lib/MyApp.pm> (this file is generally referred to as
+your I<application class>) and delete the lines with:
+
+ use Catalyst qw/-Debug
+ ConfigLoader
+ Static::Simple/;
+
+Then replace it with:
+
+ # Load plugins
+ use Catalyst qw/-Debug
+ ConfigLoader
+ Static::Simple
+
+ StackTrace
+ /;
+
+B<Note:> Recent versions of C<Catalyst::Devel> have used a variety of
+techniques to load these plugins/flags. For example, you might see
+the following:
+
+ __PACKAGE__->setup(qw/-Debug ConfigLoader Static::Simple/);
+
+Don't let these variations confuse you -- they all accomplish the same
+result.
+
+This tells Catalyst to start using one new plugin,
+L<Catalyst::Plugin::StackTrace|Catalyst::Plugin::StackTrace>, to add a
+stack trace to the standard Catalyst "debug screen" (the screen
+Catalyst sends to your browser when an error occurs). Be aware that
+L<StackTrace|Catalyst::Plugin::StackTrace> output appears in your
+browser, not in the console window from which you're running your
+application, which is where logging output usually goes.
+
+Make sure that when adding new plugins that you include them as a new
+dependancies within the Makefile.PL file. For example, after adding
+the StackTrace plugin the Makefile.PL should include the following
+line:
+
+ requires 'Catalyst::Plugin::StackTrace';
+
+
+B<Notes:>
+
+=over 4
+
+=item *
+
+C<__PACKAGE__> is just a shorthand way of referencing the name of the
+package where it is used. Therefore, in C<MyApp.pm>, C<__PACKAGE__>
+is equivalent to C<MyApp>.
+
+=item *
+
+You will want to disable L<StackTrace|Catalyst::Plugin::StackTrace>
+before you put your application into production, but it can be helpful
+during development.
+
+=item *
+
+When specifying plugins on the C<__PACKAGE__-E<gt>setup> line, you can
+omit C<Catalyst::Plugin::> from the name. Additionally, you can
+spread the plugin names across multiple lines as shown here, or place
+them all on one (or more) lines as with the default configuration.
+
+=back
+
+
+=head1 CREATE A CATALYST CONTROLLER
+
+As discussed earlier, controllers are where you write methods that
+interact with user input. Typically, controller methods respond to
+C<GET> and C<POST> requests from the user's web browser.
+
+Use the Catalyst C<create> script to add a controller for book-related
+actions:
+
+ $ script/myapp_create.pl controller Books
+ exists "/home/me/MyApp/script/../lib/MyApp/Controller"
+ exists "/home/me/MyApp/script/../t"
+ created "/home/me/MyApp/script/../lib/MyApp/Controller/Books.pm"
+ created "/home/me/MyApp/script/../t/controller_Books.t"
+
+Then edit C<lib/MyApp/Controller/Books.pm> (as discussed in Chapter 2 of
+the Tutorial, Catalyst has a separate directory under C<lib/MyApp> for
+each of the three parts of MVC: C<Model>, C<View>, and C<Controller>)
+and add the following method to the controller:
+
+ =head2 list
+
+ Fetch all book objects and pass to books/list.tt2 in stash to be displayed
+
+ =cut
+
+ sub list : Local {
+ # Retrieve the usual Perl OO '$self' for this object. $c is the Catalyst
+ # 'Context' that's used to 'glue together' the various components
+ # that make up the application
+ my ($self, $c) = @_;
+
+ # Retrieve all of the book records as book model objects and store in the
+ # stash where they can be accessed by the TT template
+ # $c->stash->{books} = [$c->model('DB::Book')->all];
+ # But, for now, use this code until we create the model later
+ $c->stash->{books} = '';
+
+ # Set the TT template to use. You will almost always want to do this
+ # in your action methods (action methods respond to user input in
+ # your controllers).
+ $c->stash->{template} = 'books/list.tt2';
+ }
+
+B<TIP>: See Appendix 1 for tips on removing the leading spaces when
+cutting and pasting example code from POD-based documents.
+
+Programmers experienced with object-oriented Perl should recognize
+C<$self> as a reference to the object where this method was called.
+On the other hand, C<$c> will be new to many Perl programmers who have
+not used Catalyst before (it's sometimes written as C<$context>). The
+Context object is automatically passed to all Catalyst components. It
+is used to pass information between components and provide access to
+Catalyst and plugin functionality.
+
+Catalyst actions are regular Perl methods, but they make use of
+attributes (the "C<: Local>" next to the "C<sub list>" in the code
+above) to provide additional information to the Catalyst dispatcher
+logic (note that the space between the colon and the attribute name is
+optional; you will see attributes written both ways). Most Catalyst
+Controllers use one of five action types:
+
+=over 4
+
+=item *
+
+B<:Private> -- Use C<:Private> for methods that you want to make into
+an action, but you do not want Catalyst to directly expose
+to your users. Catalyst will not map C<:Private> methods to a URI.
+Use them for various sorts of "special" methods (the C<begin>,
+C<auto>, etc. discussed below) or for methods you want to be able to
+C<forward> or C<detach> to. (If the method is a plain old "helper
+method" that you don't want to be an action at all, then just define
+the method without any attribute -- you can call it in your code, but
+the Catalyst dispatcher will ignore it.)
+
+There are five types of "special" build-in C<:Private> actions:
+C<begin>, C<end>, C<default>, C<index>, and C<auto>.
+
+=over 4
+
+=item *
+
+With C<begin>, C<end>, C<default>, C<index> private actions, only the
+most specific action of each type will be called. For example, if you
+define a C<begin> action in your controller it will I<override> a
+C<begin> action in your application/root controller -- I<only> the
+action in your controller will be called.
+
+=item *
+
+Unlike the other actions where only a single method is called for each
+request, I<every> auto action along the chain of namespaces will be
+called. Each C<auto> action will be called I<from the application/root
+controller down through the most specific class>.
+
+=back
+
+=item *
+
+B<:Path> -- C<:Path> actions let you map a method to an explicit URI
+path. For example, "C<:Path('list')>" in
+C<lib/MyApp/Controller/Books.pm> would match on the URL
+C<http://localhost:3000/books/list> but "C<:Path('/list')>" would match
+on C<http://localhost:3000/list>. You can use C<:Args()> to specify
+how many arguments an action should accept. See
+L<Catalyst::Manual::Intro/Action_types> for more information and a few
+examples.
+
+=item *
+
+B<:Local> -- C<:Local> is merely a shorthand for
+"C<:Path('_name_of_method_')>". For example, these are equivalent:
+"C<sub create_book :Local {...}>" and
+"C<sub create_book :Path('create_book') {...}>".
+
+=item *
+
+B<:Global> -- C<:Global> is merely a shorthand for
+"C<:Path('/_name_of_method_')>". For example, these are equivalent:
+"C<sub create_book :Global {...}>" and
+"C<sub create_book :Path('/create_book') {...}>".
+
+=item *
+
+B<:Chained> -- Newer Catalyst applications tend to use the Chained
+dispatch form of action types because of its power and flexibility.
+It allows a series of controller methods to be automatically dispatched
+to service a single user request. See
+L<Catalyst::Manual::Tutorial::04_BasicCRUD|Catalyst::Manual::Tutorial::04_BasicCRUD>
+and L<Catalyst::DispatchType::Chained|Catalyst::DispatchType::Chained>
+for more information on chained actions.
+
+=back
+
+You should refer to L<Catalyst::Manual::Intro/Action_types> for
+additional information and for coverage of some lesser-used action
+types not discussed here (C<Regex> and C<LocalRegex>).
+
+
+=head1 CATALYST VIEWS
+
+As mentioned in Chapter 2 of the tutorial, views are where you render
+output, typically for display in the user's web browser (but also
+possibly using into output-generation systems, such as PDF or JSON).
+The code in C<lib/MyApp/View> selects the I<type> of view to use, with
+the actual rendering template found in the C<root> directory. As with
+virtually every aspect of Catalyst, options abound when it comes to the
+specific view technology you adopt inside your application. However,
+most Catalyst applications use the Template Toolkit, known as TT (for
+more information on TT, see L<http://www.template-toolkit.org>). Other
+somewhat popular view technologies include Mason
+(L<http://www.masonhq.com> and L<http://www.masonbook.com>) and
+L<HTML::Template> (L<http://html-template.sourceforge.net>).
+
+
+=head2 Create a Catalyst View
+
+When using TT for the Catalyst view, there are two main helper scripts:
+
+=over 4
+
+=item *
+
+L<Catalyst::Helper::View::TT|Catalyst::Helper::View::TT>
+
+=item *
+
+L<Catalyst::Helper::View::TTSite|Catalyst::Helper::View::TTSite>
+
+=back
+
+Both helpers are similar. C<TT> creates the C<lib/MyApp/View/TT.pm>
+file and leaves the creation of any hierarchical template organization
+entirely up to you. (It also creates a C<t/view_TT.t> file for testing;
+test cases will be discussed in Chapter 8.) C<TTSite>, on the other hand,
+creates a modular and hierarchical view layout with
+separate Template Toolkit (TT) files for common header and footer
+information, configuration values, a CSS stylesheet, and more.
+
+While C<TTSite> was useful to bootstrap a project, its use is now
+deprecated and it should be considered historical. For most Catalyst
+applications it adds redundant functionality and structure; many in the
+Catalyst community recommend that it's easier to learn both Catalyst and
+Template Toolkit if you use the more basic C<TT> approach.
+Consequently, this tutorial will use "plain old TT."
+
+Enter the following command to enable the C<TT> style of view
+rendering for this tutorial:
+
+ $ script/myapp_create.pl view TT TT
+ exists "/home/me/MyApp/script/../lib/MyApp/View"
+ exists "/home/me/MyApp/script/../t"
+ created "/home/me/MyApp/script/../lib/MyApp/View/TT.pm"
+ created "/home/me/MyApp/script/../t/view_TT.t"
+
+This simply creates a view called C<TT> (the second 'TT' argument) in
+a file called C<TT.pm> (the first 'TT' argument). It is now up to you
+to decide how you want to structure your view layout. For the
+tutorial, we will start with a very simple TT template to initially
+demonstrate the concepts, but quickly migrate to a more typical
+"wrapper page" type of configuration (where the "wrapper" controls the
+overall "look and feel" of your site from a single file or set of
+files).
+
+Edit C<lib/MyApp/View/TT.pm> and you should see that the default
+contents contains something similar to the following:
+
+ __PACKAGE__->config(TEMPLATE_EXTENSION => '.tt');
+
+And update it to match:
+
+ __PACKAGE__->config(
+ # Change default TT extension
+ TEMPLATE_EXTENSION => '.tt2',
+ # Set the location for TT files
+ INCLUDE_PATH => [
+ MyApp->path_to( 'root', 'src' ),
+ ],
+ );
+
+B<NOTE:> Make sure to add a comma after '.tt2' outside the single
+quote.
+
+This changes the default extension for Template Toolkit from '.tt' to
+'.tt2' and changes the base directory for your template files from
+C<root> to C<root/src>. These changes from the default are done mostly
+to facilitate the application we're developing in this tutorial; as with
+most things Perl, there's more than one way to do it...
+
+B<Note:> We will use C<root/src> as the base directory for our
+template files, which a full naming convention of
+C<root/src/_controller_name_/_action_name_.tt2>. Another popular option is to
+use C<root/> as the base (with a full filename pattern of
+C<root/_controller_name_/_action_name_.tt2>).
+
+
+=head2 Create a TT Template Page
+
+First create a directory for book-related TT templates:
+
+ $ mkdir -p root/src/books
+
+Then create C<root/src/books/list.tt2> in your editor and enter:
+
+ [% # This is a TT comment. The '-' at the end "chomps" the newline. You won't -%]
+ [% # see this "chomping" in your browser because HTML ignores blank lines, but -%]
+ [% # it WILL eliminate a blank line if you view the HTML source. It's purely -%]
+ [%- # optional, but both the beginning and the ending TT tags support chomping. -%]
+
+ [% # Provide a title -%]
+ [% META title = 'Book List' -%]
+
+ <table>
+ <tr><th>Title</th><th>Rating</th><th>Author(s)</th></tr>
+ [% # Display each book in a table row %]
+ [% FOREACH book IN books -%]
+ <tr>
+ <td>[% book.title %]</td>
+ <td>[% book.rating %]</td>
+ <td></td>
+ </tr>
+ [% END -%]
+ </table>
+
+As indicated by the inline comments above, the C<META title> line uses
+TT's META feature to provide a title to the "wrapper" that we will
+create later. Meanwhile, the C<FOREACH> loop iterates through each
+C<book> model object and prints the C<title> and C<rating> fields.
+
+The C<[%> and C<%]> tags are used to delimit Template Toolkit code. TT
+supports a wide variety of directives for "calling" other files,
+looping, conditional logic, etc. In general, TT simplifies the usual
+range of Perl operators down to the single dot (C<.>) operator. This
+applies to operations as diverse as method calls, hash lookups, and list
+index values (see
+L<http://search.cpan.org/perldoc?Template::Manual::Variables> for
+details and examples). In addition to the usual C<Template> module Pod
+documentation, you can access the TT manual at
+L<http://search.cpan.org/perldoc?Template::Manual>.
+
+B<TIP:> While you can build all sorts of complex logic into your TT
+templates, you should in general keep the "code" part of your templates
+as simple as possible. If you need more complex logic, create helper
+methods in your model that abstract out a set of code into a single call
+from your TT template. (Note that the same is true of your controller
+logic as well -- complex sections of code in your controllers should
+often be pulled out and placed into your model objects.)
+
+
+=head2 Test Run The Application
+
+To test your work so far, first start the development server:
+
+ $ script/myapp_server.pl
+
+Then point your browser to L<http://localhost:3000> and you should
+still get the Catalyst welcome page. Next, change the URL in your
+browser to L<http://localhost:3000/books/list>. If you have
+everything working so far, you should see a web page that displays
+nothing other than our column headers for "Title", "Rating", and
+"Author(s)" -- we will not see any books until we get the database and
+model working below.
+
+If you run into problems getting your application to run correctly, it
+might be helpful to refer to some of the debugging techniques covered in
+the L<Debugging|Catalyst::Manual::Tutorial::07_Debugging> part of the
+tutorial.
+
+
+=head1 CREATE A SQLITE DATABASE
+
+In this step, we make a text file with the required SQL commands to
+create a database table and load some sample data. We will use SQLite,
+a popular database that is lightweight and easy to use. Open
+C<myapp01.sql> in your editor and enter:
+
+ --
+ -- Create a very simple database to hold book and author information
+ --
+ CREATE TABLE book (
+ id INTEGER PRIMARY KEY,
+ title TEXT ,
+ rating INTEGER
+ );
+ -- 'book_author' is a many-to-many join table between books & authors
+ CREATE TABLE book_author (
+ book_id INTEGER,
+ author_id INTEGER,
+ PRIMARY KEY (book_id, author_id)
+ );
+ CREATE TABLE author (
+ id INTEGER PRIMARY KEY,
+ first_name TEXT,
+ last_name TEXT
+ );
+ ---
+ --- Load some sample data
+ ---
+ INSERT INTO book VALUES (1, 'CCSP SNRS Exam Certification Guide', 5);
+ INSERT INTO book VALUES (2, 'TCP/IP Illustrated, Volume 1', 5);
+ INSERT INTO book VALUES (3, 'Internetworking with TCP/IP Vol.1', 4);
+ INSERT INTO book VALUES (4, 'Perl Cookbook', 5);
+ INSERT INTO book VALUES (5, 'Designing with Web Standards', 5);
+ INSERT INTO author VALUES (1, 'Greg', 'Bastien');
+ INSERT INTO author VALUES (2, 'Sara', 'Nasseh');
+ INSERT INTO author VALUES (3, 'Christian', 'Degu');
+ INSERT INTO author VALUES (4, 'Richard', 'Stevens');
+ INSERT INTO author VALUES (5, 'Douglas', 'Comer');
+ INSERT INTO author VALUES (6, 'Tom', 'Christiansen');
+ INSERT INTO author VALUES (7, 'Nathan', 'Torkington');
+ INSERT INTO author VALUES (8, 'Jeffrey', 'Zeldman');
+ INSERT INTO book_author VALUES (1, 1);
+ INSERT INTO book_author VALUES (1, 2);
+ INSERT INTO book_author VALUES (1, 3);
+ INSERT INTO book_author VALUES (2, 4);
+ INSERT INTO book_author VALUES (3, 5);
+ INSERT INTO book_author VALUES (4, 6);
+ INSERT INTO book_author VALUES (4, 7);
+ INSERT INTO book_author VALUES (5, 8);
+
+Then use the following command to build a C<myapp.db> SQLite database:
+
+ $ sqlite3 myapp.db < myapp01.sql
+
+If you need to create the database more than once, you probably want to
+issue the C<rm myapp.db> command to delete the database before you use
+the C<sqlite3 myapp.db E<lt> myapp01.sql> command.
+
+Once the C<myapp.db> database file has been created and initialized, you
+can use the SQLite command line environment to do a quick dump of the
+database contents:
+
+ $ sqlite3 myapp.db
+ SQLite version 3.5.9
+ Enter ".help" for instructions
+ sqlite> select * from book;
+ 1|CCSP SNRS Exam Certification Guide|5
+ 2|TCP/IP Illustrated, Volume 1|5
+ 3|Internetworking with TCP/IP Vol.1|4
+ 4|Perl Cookbook|5
+ 5|Designing with Web Standards|5
+ sqlite> .q
+ $
+
+Or:
+
+ $ sqlite3 myapp.db "select * from book"
+ 1|CCSP SNRS Exam Certification Guide|5
+ 2|TCP/IP Illustrated, Volume 1|5
+ 3|Internetworking with TCP/IP Vol.1|4
+ 4|Perl Cookbook|5
+ 5|Designing with Web Standards|5
+
+As with most other SQL tools, if you are using the full "interactive"
+environment you need to terminate your SQL commands with a ";" (it's not
+required if you do a single SQL statement on the command line). Use
+".q" to exit from SQLite from the SQLite interactive mode and return to
+your OS command prompt.
+
+Please note that here we have chosen to use 'singular' table names. This
+is because the default inflection code for L<DBIx::Class:Schema::Loader>
+does NOT handle plurals. There has been much philosophical discussion
+on whether table names should be plural or singular. There is no one
+correct answer, as long as one makes a choice and remains consistent
+with it. If you prefer plural table names (e.g. they are easier and
+more natural to read) then you will need to pass it an inflect_map
+option. See L<DBIx::Class:Schema::Loader> for more information.
+
+For using other databases, such as PostgreSQL or MySQL, see
+L<Appendix 2|Catalyst::Manual::Tutorial::10_Appendices>.
+
+
+=head1 DATABASE ACCESS WITH DBIx::Class
+
+Catalyst can be used with virtually any form of datastore available
+via Perl. For example, L<Catalyst::Model::DBI|Catalyst::Model::DBI>
+can be used to access databases through the traditional Perl C<DBI>
+interface or you can use a model to access files of any type on the
+filesystem. However, most Catalyst applications use some form of
+object-relational mapping (ORM) technology to create objects
+associated with tables in a relational database. Matt Trout's
+L<DBIx::Class|DBIx::Class> (abbreviated as "DBIC") has rapidly emerged
+as the Perl-based ORM technology of choice. Most new Catalyst
+applications rely on DBIx::Class, as will this tutorial.
+
+Although DBIx::Class has included support for a C<create=dynamic> mode
+to automatically read the database structure every time the
+application starts, it's use is no longer recommended. While it can
+make for "flashy" demos, the use of the C<create=static> mode we use
+below can be implemented just as quickly and provides many advantages
+(such as the ability to add your own methods to the overall DBIC
+framework, a technique that we see in Chapter 4).
+
+
+=head2 Make Sure You Have a Recent Version of the DBIx::Class Model
+
+First, let's be sure we have a recent version of the DBIC helper,
+L<Catalyst::Model::DBIC::Schema|Catalyst::Model::DBIC::Schema>, by
+running this command:
+
+ $ perl -MCatalyst::Model::DBIC::Schema -e \
+ 'print "$Catalyst::Model::DBIC::Schema::VERSION\n"'
+ 0.23
+
+(please note that the '\' above is a line continuation marker and
+should NOT be included as part of the command)
+
+If you don't have version 0.23 or higher, please run this command
+to install it directly from CPAN:
+
+ $ sudo cpan Catalyst::Model::DBIC::Schema
+
+And re-run the version print command to verify that you are now at
+0.23 or higher.
+
+
+=head2 Create Static DBIx::Class Schema Files
+
+Before you continue, make sure your C<myapp.db> database file is in
+the application's topmost directory. Now use the model helper with
+the C<create=static> option to read the database with
+L<DBIx::Class::Schema::Loader|DBIx::Class::Schema::Loader> and
+automatically build the required files for us:
+
+ $ script/myapp_create.pl model DB DBIC::Schema MyApp::Schema \
+ create=static components=TimeStamp dbi:SQLite:myapp.db
+ exists "/home/me/MyApp/script/../lib/MyApp/Model"
+ exists "/home/me/MyApp/script/../t"
+ Dumping manual schema for MyApp::Schema to directory /home/me/MyApp/script/../lib ...
+ Schema dump completed.
+ created "/home/me/MyApp/script/../lib/MyApp/Model/DB.pm"
+ created "/home/me/MyApp/script/../t/model_DB.t"
+
+(please note that the '\' above is a line continuation marker and
+should NOT be included as part of the command)
+
+The C<script/myapp_create.pl> command breaks down like this:
+
+=over 4
+
+=item *
+
+C<DB> is the name of the model class to be created by the helper in
+C<lib/MyApp/Model>.
+
+=item *
+
+C<DBIC::Schema> is the type of the model to create.
+
+=item *
+
+C<MyApp::Schema> is the name of the DBIC schema file written to
+C<lib/MyApp/Schema.pm>.
+
+=item *
+
+C<create=static> causes
+L<DBIx::Class::Schema::Loader|DBIx::Class::Schema::Loader> to
+load the schema as it runs and then write that information out
+into files.
+
+=item *
+
+C<components=TimeStamp> causes the help to include the
+L<DBIx::Class::TimeStamp|DBIx::Class::TimeStamp> DBIC component.
+
+=item *
+
+And finally, C<dbi:SQLite:myapp.db> is the standard DBI connect string
+for use with SQLite.
+
+=back
+
+If you look in the C<lib/MyApp/Schema.pm> file, you will find that it
+only contains a call to the C<load_namespaces> method. You will also
+find that C<lib/MyApp> contains a C<Schema> subdirectory, which then
+has a subdirectory called "Result". This "Result" subdirectory then
+has files named according to each of the tables in our simple database
+(C<Author.pm>, C<BookAuthor.pm>, and C<Book.pm>). These three
+files are called "Result Classes" in DBIx::Class nomenclature. Although the
+Result Class files are named after tables in our database, the classes
+correspond to the I<row-level data> that is returned by DBIC (more on
+this later, especially in
+L<Catalyst::Manual::Tutorial::04_BasicCRUD/EXPLORING THE POWER OF DBIC>).
+
+The idea with the Result Source files created under
+C<lib/MyApp/Schema/Result> by the C<create=static> option is to only
+edit the files below the C<# DO NOT MODIFY THIS OR ANYTHING ABOVE!>
+warning. If you place all of your changes below that point in the
+file, you can regenerate the automatically created information at the
+top of each file should your database structure get updated.
+
+Also note the "flow" of the model information across the various files
+and directories. Catalyst will initially load the model from
+C<lib/MyApp/Model/DB.pm>. This file contains a reference to
+C<lib/MyApp/Schema.pm>, so that file is loaded next. Finally, the
+call to C<load_namespaces> in C<Schema.pm> will load each of the
+"Result Class" files from the C<lib/MyApp/Schema/Result> subdirectory.
+The final outcome is that Catalyst will dynamically create three
+table-specific Catalyst models every time the application starts (you
+can see these three model files listed in the debug output generated
+when you launch the application).
+
+B<NOTE:> Older versions of
+L<Catalyst::Model::DBIC::Schema|Catalyst::Model::DBIC::Schema> use the
+deprecated DBIx::Class C<load_classes> technique instead of the newer
+C<load_namspaces>. For new applications, please try to use
+C<load_namespaces> since it more easily supports a very useful DBIC
+technique called "ResultSet Classes." If you need to convert an
+existing application from "load_classes" to "load_namespaces," you can
+use this process to automate the migration (but first make sure you
+have v0.23 C<Catalyst::Model::DBIC::Schema> as discussed above):
+
+ $ # First delete the existing schema file to disable "compatibility" mode
+ $ rm lib/MyApp/Schema.pm
+ $
+ $ # Then re-run the helper to build the files for "load_namespaces"
+ $ script/myapp_create.pl model DB DBIC::Schema MyApp::Schema \
+ create=static components=TimeStamp dbi:SQLite:myapp.db
+ $
+ $ # Note that the '\' above is a line continuation marker and
+ $ # should NOT be included as part of the command
+
+ $
+ $ # Now convert the existing files over
+ $ cd lib/MyApp/Schema
+ $ perl -MIO::All -e 'for (@ARGV) { my $s < io($_); $s =~ s/.*\n\# You can replace.*?\n//s;
+ $s =~ s/'MyApp::Schema::/'MyApp::Schema::Result::/g; my $d < io("Result/$_");
+ $d =~ s/1;\n?//; "$d$s" > io("Result/$_"); }' *.pm
+ $ cd ../../..
+ $
+ $ # And finally delete the old files
+ $ rm lib/MyApp/Schema/*.pm
+
+The "C<perl -MIO::ALL ...>" script will copy all the customized
+relationship (and other) information below "C<# DO NOT MODIFY>" line
+from the old files in C<lib/MyApp/Schema> to the new files in
+C<lib/MyApp/Schema/Result> (we will be starting to add some
+"customized relationship information in the section below).
+
+
+=head1 ENABLE THE MODEL IN THE CONTROLLER
+
+Open C<lib/MyApp/Controller/Books.pm> and un-comment the model code we
+left disabled earlier so that your version matches the following (un-
+comment the line containing C<[$c-E<gt>model('DB::Book')-E<gt>all]>
+and delete the next 2 lines):
+
+ =head2 list
+
+ Fetch all book objects and pass to books/list.tt2 in stash to be displayed
+
+ =cut
+
+ sub list : Local {
+ # Retrieve the usual Perl OO '$self' for this object. $c is the Catalyst
+ # 'Context' that's used to 'glue together' the various components
+ # that make up the application
+ my ($self, $c) = @_;
+
+ # Retrieve all of the book records as book model objects and store in the
+ # stash where they can be accessed by the TT template
+ $c->stash->{books} = [$c->model('DB::Book')->all];
+
+ # Set the TT template to use. You will almost always want to do this
+ # in your action methods (action methods respond to user input in
+ # your controllers).
+ $c->stash->{template} = 'books/list.tt2';
+ }
+
+B<TIP>: You may see the C<$c-E<gt>model('DB::Book')> un-commented
+above written as C<$c-E<gt>model('DB')-E<gt>resultset('Book')>. The
+two are equivalent. Either way, C<$c-E<gt>model> returns a
+L<DBIx::Class::ResultSet|DBIx::Class::ResultSet> which handles queries
+against the database and iterating over the set of results that is
+returned.
+
+We are using the C<-E<gt>all> to fetch all of the books. DBIC
+supports a wide variety of more advanced operations to easily do
+things like filtering and sorting the results. For example, the
+following could be used to sort the results by descending title:
+
+ $c->model('DB::Book')->search({}, {order_by => 'title DESC'});
+
+Some other examples are provided in
+L<DBIx::Class::Manual::Cookbook/Complex WHERE clauses>, with
+additional information found at L<DBIx::Class::ResultSet/search>,
+L<DBIx::Class::Manual::FAQ/Searching>,
+L<DBIx::Class::Manual::Intro|DBIx::Class::Manual::Intro>
+and L<Catalyst::Model::DBIC::Schema|Catalyst::Model::DBIC::Schema>.
+
+
+=head2 Test Run The Application
+
+First, let's enable an environment variable that causes DBIx::Class to
+dump the SQL statements used to access the database. This is a
+helpful trick when you are trying to debug your database-oriented
+code:
+
+ $ export DBIC_TRACE=1
+
+This assumes you are using bash as your shell -- adjust accordingly if
+you are using a different shell (for example, under tcsh, use
+C<setenv DBIC_TRACE 1>).
+
+B<NOTE:> You can also set this in your code using
+C<$class-E<gt>storage-E<gt>debug(1);>. See
+L<DBIx::Class::Manual::Troubleshooting> for details (including options
+to log to a file instead of displaying to the Catalyst development server
+log).
+
+Then launch the Catalyst development server. The log output should
+display something like:
+
+ $ script/myapp_server.pl
+ [debug] Debug messages enabled
+ [debug] Statistics enabled
+ [debug] Loaded plugins:
+ .----------------------------------------------------------------------------.
+ | Catalyst::Plugin::ConfigLoader 0.23 |
+ | Catalyst::Plugin::StackTrace 0.10 |
+ | Catalyst::Plugin::Static::Simple 0.21 |
+ '----------------------------------------------------------------------------'
+
+ [debug] Loaded dispatcher "Catalyst::Dispatcher"
+ [debug] Loaded engine "Catalyst::Engine::HTTP"
+ [debug] Found home "/home/me/MyApp"
+ [debug] Loaded Config "/home/me/MyApp/myapp.conf"
+ [debug] Loaded components:
+ .-----------------------------------------------------------------+----------.
+ | Class | Type |
+ +-----------------------------------------------------------------+----------+
+ | MyApp::Controller::Books | instance |
+ | MyApp::Controller::Root | instance |
+ | MyApp::Model::DB | instance |
+ | MyApp::Model::DB::Author | class |
+ | MyApp::Model::DB::Book | class |
+ | MyApp::Model::DB::BookAuthor | class |
+ | MyApp::View::TT | instance |
+ '-----------------------------------------------------------------+----------'
+
+ [debug] Loaded Private actions:
+ .----------------------+--------------------------------------+--------------.
+ | Private | Class | Method |
+ +----------------------+--------------------------------------+--------------+
+ | /default | MyApp::Controller::Root | default |
+ | /end | MyApp::Controller::Root | end |
+ | /index | MyApp::Controller::Root | index |
+ | /books/index | MyApp::Controller::Books | index |
+ | /books/list | MyApp::Controller::Books | list |
+ '----------------------+--------------------------------------+--------------'
+
+ [debug] Loaded Path actions:
+ .-------------------------------------+--------------------------------------.
+ | Path | Private |
+ +-------------------------------------+--------------------------------------+
+ | / | /default |
+ | / | /index |
+ | /books | /books/index |
+ | /books/list | /books/list |
+ '-------------------------------------+--------------------------------------'
+
+ [info] MyApp powered by Catalyst 5.80003
+ You can connect to your server at http://debian:3000
+
+B<NOTE:> Be sure you run the C<script/myapp_server.pl> command from
+the 'base' directory of your application, not inside the C<script>
+directory itself or it will not be able to locate the C<myapp.db>
+database file. You can use a fully qualified or a relative path to
+locate the database file, but we did not specify that when we ran the
+model helper earlier.
+
+Some things you should note in the output above:
+
+=over 4
+
+=item *
+
+Catalyst::Model::DBIC::Schema dynamically created three model classes,
+one to represent each of the three tables in our database
+(C<MyApp::Model::DB::Author>, C<MyApp::Model::DB::BookAuthor>,
+and C<MyApp::Model::DB::Book>).
+
+=item *
+
+The "list" action in our Books controller showed up with a path of
+C</books/list>.
+
+=back
+
+Point your browser to L<http://localhost:3000> and you should still get
+the Catalyst welcome page.
+
+Next, to view the book list, change the URL in your browser to
+L<http://localhost:3000/books/list>. You should get a list of the five
+books loaded by the C<myapp01.sql> script above without any formatting.
+The rating for each book should appear on each row, but the "Author(s)"
+column will still be blank (we will fill that in later).
+
+Also notice in the output of the C<script/myapp_server.pl> that
+DBIx::Class used the following SQL to retrieve the data:
+
+ SELECT me.id, me.title, me.rating FROM books me
+
+because we enabled DBIC_TRACE.
+
+You now have the beginnings of a simple but workable web application.
+Continue on to future sections and we will develop the application
+more fully.
+
+
+=head1 CREATE A WRAPPER FOR THE VIEW
+
+When using TT, you can (and should) create a wrapper that will
+literally wrap content around each of your templates. This is
+certainly useful as you have one main source for changing things that
+will appear across your entire site/application instead of having to
+edit many individual files.
+
+
+=head2 Configure TT.pm For The Wrapper
+
+In order to create a wrapper, you must first edit your TT view and
+tell it where to find your wrapper file. Your TT view is located in
+C<lib/MyApp/View/TT.pm>.
+
+Edit C<lib/MyApp/View/TT.pm> and change it to match the following:
+
+ __PACKAGE__->config(
+ # Change default TT extension
+ TEMPLATE_EXTENSION => '.tt2',
+ # Set the location for TT files
+ INCLUDE_PATH => [
+ MyApp->path_to( 'root', 'src' ),
+ ],
+ # Set to 1 for detailed timer stats in your HTML as comments
+ TIMER => 0,
+ # This is your wrapper template located in the 'root/src'
+ WRAPPER => 'wrapper.tt2',
+ );
+
+
+=head2 Create the Wrapper Template File and Stylesheet
+
+Next you need to set up your wrapper template. Basically, you'll want
+to take the overall layout of your site and put it into this file.
+For the tutorial, open C<root/src/wrapper.tt2> and input the following:
+
+ <?xml version="1.0" encoding="UTF-8"?>
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ <head>
+ <title>[% template.title or "My Catalyst App!" %]</title>
+ <link rel="stylesheet" href="[% c.uri_for('/static/css/main.css') %]" />
+ </head>
+
+ <body>
+ <div id="outer">
+ <div id="header">
+ [%# Your logo could go here -%]
+ <img src="[% c.uri_for('/static/images/btn_88x31_powered.png') %]" />
+ [%# Insert the page title -%]
+ <h1>[% template.title or site.title %]</h1>
+ </div>
+
+ <div id="bodyblock">
+ <div id="menu">
+ Navigation:
+ <ul>
+ <li><a href="[% c.uri_for('/books/list') %]">Home</a></li>
+ <li><a href="[% c.uri_for('/') %]" title="Catalyst Welcome Page">Welcome</a></li>
+ </ul>
+ </div><!-- end menu -->
+
+ <div id="content">
+ [%# Status and error messages %]
+ <span class="message">[% status_msg %]</span>
+ <span class="error">[% error_msg %]</span>
+ [%# This is where TT will stick all of your template's contents. -%]
+ [% content %]
+ </div><!-- end content -->
+ </div><!-- end bodyblock -->
+
+ <div id="footer">Copyright (c) your name goes here</div>
+ </div><!-- end outer -->
+
+ </body>
+ </html>
+
+Notice the status and error message sections in the code above:
+
+ <span class="status">[% status_msg %]</span>
+ <span class="error">[% error_msg %]</span>
+
+If we set either message in the Catalyst stash (e.g.,
+C<$c-E<gt>stash-E<gt>{status_msg} = 'Request was successful!'>) it
+will be displayed whenever any view used by that request is rendered.
+The C<message> and C<error> CSS styles can be customized to suit your
+needs in the C<root/static/css/main.css> file we create below.
+
+B<Notes:>
+
+=over 4
+
+=item *
+
+The Catalyst stash only lasts for a single HTTP request. If
+you need to retain information across requests you can use
+L<Catalyst::Plugin::Session|Catalyst::Plugin::Session> (we will use
+Catalyst sessions in the Authentication chapter of the tutorial).
+
+=item *
+
+Although it is beyond the scope of this tutorial, you may wish to use
+a JavaScript or AJAX tool such as jQuery (L<http://www.jquery.com>) or
+Dojo (L<http://www.dojotoolkit.org>).
+
+=back
+
+
+=head3 Create A Basic Stylesheet
+
+First create a central location for stylesheets under the static
+directory:
+
+ $ mkdir root/static/css
+
+Then open the file C<root/static/css/main.css> (the file referenced in
+the stylesheet href link of our wrapper above) and add the following
+content:
+
+ #header {
+ text-align: center;
+ }
+ #header h1 {
+ margin: 0;
+ }
+ #header img {
+ float: right;
+ }
+ #footer {
+ text-align: center;
+ font-style: italic;
+ padding-top: 20px;
+ }
+ #menu {
+ font-weight: bold;
+ background-color: #ddd;
+ }
+ #menu ul {
+ list-style: none;
+ float: left;
+ margin: 0;
+ padding: 0 0 50% 5px;
+ font-weight: normal;
+ background-color: #ddd;
+ width: 100px;
+ }
+ #content {
+ margin-left: 120px;
+ }
+ .message {
+ color: #390;
+ }
+ .error {
+ color: #f00;
+ }
+
+You may wish to check out a "CSS Framework" like Emastic
+(L<http://code.google.com/p/emastic/>) as a way to quickly
+provide lots of high-quality CSS functionality.
+
+
+=head2 Test Run The Application
+
+Restart the development server and hit "Reload" in your web browser
+and you should now see a formatted version of our basic book list.
+Although our wrapper and stylesheet are obviously very simple, you
+should see how it allows us to control the overall look of an entire
+website from two central files. To add new pages to the site, just
+provide a template that fills in the C<content> section of our wrapper
+template -- the wrapper will provide the overall feel of the page.
+
+
+=head2 Updating the Generated DBIx::Class Result Class Files
+
+Let's manually add some relationship information to the auto-generated
+Result Class files. (Note: if you are using a database other than
+SQLite, such as PostgreSQL, then the relationship could have been
+automatically placed in the Result Class files. If so, you can skip
+this step.) First edit C<lib/MyApp/Schema/Result/Book.pm> and add the
+following text below the C<# You can replace this text...> comment:
+
+ #
+ # Set relationships:
+ #
+
+ # has_many():
+ # args:
+ # 1) Name of relationship, DBIC will create accessor with this name
+ # 2) Name of the model class referenced by this relationship
+ # 3) Column name in *foreign* table (aka, foreign key in peer table)
+ __PACKAGE__->has_many(book_author => 'MyApp::Schema::Result::BookAuthor', 'book_id');
+
+ # many_to_many():
+ # args:
+ # 1) Name of relationship, DBIC will create accessor with this name
+ # 2) Name of has_many() relationship this many_to_many() is shortcut for
+ # 3) Name of belongs_to() relationship in model class of has_many() above
+ # You must already have the has_many() defined to use a many_to_many().
+ __PACKAGE__->many_to_many(author => 'book_author', 'author');
+
+
+B<Note:> Be careful to put this code I<above> the C<1;> at the end of the
+file. As with any Perl package, we need to end the last line with
+a statement that evaluates to C<true>. This is customarily done with
+C<1;> on a line by itself.
+
+This code defines both a C<has_many> and a C<many_to_many>
+relationship. The C<many_to_many> relationship is optional, but it
+makes it easier to map a book to its collection of authors. Without
+it, we would have to "walk" though the C<book_author> table as in
+C<$book-E<gt>book_author-E<gt>first-E<gt>author-E<gt>last_name> (we
+will see examples on how to use DBIx::Class objects in your code soon,
+but note that because C<$book-E<gt>book_author> can return multiple
+authors, we have to use C<first> to display a single author).
+C<many_to_many> allows us to use the shorter C<$book-E<gt>author-
+E<gt>first-E<gt>last_name>. Note that you cannot define a
+C<many_to_many> relationship without also having the C<has_many>
+relationship in place.
+
+Then edit C<lib/MyApp/Schema/Result/Author.pm> and add relationship
+information as follows (again, be careful to put in above the C<1;> but
+below the C<# DO NOT MODIFY THIS OR ANYTHING ABOVE!> comment):
+
+ #
+ # Set relationships:
+ #
+
+ # has_many():
+ # args:
+ # 1) Name of relationship, DBIC will create an accessor with this name
+ # 2) Name of the model class referenced by this relationship
+ # 3) Column name in *foreign* table (aka, foreign key in peer table)
+ __PACKAGE__->has_many(book_author => 'MyApp::Schema::Result::BookAuthor', 'author_id');
+
+ # many_to_many():
+ # args:
+ # 1) Name of relationship, DBIC will create accessor with this name
+ # 2) Name of has_many() relationship this many_to_many() is shortcut for
+ # 3) Name of belongs_to() relationship in model class of has_many() above
+ # You must already have the has_many() defined to use a many_to_many().
+ __PACKAGE__->many_to_many(book => 'book_author', 'book');
+
+Finally, do the same for the "join table,"
+C<lib/MyApp/Schema/Result/BookAuthor.pm>:
+
+ #
+ # Set relationships:
+ #
+
+ # belongs_to():
+ # args:
+ # 1) Name of relationship, DBIC will create accessor with this name
+ # 2) Name of the model class referenced by this relationship
+ # 3) Column name in *this* table
+ __PACKAGE__->belongs_to(book => 'MyApp::Schema::Result::Book', 'book_id');
+
+ # belongs_to():
+ # args:
+ # 1) Name of relationship, DBIC will create accessor with this name
+ # 2) Name of the model class referenced by this relationship
+ # 3) Column name in *this* table
+ __PACKAGE__->belongs_to(author => 'MyApp::Schema::Result::Author', 'author_id');
+
+
+=head2 Run The Application
+
+Run the Catalyst development server script with the C<DBIC_TRACE> option
+(it might still be enabled from earlier in the tutorial, but here is an
+alternate way to specify the option just in case):
+
+ $ DBIC_TRACE=1 script/myapp_server.pl
+
+Make sure that the application loads correctly and that you see the
+three dynamically created model class (one for each of the
+Result Classes we created).
+
+Then hit the URL L<http://localhost:3000/books/list> with your browser
+and be sure that the book list is displayed via the relationships
+established above. You can leave the development server running for
+the next step if you wish.
+
+B<Note:> You will not see the authors yet because the view does not yet
+use the new relations. Read on to the next section where we update the
+template to do that.
+
+
+=head1 UPDATING THE VIEW
+
+Let's add a new column to our book list page that takes advantage of
+the relationship information we manually added to our schema files in
+the previous section. Edit C<root/src/books/list.tt2> and replace
+the "empty" table cell "<td></td>" with the following:
+
+ ...
+ <td>
+ [% # First initialize a TT variable to hold a list. Then use a TT FOREACH -%]
+ [% # loop in 'side effect notation' to load just the last names of the -%]
+ [% # authors into the list. Note that the 'push' TT vmethod does not print -%]
+ [% # a value, so nothing will be printed here. But, if you have something -%]
+ [% # in TT that does return a method and you don't want it printed, you -%]
+ [% # can: 1) assign it to a bogus value, or 2) use the CALL keyword to -%]
+ [% # call it and discard the return value. -%]
+ [% tt_authors = [ ];
+ tt_authors.push(author.last_name) FOREACH author = book.authors %]
+ [% # Now use a TT 'virtual method' to display the author count in parens -%]
+ [% # Note the use of the TT filter "| html" to escape dangerous characters -%]
+ ([% tt_authors.size | html %])
+ [% # Use another TT vmethod to join & print the names & comma separators -%]
+ [% tt_authors.join(', ') | html %]
+ </td>
+ ...
+
+Then hit "Reload" in your browser (note that you don't need to reload
+the development server or use the C<-r> option when updating TT
+templates) and you should now see the number of authors each book has
+along with a comma-separated list of the authors' last names. (If you
+didn't leave the development server running from the previous step,
+you will obviously need to start it before you can refresh your
+browser window.)
+
+If you are still running the development server with C<DBIC_TRACE>
+enabled, you should also now see five more C<SELECT> statements in the
+debug output (one for each book as the authors are being retrieved by
+DBIx::Class):
+
+ SELECT me.id, me.title, me.rating FROM books me:
+ SELECT author.id, author.first_name, author.last_name FROM book_author me
+ JOIN author author ON ( author.id = me.author_id ) WHERE ( me.book_id = ? ): '1'
+ SELECT author.id, author.first_name, author.last_name FROM book_author me
+ JOIN author author ON ( author.id = me.author_id ) WHERE ( me.book_id = ? ): '2'
+ SELECT author.id, author.first_name, author.last_name FROM book_author me
+ JOIN author author ON ( author.id = me.author_id ) WHERE ( me.book_id = ? ): '3'
+ SELECT author.id, author.first_name, author.last_name FROM book_author me
+ JOIN author author ON ( author.id = me.author_id ) WHERE ( me.book_id = ? ): '4'
+ SELECT author.id, author.first_name, author.last_name FROM book_author me
+ JOIN author author ON ( author.id = me.author_id ) WHERE ( me.book_id = ? ): '5'
+
+Also note in C<root/src/books/list.tt2> that we are using "| html", a
+type of TT filter, to escape characters such as E<lt> and E<gt> to <
+and > and avoid various types of dangerous hacks against your
+application. In a real application, you would probably want to put
+"| html" at the end of every field where a user has control over the
+information that can appear in that field (and can therefore inject
+markup or code if you don't "neutralize" those fields). In addition to
+"| html", Template Toolkit has a variety of other useful filters that
+can found in the documentation for
+L<Template::Filters|Template::Filters>.
+
+
+=head1 RUNNING THE APPLICATION FROM THE COMMAND LINE
+
+In some situations, it can be useful to run your application and
+display a page without using a browser. Catalyst lets you do this
+using the C<scripts/myapp_test.pl> script. Just supply the URL you
+wish to display and it will run that request through the normal
+controller dispatch logic and use the appropriate view to render the
+output (obviously, complex pages may dump a lot of text to your
+terminal window). For example, if you type:
+
+ $ script/myapp_test.pl "/books/list"
+
+You should get the same text as if you visited
+L<http://localhost:3000/books/list> with the normal development server
+and asked your browser to view the page source.
+
+
+=head1 OPTIONAL INFORMATION
+
+B<NOTE: The rest of this chapter of the tutorial is optional. You can
+skip to Chapter 4, L<Basic CRUD|Catalyst::Manual::Tutorial::04_BasicCRUD>,
+if you wish.>
+
+
+=head2 Using 'RenderView' for the Default View
+
+Once your controller logic has processed the request from a user, it
+forwards processing to your view in order to generate the appropriate
+response output. Catalyst uses
+L<Catalyst::Action::RenderView|Catalyst::Action::RenderView> by
+default to automatically perform this operation. If you look in
+C<lib/MyApp/Controller/Root.pm>, you should see the empty
+definition for the C<sub end> method:
+
+ sub end : ActionClass('RenderView') {}
+
+The following bullet points provide a quick overview of the
+C<RenderView> process:
+
+=over 4
+
+=item *
+
+C<Root.pm> is designed to hold application-wide logic.
+
+=item *
+
+At the end of a given user request, Catalyst will call the most specific
+C<end> method that's appropriate. For example, if the controller for a
+request has an C<end> method defined, it will be called. However, if
+the controller does not define a controller-specific C<end> method, the
+"global" C<end> method in C<Root.pm> will be called.
+
+=item *
+
+Because the definition includes an C<ActionClass> attribute, the
+L<Catalyst::Action::RenderView|Catalyst::Action::RenderView> logic
+will be executed B<after> any code inside the definition of C<sub end>
+is run. See L<Catalyst::Manual::Actions|Catalyst::Manual::Actions>
+for more information on C<ActionClass>.
+
+=item *
+
+Because C<sub end> is empty, this effectively just runs the default
+logic in C<RenderView>. However, you can easily extend the
+C<RenderView> logic by adding your own code inside the empty method body
+(C<{}>) created by the Catalyst Helpers when we first ran the
+C<catalyst.pl> to initialize our application. See
+L<Catalyst::Action::RenderView|Catalyst::Action::RenderView> for more
+detailed information on how to extend C<RenderView> in C<sub end>.
+
+=back
+
+
+=head2 Using The Default Template Name
+
+By default, C<Catalyst::View::TT> will look for a template that uses the
+same name as your controller action, allowing you to save the step of
+manually specifying the template name in each action. For example, this
+would allow us to remove the
+C<$c-E<gt>stash-E<gt>{template} = 'books/list.tt2';> line of our
+C<list> action in the Books controller. Open
+C<lib/MyApp/Controller/Books.pm> in your editor and comment out this line
+to match the following (only the C<$c-E<gt>stash-E<gt>{template}> line
+has changed):
+
+ =head2 list
+
+ Fetch all book objects and pass to books/list.tt2 in stash to be displayed
+
+ =cut
+
+ sub list : Local {
+ # Retrieve the usual Perl OO '$self' for this object. $c is the Catalyst
+ # 'Context' that's used to 'glue together' the various components
+ # that make up the application
+ my ($self, $c) = @_;
+
+ # Retrieve all of the book records as book model objects and store in the
+ # stash where they can be accessed by the TT template
+ $c->stash->{books} = [$c->model('DB::Book')->all];
+
+ # Set the TT template to use. You will almost always want to do this
+ # in your action methods (actions methods respond to user input in
+ # your controllers).
+ #$c->stash->{template} = 'books/list.tt2';
+ }
+
+
+You should now be able to restart the development server as per the
+previous section and access the L<http://localhost:3000/books/list>
+as before.
+
+B<NOTE:> Please note that if you use the default template technique,
+you will B<not> be able to use either the C<$c-E<gt>forward> or
+the C<$c-E<gt>detach> mechanisms (these are discussed in Chapter 2 and
+Chapter 9 of the Tutorial).
+
+B<IMPORTANT:> Make sure that you do NOT skip the following section
+before continuing to the next chapter 4 Basic CRUD.
+
+=head2 Return To A Manually Specified Template
+
+In order to be able to use C<$c-E<gt>forward> and C<$c-E<gt>detach>
+later in the tutorial, you should remove the comment from the
+statement in C<sub list> in C<lib/MyApp/Controller/Books.pm>:
+
+ $c->stash->{template} = 'books/list.tt2';
+
+Then delete the C<TEMPLATE_EXTENSION> line in
+C<lib/MyApp/View/TT.pm>.
+
+You should then be able to restart the development server and
+access L<http://localhost:3000/books/list> in the same manner as
+with earlier sections.
+
+
+=head1 AUTHOR
+
+Kennedy Clark, C<hkclark at gmail.com>
+
+Please report any errors, issues or suggestions to the author. The
+most recent version of the Catalyst Tutorial can be found at
+L<http://dev.catalyst.perl.org/repos/Catalyst/Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/>.
+
+Copyright 2006-2008, Kennedy Clark, under Creative Commons License
+(L<http://creativecommons.org/licenses/by-sa/3.0/us/>).
Copied: Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/04_BasicCRUD.pod (from rev 10275, Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/BasicCRUD.pod)
===================================================================
--- Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/04_BasicCRUD.pod (rev 0)
+++ Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/04_BasicCRUD.pod 2009-05-24 22:30:47 UTC (rev 10277)
@@ -0,0 +1,1302 @@
+=head1 NAME
+
+Catalyst::Manual::Tutorial::04_BasicCRUD - Catalyst Tutorial - Chapter 4: Basic CRUD
+
+
+=head1 OVERVIEW
+
+This is B<Chapter 4 of 10> for the Catalyst tutorial.
+
+L<Tutorial Overview|Catalyst::Manual::Tutorial>
+
+=over 4
+
+=item 1
+
+L<Introduction|Catalyst::Manual::Tutorial::01_Intro>
+
+=item 2
+
+L<Catalyst Basics|Catalyst::Manual::Tutorial::02_CatalystBasics>
+
+=item 3
+
+L<More Catalyst Basics|Catalyst::Manual::Tutorial::03_MoreCatalystBasics>
+
+=item 4
+
+B<04_Basic CRUD>
+
+=item 5
+
+L<Authentication|Catalyst::Manual::Tutorial::05_Authentication>
+
+=item 6
+
+L<Authorization|Catalyst::Manual::Tutorial::06_Authorization>
+
+=item 7
+
+L<Debugging|Catalyst::Manual::Tutorial::07_Debugging>
+
+=item 8
+
+L<Testing|Catalyst::Manual::Tutorial::08_Testing>
+
+=item 9
+
+L<Advanced CRUD|Catalyst::Manual::Tutorial::09_AdvancedCRUD>
+
+=item 10
+
+L<Appendices|Catalyst::Manual::Tutorial::10_Appendices>
+
+=back
+
+
+=head1 DESCRIPTION
+
+This chapter of the tutorial builds on the fairly primitive
+application created in Chapter 3 to add basic support for Create,
+Read, Update, and Delete (CRUD) of C<Book> objects. Note that the
+'list' function in Chapter 2 already implements the Read portion of
+CRUD (although Read normally refers to reading a single object; you
+could implement full Read functionality using the techniques
+introduced below). This section will focus on the Create and Delete
+aspects of CRUD. More advanced capabilities, including full Update
+functionality, will be addressed in Chapter 9.
+
+Although this chapter of the tutorial will show you how to build CRUD
+functionality yourself, another option is to use a "CRUD builder" type
+of tool to automate the process. You get less control, but it's quick
+and easy. For example, see
+L<CatalystX::ListFramework::Builder|CatalystX::ListFramework::Builder>,
+L<CatalystX::CRUD|CatalystX::CRUD>, and
+L<CatalystX::CRUD::YUI|CatalystX::CRUD::YUI>.
+
+You can check out the source code for this example from the Catalyst
+Subversion repository as per the instructions in
+L<Catalyst::Manual::Tutorial::01_Intro|Catalyst::Manual::Tutorial::01_Intro>.
+
+
+=head1 FORMLESS SUBMISSION
+
+Our initial attempt at object creation will utilize the "URL
+arguments" feature of Catalyst (we will employ the more common form-
+based submission in the sections that follow).
+
+
+=head2 Include a Create Action in the Books Controller
+
+Edit C<lib/MyApp/Controller/Books.pm> and enter the following method:
+
+ =head2 url_create
+
+ Create a book with the supplied title, rating, and author
+
+ =cut
+
+ sub url_create : Local {
+ # In addition to self & context, get the title, rating, &
+ # author_id args from the URL. Note that Catalyst automatically
+ # puts extra information after the "/<controller_name>/<action_name/"
+ # into @_
+ my ($self, $c, $title, $rating, $author_id) = @_;
+
+ # Call create() on the book model object. Pass the table
+ # columns/field values we want to set as hash values
+ my $book = $c->model('DB::Book')->create({
+ title => $title,
+ rating => $rating
+ });
+
+ # Add a record to the join table for this book, mapping to
+ # appropriate author
+ $book->add_to_book_author({author_id => $author_id});
+ # Note: Above is a shortcut for this:
+ # $book->create_related('book_author', {author_id => $author_id});
+
+ # Assign the Book object to the stash for display in the view
+ $c->stash->{book} = $book;
+
+ # Set the TT template to use
+ $c->stash->{template} = 'books/create_done.tt2';
+ }
+
+Notice that Catalyst takes "extra slash-separated information" from the
+URL and passes it as arguments in C<@_>. The C<url_create> action then
+uses a simple call to the DBIC C<create> method to add the requested
+information to the database (with a separate call to
+C<add_to_book_author> to update the join table). As do virtually all
+controller methods (at least the ones that directly handle user input),
+it then sets the template that should handle this request.
+
+
+=head2 Include a Template for the 'url_create' Action:
+
+Edit C<root/src/books/create_done.tt2> and then enter:
+
+ [% # Use the TT Dumper plugin to Data::Dumper variables to the browser -%]
+ [% # Not a good idea for production use, though. :-) 'Indent=1' is -%]
+ [% # optional, but prevents "massive indenting" of deeply nested objects -%]
+ [% USE Dumper(Indent=1) -%]
+
+ [% # Set the page title. META can 'go back' and set values in templates -%]
+ [% # that have been processed 'before' this template (here it's for -%]
+ [% # root/lib/site/html and root/lib/site/header). Note that META only -%]
+ [% # works on simple/static strings (i.e. there is no variable -%]
+ [% # interpolation). -%]
+ [% META title = 'Book Created' %]
+
+ [% # Output information about the record that was added. First title. -%]
+ <p>Added book '[% book.title %]'
+
+ [% # Output the last name of the first author. This is complicated by an -%]
+ [% # issue in TT 2.15 where blessed hash objects are not handled right. -%]
+ [% # First, fetch 'book.author' from the DB once. -%]
+ [% authors = book.author %]
+ [% # Now use IF statements to test if 'authors.first' is "working". If so, -%]
+ [% # we use it. Otherwise we use a hack that seems to keep TT 2.15 happy. -%]
+ by '[% authors.first.last_name IF authors.first;
+ authors.list.first.value.last_name IF ! authors.first %]'
+
+ [% # Output the rating for the book that was added -%]
+ with a rating of [% book.rating %].</p>
+
+ [% # Provide a link back to the list page -%]
+ [% # 'uri_for()' builds a full URI; e.g., 'http://localhost:3000/books/list' -%]
+ <p><a href="[% c.uri_for('/books/list') %]">Return to list</a></p>
+
+ [% # Try out the TT Dumper (for development only!) -%]
+ <pre>
+ Dump of the 'book' variable:
+ [% Dumper.dump(book) %]
+ </pre>
+
+The TT C<USE> directive allows access to a variety of plugin modules
+(TT plugins, that is, not Catalyst plugins) to add extra functionality
+to the base TT capabilities. Here, the plugin allows
+L<Data::Dumper|Data::Dumper> "pretty printing" of objects and
+variables. Other than that, the rest of the code should be familiar
+from the examples in Chapter 3.
+
+
+=head2 Try the 'url_create' Feature
+
+If the application is still running from before, use C<Ctrl-C> to kill
+it. Then restart the server:
+
+ $ DBIC_TRACE=1 script/myapp_server.pl
+
+Note that new path for C</books/url_create> appears in the startup debug
+output.
+
+B<TIP>: You can use C<script/myapp_server.pl -r> to have the development
+server auto-detect changed files and reload itself (if your browser acts
+odd, you should also try throwing in a C<-k>). If you make changes to
+the TT templates only, you do not need to reload the development server
+(only changes to "compiled code" such as Controller and Model C<.pm>
+files require a reload).
+
+Next, use your browser to enter the following URL:
+
+ http://localhost:3000/books/url_create/TCPIP_Illustrated_Vol-2/5/4
+
+Your browser should display "Added book 'TCPIP_Illustrated_Vol-2' by
+'Stevens' with a rating of 5." along with a dump of the new book model
+object as it was returned by DBIC. You should also see the following
+DBIC debug messages displayed in the development server log messages
+if you have DBIC_TRACE set:
+
+ INSERT INTO book (rating, title) VALUES (?, ?): `5', `TCPIP_Illustrated_Vol-2'
+ INSERT INTO book_author (author_id, book_id) VALUES (?, ?): `4', `6'
+
+The C<INSERT> statements are obviously adding the book and linking it to
+the existing record for Richard Stevens. The C<SELECT> statement results
+from DBIC automatically fetching the book for the C<Dumper.dump(book)>.
+
+If you then click the "Return to list" link, you should find that
+there are now six books shown (if necessary, Shift+Reload or
+Ctrl+Reload your browser at the C</books/list> page). You should now see
+the following six DBIC debug messages displayed for N=1-6:
+
+ SELECT author.id, author.first_name, author.last_name \
+ FROM book_author me JOIN author author \
+ ON ( author.id = me.author_id ) WHERE ( me.book_id = ? ): 'N'
+
+
+=head1 CONVERT TO A CHAINED ACTION
+
+Although the example above uses the same C<Local> action type for the
+method that we saw in the previous chapter of the tutorial, there is an
+alternate approach that allows us to be more specific while also
+paving the way for more advanced capabilities. Change the method
+declaration for C<url_create> in C<lib/MyApp/Controller/Books.pm> you
+entered above to match the following:
+
+ sub url_create :Chained('/') :PathPart('books/url_create') :Args(3) {
+
+This converts the method to take advantage of the Chained
+action/dispatch type. Chaining lets you have a single URL
+automatically dispatch to several controller methods, each of which
+can have precise control over the number of arguments that it will
+receive. A chain can essentially be thought of having three parts --
+a beginning, a middle, and an end. The bullets below summarize the key
+points behind each of these parts of a chain:
+
+
+=over 4
+
+
+=item *
+
+Beginning
+
+=over 4
+
+=item *
+
+B<Use "C<:Chained('/')>" to start a chain>
+
+=item *
+
+Get arguments through C<CaptureArgs()>
+
+=item *
+
+Specify the path to match with C<PathPart()>
+
+=back
+
+
+=item *
+
+Middle
+
+=over 4
+
+=item *
+
+Link to previous part of the chain with C<:Chained('_name_')>
+
+=item *
+
+Get arguments through C<CaptureArgs()>
+
+=item *
+
+Specify the path to match with C<PathPart()>
+
+=back
+
+
+=item *
+
+End
+
+=over 4
+
+=item *
+
+Link to previous part of the chain with C<:Chained('_name_')>
+
+=item *
+
+B<Do NOT get arguments through "C<CaptureArgs()>," use "C<Args()>" instead to end a chain>
+
+=item *
+
+Specify the path to match with C<PathPart()>
+
+=back
+
+
+=back
+
+In our C<url_create> method above, we have combined all three parts into
+a single method: C<:Chained('/')> to start the chain,
+C<:PathPart('books/url_create')> to specify the base URL to match, and
+C<:Args(3)> to capture exactly three arguments and to end the chain.
+
+As we will see shortly, a chain can consist of as many "links" as you
+wish, with each part capturing some arguments and doing some work
+along the way. We will continue to use the Chained action type in this
+chapter of the tutorial and explore slightly more advanced capabilities
+with the base method and delete feature below. But Chained dispatch
+is capable of far more. For additional information, see
+L<Catalyst::Manual::Intro/Action types>,
+L<Catalyst::DispatchType::Chained|Catalyst::DispatchType::Chained>,
+and the 2006 Advent calendar entry on the subject:
+L<http://www.catalystframework.org/calendar/2006/10>.
+
+
+=head2 Try the Chained Action
+
+If you look back at the development server startup logs from your
+initial version of the C<url_create> method (the one using the
+C<:Local> attribute), you will notice that it produced output similar
+to the following:
+
+ [debug] Loaded Path actions:
+ .-------------------------------------+--------------------------------------.
+ | Path | Private |
+ +-------------------------------------+--------------------------------------+
+ | / | /default |
+ | / | /index |
+ | /books | /books/index |
+ | /books/list | /books/list |
+ | /books/url_create | /books/url_create |
+ '-------------------------------------+--------------------------------------'
+
+Now start the development server with our basic chained method in
+place and the startup debug output should change to something along
+the lines of the following:
+
+ [debug] Loaded Path actions:
+ .-------------------------------------+--------------------------------------.
+ | Path | Private |
+ +-------------------------------------+--------------------------------------+
+ | / | /default |
+ | / | /index |
+ | /books | /books/index |
+ | /books/list | /books/list |
+ '-------------------------------------+--------------------------------------'
+
+ [debug] Loaded Chained actions:
+ .-------------------------------------+--------------------------------------.
+ | Path Spec | Private |
+ +-------------------------------------+--------------------------------------+
+ | /books/url_create/*/*/* | /books/url_create |
+ '-------------------------------------+--------------------------------------'
+
+C<url_create> has disappeared form the "Loaded Path actions" section
+but it now shows up under the newly created "Loaded Chained actions"
+section. And the "/*/*/*" portion clearly shows our requirement for
+three arguments.
+
+As with our non-chained version of C<url_create>, use your browser to
+enter the following URL:
+
+ http://localhost:3000/books/url_create/TCPIP_Illustrated_Vol-2/5/4
+
+You should see the same "Added book 'TCPIP_Illustrated_Vol-2' by
+'Stevens' with a rating of 5." along with a dump of the new book model
+object. Click the "Return to list" link, and you should find that there
+are now seven books shown (two copies of I<TCPIP_Illustrated_Vol-2>).
+
+
+=head2 Refactor to Use a 'base' Method to Start the Chains
+
+Let's make a quick update to our initial Chained action to show a
+little more of the power of chaining. First, open
+C<lib/MyApp/Controller/Books.pm> in your editor and add the following
+method:
+
+ =head2 base
+
+ Can place common logic to start chained dispatch here
+
+ =cut
+
+ sub base :Chained('/') :PathPart('books') :CaptureArgs(0) {
+ my ($self, $c) = @_;
+
+ # Store the ResultSet in stash so it's available for other methods
+ $c->stash->{resultset} = $c->model('DB::Book');
+
+ # Print a message to the debug log
+ $c->log->debug('*** INSIDE BASE METHOD ***');
+ }
+
+Here we print a log message and store the DBIC ResultSet in
+C<$c-E<gt>stash-E<gt>{resultset}> so that it's automatically available
+for other actions that chain off C<base>. If your controller always
+needs a book ID as its first argument, you could have the base method
+capture that argument (with C<:CaptureArgs(1)>) and use it to pull the
+book object with C<-E<gt>find($id)> and leave it in the stash for
+later parts of your chains to then act upon. Because we have several
+actions that don't need to retrieve a book (such as the C<url_create>
+we are working with now), we will instead add that functionality
+to a common C<object> action shortly.
+
+As for C<url_create>, let's modify it to first dispatch to C<base>.
+Open up C<lib/MyApp/Controller/Books.pm> and edit the declaration for
+C<url_create> to match the following:
+
+ sub url_create :Chained('base') :PathPart('url_create') :Args(3) {
+
+Next, try out the refactored chain by restarting the development
+server. Notice that our "Loaded Chained actions" section has changed
+slightly:
+
+ [debug] Loaded Chained actions:
+ .-------------------------------------+--------------------------------------.
+ | Path Spec | Private |
+ +-------------------------------------+--------------------------------------+
+ | /books/url_create/*/*/* | /books/base (0) |
+ | | => /books/url_create |
+ '-------------------------------------+--------------------------------------'
+
+The "Path Spec" is the same, but now it maps to two Private actions as
+we would expect.
+
+Once again, enter the following URL into your browser:
+
+ http://localhost:3000/books/url_create/TCPIP_Illustrated_Vol-2/5/4
+
+The same "Added book 'TCPIP_Illustrated_Vol-2' by 'Stevens' with a
+rating of 5." message and a dump of the new book object should appear.
+Also notice the extra debug message in the development server output
+from the C<base> method. Click the "Return to list" link, and you
+should find that there are now eight books shown.
+
+
+=head1 MANUALLY BUILDING A CREATE FORM
+
+Although the C<url_create> action in the previous step does begin to
+reveal the power and flexibility of both Catalyst and DBIC, it's
+obviously not a very realistic example of how users should be expected
+to enter data. This section begins to address that concern.
+
+
+=head2 Add Method to Display The Form
+
+Edit C<lib/MyApp/Controller/Books.pm> and add the following method:
+
+ =head2 form_create
+
+ Display form to collect information for book to create
+
+ =cut
+
+ sub form_create :Chained('base') :PathPart('form_create') :Args(0) {
+ my ($self, $c) = @_;
+
+ # Set the TT template to use
+ $c->stash->{template} = 'books/form_create.tt2';
+ }
+
+This action simply invokes a view containing a form to create a book.
+
+
+=head2 Add a Template for the Form
+
+Open C<root/src/books/form_create.tt2> in your editor and enter:
+
+ [% META title = 'Manual Form Book Create' -%]
+
+ <form method="post" action="[% c.uri_for('form_create_do') %]">
+ <table>
+ <tr><td>Title:</td><td><input type="text" name="title"></td></tr>
+ <tr><td>Rating:</td><td><input type="text" name="rating"></td></tr>
+ <tr><td>Author ID:</td><td><input type="text" name="author_id"></td></tr>
+ </table>
+ <input type="submit" name="Submit" value="Submit">
+ </form>
+
+Note that we have specified the target of the form data as
+C<form_create_do>, the method created in the section that follows.
+
+
+=head2 Add a Method to Process Form Values and Update Database
+
+Edit C<lib/MyApp/Controller/Books.pm> and add the following method to
+save the form information to the database:
+
+ =head2 form_create_do
+
+ Take information from form and add to database
+
+ =cut
+
+ sub form_create_do :Chained('base') :PathPart('form_create_do') :Args(0) {
+ my ($self, $c) = @_;
+
+ # Retrieve the values from the form
+ my $title = $c->request->params->{title} || 'N/A';
+ my $rating = $c->request->params->{rating} || 'N/A';
+ my $author_id = $c->request->params->{author_id} || '1';
+
+ # Create the book
+ my $book = $c->model('DB::Book')->create({
+ title => $title,
+ rating => $rating,
+ });
+ # Handle relationship with author
+ $book->add_to_book_author({author_id => $author_id});
+
+ # Store new model object in stash
+ $c->stash->{book} = $book;
+
+ # Avoid Data::Dumper issue mentioned earlier
+ # You can probably omit this
+ $Data::Dumper::Useperl = 1;
+
+ # Set the TT template to use
+ $c->stash->{template} = 'books/create_done.tt2';
+ }
+
+
+=head2 Test Out The Form
+
+If the application is still running from before, use C<Ctrl-C> to kill
+it. Then restart the server:
+
+ $ script/myapp_server.pl
+
+Notice that the server startup log reflects the two new chained
+methods that we added:
+
+ [debug] Loaded Chained actions:
+ .-------------------------------------+--------------------------------------.
+ | Path Spec | Private |
+ +-------------------------------------+--------------------------------------+
+ | /books/form_create | /books/base (0) |
+ | | => /books/form_create |
+ | /books/form_create_do | /books/base (0) |
+ | | => /books/form_create_do |
+ | /books/url_create/*/*/* | /books/base (0) |
+ | | => /books/url_create |
+ '-------------------------------------+--------------------------------------'
+
+Point your browser to L<http://localhost:3000/books/form_create> and
+enter "TCP/IP Illustrated, Vol 3" for the title, a rating of 5, and an
+author ID of 4. You should then see the output of the same
+C<create_done.tt2> template seen in earlier examples. Finally, click
+"Return to list" to view the full list of books.
+
+B<Note:> Having the user enter the primary key ID for the author is
+obviously crude; we will address this concern with a drop-down list in
+Chapter 9.
+
+
+=head1 A SIMPLE DELETE FEATURE
+
+Turning our attention to the Delete portion of CRUD, this section
+illustrates some basic techniques that can be used to remove information
+from the database.
+
+
+=head2 Include a Delete Link in the List
+
+Edit C<root/src/books/list.tt2> and update it to match the following (two
+sections have changed: 1) the additional '<th>Links</th>' table header,
+and 2) the four lines for the Delete link near the bottom):
+
+ [% # This is a TT comment. The '-' at the end "chomps" the newline. You won't -%]
+ [% # see this "chomping" in your browser because HTML ignores blank lines, but -%]
+ [% # it WILL eliminate a blank line if you view the HTML source. It's purely -%]
+ [%- # optional, but both the beginning and the ending TT tags support chomping. -%]
+
+ [% # Provide a title -%]
+ [% META title = 'Book List' -%]
+
+ <table>
+ <tr><th>Title</th><th>Rating</th><th>Author(s)</th><th>Links</th></tr>
+ [% # Display each book in a table row %]
+ [% FOREACH book IN books -%]
+ <tr>
+ <td>[% book.title %]</td>
+ <td>[% book.rating %]</td>
+ <td>
+ [% # First initialize a TT variable to hold a list. Then use a TT FOREACH -%]
+ [% # loop in 'side effect notation' to load just the last names of the -%]
+ [% # authors into the list. Note that the 'push' TT vmethod doesn't return -%]
+ [% # a value, so nothing will be printed here. But, if you have something -%]
+ [% # in TT that does return a value and you don't want it printed, you can -%]
+ [% # 1) assign it to a bogus value, or # 2) use the CALL keyword to -%]
+ [% # call it and discard the return value. -%]
+ [% tt_authors = [ ];
+ tt_authors.push(author.last_name) FOREACH author = book.author %]
+ [% # Now use a TT 'virtual method' to display the author count in parens -%]
+ [% # Note the use of the TT filter "| html" to escape dangerous characters -%]
+ ([% tt_authors.size | html %])
+ [% # Use another TT vmethod to join & print the names & comma separators -%]
+ [% tt_authors.join(', ') | html %]
+ </td>
+ <td>
+ [% # Add a link to delete a book %]
+ <a href="[% c.uri_for(c.controller.action_for('delete'), [book.id]) %]">Delete</a>
+ </td>
+ </tr>
+ [% END -%]
+ </table>
+
+The additional code is obviously designed to add a new column to the
+right side of the table with a C<Delete> "button" (for simplicity, links
+will be used instead of full HTML buttons; in practice, anything that
+modifies data should be handled with a form sending a PUT request).
+
+Also notice that we are using a more advanced form of C<uri_for> than
+we have seen before. Here we use
+C<$c-E<gt>controller-E<gt>action_for> to automatically generate a URI
+appropriate for that action based on the method we want to link to
+while inserting the C<book.id> value into the appropriate place. Now,
+if you ever change C<:PathPart('delete')> in your controller method to
+C<:PathPart('kill')>, then your links will automatically update
+without any changes to your .tt2 template file. As long as the name
+of your method does not change (here, "delete"), then your links will
+still be correct. There are a few shortcuts and options when using
+C<action_for()>:
+
+=over 4
+
+=item *
+
+If you are referring to a method in the current controller, you can
+use C<$self-E<gt>action_for('_method_name_')>.
+
+=item *
+
+If you are referring to a method in a different controller, you need
+to include that controller's name as an argument to C<controller()>, as in
+C<$c-E<gt>controller('_controller_name_')-E<gt>action_for('_method_name_')>.
+
+=back
+
+B<Note:> In practice you should B<never> use a GET request to delete a
+record -- always use POST for actions that will modify data. We are
+doing it here for illustrative and simplicity purposes only.
+
+
+=head2 Add a Common Method to Retrieve a Book for the Chain
+
+As mentioned earlier, since we have a mixture of actions that operate
+on a single book ID and others that do not, we should not have C<base>
+capture the book ID, find the corresponding book in the database and
+save it in the stash for later links in the chain. However, just
+because that logic does not belong in C<base> doesn't mean that we
+can't create another location to centralize the book lookup code. In
+our case, we will create a method called C<object> that will store the
+specific book in the stash. Chains that always operate on a single
+existing book can chain off this method, but methods such as
+C<url_create> that don't operate on an existing book can chain
+directly off base.
+
+To add the C<object> method, edit C<lib/MyApp/Controller/Books.pm>
+and add the following code:
+
+ =head2 object
+
+ Fetch the specified book object based on the book ID and store
+ it in the stash
+
+ =cut
+
+ sub object :Chained('base') :PathPart('id') :CaptureArgs(1) {
+ # $id = primary key of book to delete
+ my ($self, $c, $id) = @_;
+
+ # Find the book object and store it in the stash
+ $c->stash(object => $c->stash->{resultset}->find($id));
+
+ # Make sure the lookup was successful. You would probably
+ # want to do something like this in a real app:
+ # $c->detach('/error_404') if !$c->stash->{object};
+ die "Book $id not found!" if !$c->stash->{object};
+ }
+
+Now, any other method that chains off C<object> will automatically
+have the appropriate book waiting for it in
+C<$c-E<gt>stash-E<gt>{object}>.
+
+Also note that we are using a different technique for setting
+C<$c-E<gt>stash>. The advantage of this style is that it lets you set
+multiple stash variables at a time. For example:
+
+ $c->stash(object => $c->stash->{resultset}->find($id),
+ another_thing => 1);
+
+or as a hashref:
+
+ $c->stash({object => $c->stash->{resultset}->find($id),
+ another_thing => 1});
+
+Either format works, but the C<$c-E<gt>stash(name =E<gt> value);>
+style is growing in popularity -- you may wish to use it all
+the time (even when you are only setting a single value).
+
+
+=head2 Add a Delete Action to the Controller
+
+Open C<lib/MyApp/Controller/Books.pm> in your editor and add the
+following method:
+
+ =head2 delete
+
+ Delete a book
+
+ =cut
+
+ sub delete :Chained('object') :PathPart('delete') :Args(0) {
+ my ($self, $c) = @_;
+
+ # Use the book object saved by 'object' and delete it along
+ # with related 'book_author' entries
+ $c->stash->{object}->delete;
+
+ # Set a status message to be displayed at the top of the view
+ $c->stash->{status_msg} = "Book deleted.";
+
+ # Forward to the list action/method in this controller
+ $c->forward('list');
+ }
+
+This method first deletes the book object saved by the C<object> method.
+However, it also removes the corresponding entry from the
+C<book_author> table with a cascading delete.
+
+Then, rather than forwarding to a "delete done" page as we did with the
+earlier create example, it simply sets the C<status_msg> to display a
+notification to the user as the normal list view is rendered.
+
+The C<delete> action uses the context C<forward> method to return the
+user to the book list. The C<detach> method could have also been used.
+Whereas C<forward> I<returns> to the original action once it is
+completed, C<detach> does I<not> return. Other than that, the two are
+equivalent.
+
+
+=head2 Try the Delete Feature
+
+If the application is still running from before, use C<Ctrl-C> to kill
+it. Then restart the server:
+
+ $ DBIC_TRACE=1 script/myapp_server.pl
+
+The C<delete> method now appears in the "Loaded Chained actions" section
+of the startup debug output:
+
+ [debug] Loaded Chained actions:
+ .-------------------------------------+--------------------------------------.
+ | Path Spec | Private |
+ +-------------------------------------+--------------------------------------+
+ | /books/id/*/delete | /books/base (0) |
+ | | -> /books/object (1) |
+ | | => /books/delete |
+ | /books/form_create | /books/base (0) |
+ | | => /books/form_create |
+ | /books/form_create_do | /books/base (0) |
+ | | => /books/form_create_do |
+ | /books/url_create/*/*/* | /books/base (0) |
+ | | => /books/url_create |
+ '-------------------------------------+--------------------------------------'
+
+Then point your browser to L<http://localhost:3000/books/list> and click
+the "Delete" link next to the first "TCPIP_Illustrated_Vol-2". A green
+"Book deleted" status message should display at the top of the page,
+along with a list of the eight remaining books. You will also see the
+cascading delete operation via the DBIC_TRACE output:
+
+ SELECT me.id, me.title, me.rating FROM book me WHERE ( ( me.id = ? ) ): '6'
+ DELETE FROM book WHERE ( id = ? ): '6'
+ SELECT me.book_id, me.author_id FROM book_author me WHERE ( me.book_id = ? ): '6'
+ DELETE FROM book_author WHERE ( author_id = ? AND book_id = ? ): '4', '6'
+
+
+=head2 Fixing a Dangerous URL
+
+Note the URL in your browser once you have performed the deletion in the
+prior step -- it is still referencing the delete action:
+
+ http://localhost:3000/books/id/6/delete
+
+What if the user were to press reload with this URL still active? In
+this case the redundant delete is harmless (although it does generate
+an exception screen, it doesn't perform any undesirable actions on the
+application or database), but in other cases this could clearly be
+extremely dangerous.
+
+We can improve the logic by converting to a redirect. Unlike
+C<$c-E<gt>forward('list'))> or C<$c-E<gt>detach('list'))> that perform
+a server-side alteration in the flow of processing, a redirect is a
+client-side mechanism that causes the browser to issue an entirely
+new request. As a result, the URL in the browser is updated to match
+the destination of the redirection URL.
+
+To convert the forward used in the previous section to a redirect,
+open C<lib/MyApp/Controller/Books.pm> and edit the existing
+C<sub delete> method to match:
+
+ =head2 delete
+
+ Delete a book
+
+ =cut
+
+ sub delete :Chained('object') :PathPart('delete') :Args(0) {
+ my ($self, $c) = @_;
+
+ # Use the book object saved by 'object' and delete it along
+ # with related 'book_author' entries
+ $c->stash->{object}->delete;
+
+ # Set a status message to be displayed at the top of the view
+ $c->stash->{status_msg} = "Book deleted.";
+
+ # Redirect the user back to the list page. Note the use
+ # of $self->action_for as earlier in this section (BasicCRUD)
+ $c->response->redirect($c->uri_for($self->action_for('list')));
+ }
+
+
+=head2 Try the Delete and Redirect Logic
+
+Restart the development server and point your browser to
+L<http://localhost:3000/books/list> (don't just hit "Refresh" in your
+browser since we left the URL in an invalid state in the previous
+section!) and delete the first copy of the remaining two
+"TCPIP_Illustrated_Vol-2" books. The URL in your browser should return
+to the L<http://localhost:3000/books/list> URL, so that is an
+improvement, but notice that I<no green "Book deleted" status message is
+displayed>. Because the stash is reset on every request (and a redirect
+involves a second request), the C<status_msg> is cleared before it can
+be displayed.
+
+
+=head2 Using 'uri_for' to Pass Query Parameters
+
+There are several ways to pass information across a redirect. One
+option is to use the C<flash> technique that we will see in Chapter 5
+of this tutorial; however, here we will pass the information via query
+parameters on the redirect itself. Open
+C<lib/MyApp/Controller/Books.pm> and update the existing C<sub delete>
+method to match the following:
+
+ =head2 delete
+
+ Delete a book
+
+ =cut
+
+ sub delete :Chained('object') :PathPart('delete') :Args(0) {
+ my ($self, $c) = @_;
+
+ # Use the book object saved by 'object' and delete it along
+ # with related 'book_author' entries
+ $c->stash->{object}->delete;
+
+ # Redirect the user back to the list page with status msg as an arg
+ $c->response->redirect($c->uri_for($self->action_for('list'),
+ {status_msg => "Book deleted."}));
+ }
+
+This modification simply leverages the ability of C<uri_for> to include
+an arbitrary number of name/value pairs in a hash reference. Next, we
+need to update C<root/src/wrapper.tt2> to handle C<status_msg> as a
+query parameter:
+
+ ...
+ <div id="content">
+ [%# Status and error messages %]
+ <span class="message">[% status_msg || c.request.params.status_msg %]</span>
+ <span class="error">[% error_msg %]</span>
+ [%# This is where TT will stick all of your template's contents. -%]
+ [% content %]
+ </div><!-- end content -->
+ ...
+
+Although the sample above only shows the C<content> div, leave the
+rest of the file intact -- the only change we made to the C<wrapper.tt2>
+was to add "C<|| c.request.params.status_msg>" to the
+C<E<lt>span class="message"E<gt>> line.
+
+
+=head2 Try the Delete and Redirect With Query Param Logic
+
+Restart the development server and point your browser to
+L<http://localhost:3000/books/list> (you should now be able to safely
+hit "refresh" in your browser). Then delete the remaining copy of
+"TCPIP_Illustrated_Vol-2". The green "Book deleted" status message
+should return.
+
+B<NOTE:> Another popular method for maintaining server-side
+information across a redirect is to use the C<flash> technique we
+discuss in the next chapter of the tutorial,
+L<Authentication|Catalyst::Manual::Tutorial::05_Authentication>. While
+C<flash> is a "slicker" mechanism in that it's all handled by the
+server and doesn't "pollute" your URLs, B<it is important to note that
+C<flash> can lead to situations where the wrong information shows up
+in the wrong browser window if the user has multiple windows or
+browser tabs open>. For example, Window A causes something to be
+placed in the stash, but before that window performs a redirect,
+Window B makes a request to the server and gets the status information
+that should really go to Window A. For this reason, you may wish
+to use the "query param" technique shown here in your applications.
+
+
+=head1 EXPLORING THE POWER OF DBIC
+
+In this section we will explore some additional capabilities offered
+by DBIx::Class. Although these features have relatively little to do
+with Catalyst per se, you will almost certainly want to take advantage
+of them in your applications.
+
+
+=head2 Add Datetime Columns to Our Existing Books Table
+
+Let's add two columns to our existing C<books> table to track when
+each book was added and when each book is updated:
+
+ $ sqlite3 myapp.db
+ sqlite> ALTER TABLE book ADD created INTEGER;
+ sqlite> ALTER TABLE book ADD updated INTEGER;
+ sqlite> UPDATE book SET created = DATETIME('NOW'), updated = DATETIME('NOW');
+ sqlite> SELECT * FROM book;
+ 1|CCSP SNRS Exam Certification Guide|5|2009-03-08 16:26:35|2009-03-08 16:26:35
+ 2|TCP/IP Illustrated, Volume 1|5|2009-03-08 16:26:35|2009-03-08 16:26:35
+ 3|Internetworking with TCP/IP Vol.1|4|2009-03-08 16:26:35|2009-03-08 16:26:35
+ 4|Perl Cookbook|5|2009-03-08 16:26:35|2009-03-08 16:26:35
+ 5|Designing with Web Standards|5|2009-03-08 16:26:35|2009-03-08 16:26:35
+ 9|TCP/IP Illustrated, Vol 3|5|2009-03-08 16:26:35|2009-03-08 16:26:35
+ sqlite> .quit
+ $
+
+This will modify the C<books> table to include the two new fields
+and populate those fields with the current time.
+
+
+=head2 Update DBIx::Class to Automatically Handle the Datetime Columns
+
+Next, we should re-run the DBIC helper to update the Result Classes
+with the new fields:
+
+ $ script/myapp_create.pl model DB DBIC::Schema MyApp::Schema \
+ create=static components=TimeStamp dbi:SQLite:myapp.db
+ exists "/root/dev/MyApp/script/../lib/MyApp/Model"
+ exists "/root/dev/MyApp/script/../t"
+ Dumping manual schema for MyApp::Schema to directory /root/dev/MyApp/script/../lib ...
+ Schema dump completed.
+ exists "/root/dev/MyApp/script/../lib/MyApp/Model/DB.pm"
+
+Notice that we modified our use of the helper slightly: we told
+it to include the L<DBIx::Class::TimeStamp|DBIx::Class::TimeStamp>
+in the C<load_components> line of the Result Classes.
+
+If you open C<lib/MyApp/Schema/Result/Book.pm> in your editor you
+should see that the C<created> and C<updated> fields are now included
+in the call to C<add_columns()>, but our relationship information below
+the "C<# DO NOT MODIFY...>" line was automatically preserved.
+
+While we have this file open, let's update it with some additional
+information to have DBIC automatically handle the updating of these
+two fields for us. Insert the following code at the bottom of the
+file (it B<must> be B<below> the "C<# DO NOT MODIFY...>" line and
+B<above> the C<1;> on the last line):
+
+ #
+ # Enable automatic date handling
+ #
+ __PACKAGE__->add_columns(
+ "created",
+ { data_type => 'datetime', set_on_create => 1 },
+ "updated",
+ { data_type => 'datetime', set_on_create => 1, set_on_update => 1 },
+ );
+
+This will override the definition for these fields that Schema::Loader
+placed at the top of the file. The C<set_on_create> and
+C<set_on_update> options will cause DBIx::Class to automatically
+update the timestamps in these columns whenever a row is created or
+modified.
+
+To test this out, restart the development server using the
+C<DBIC_TRACE=1> option:
+
+ DBIC_TRACE=1 script/myapp_server.pl
+
+Then enter the following URL into your web browser:
+
+ http://localhost:3000/books/url_create/TCPIP_Illustrated_Vol-2/5/4
+
+You should get the same "Book Created" screen we saw above. However,
+if you now use the sqlite3 command-line tool to dump the C<books> table,
+you will see that the new book we added has an appropriate date and
+time entered for it (see the last line in the listing below):
+
+ sqlite3 myapp.db "select * from book"
+ 1|CCSP SNRS Exam Certification Guide|5|2009-03-08 16:26:35|2009-03-08 16:26:35
+ 2|TCP/IP Illustrated, Volume 1|5|2009-03-08 16:26:35|2009-03-08 16:26:35
+ 3|Internetworking with TCP/IP Vol.1|4|2009-03-08 16:26:35|2009-03-08 16:26:35
+ 4|Perl Cookbook|5|2009-03-08 16:26:35|2009-03-08 16:26:35
+ 5|Designing with Web Standards|5|2009-03-08 16:26:35|2009-03-08 16:26:35
+ 9|TCP/IP Illustrated, Vol 3|5|2009-03-08 16:26:35|2009-03-08 16:26:35
+ 10|TCPIP_Illustrated_Vol-2|5|2009-03-08 16:29:08|2009-03-08 16:29:08
+
+Notice in the debug log that the SQL DBIC generated has changed to
+incorporate the datetime logic:
+
+ INSERT INTO book (created, rating, title, updated) VALUES (?, ?, ?, ?):
+ '2009-03-08 16:29:08', '5', 'TCPIP_Illustrated_Vol-2', '2009-03-08 16:29:08'
+ INSERT INTO book_author (author_id, book_id) VALUES (?, ?): '4', '10'
+
+
+=head2 Create a ResultSet Class
+
+An often overlooked but extremely powerful features of DBIC is that it
+allows you to supply your own subclasses of C<DBIx::Class::ResultSet>.
+It allows you to pull complex and unsightly "query code" out of your
+controllers and encapsulate it in a method of your ResultSet Class.
+These "canned queries" in your ResultSet Class can then be invoked
+via a single call, resulting in much cleaner and easier to read
+controller code.
+
+To illustrate the concept with a fairly simple example, let's create a
+method that returns books added in the last 10 minutes. Start by
+making a directory where DBIx::Class will look for our ResultSet Class:
+
+ mkdir lib/MyApp/Schema/ResultSet
+
+Then open C<lib/MyApp/Schema/ResultSet/Book.pm> and enter the following:
+
+ package MyApp::Schema::ResultSet::Book;
+
+ use strict;
+ use warnings;
+ use base 'DBIx::Class::ResultSet';
+
+ =head2 created_after
+
+ A predefined search for recently added books
+
+ =cut
+
+ sub created_after {
+ my ($self, $datetime) = @_;
+
+ my $date_str = $self->_source_handle->schema->storage
+ ->datetime_parser->format_datetime($datetime);
+
+ return $self->search({
+ created => { '>' => $date_str }
+ });
+ }
+
+ 1;
+
+Then we need to tell the Result Class to to treat this as a ResultSet
+Class. Open C<lib/MyApp/Schema/Result/Book.pm> and add the following
+above the "C<1;>" at the bottom of the file:
+
+ #
+ # Set ResultSet Class
+ #
+ __PACKAGE__->resultset_class('MyApp::Schema::ResultSet::Book');
+
+Then add the following method to the C<lib/MyApp/Controller/Books.pm>:
+
+ =head2 list_recent
+
+ List recently created books
+
+ =cut
+
+ sub list_recent :Chained('base') :PathPart('list_recent') :Args(1) {
+ my ($self, $c, $mins) = @_;
+
+ # Retrieve all of the book records as book model objects and store in the
+ # stash where they can be accessed by the TT template, but only
+ # retrieve books created within the last $min number of minutes
+ $c->stash->{books} = [$c->model('DB::Book')
+ ->created_after(DateTime->now->subtract(minutes => $mins))];
+
+ # Set the TT template to use. You will almost always want to do this
+ # in your action methods (action methods respond to user input in
+ # your controllers).
+ $c->stash->{template} = 'books/list.tt2';
+ }
+
+Now start the development server with C<DBIC_TRACE=1> and try
+different values for the minutes argument (the final number value) for
+the URL C<http://localhost:3000/books/list_recent/10>. For example,
+this would list all books added in the last fifteen minutes:
+
+ http://localhost:3000/books/list_recent/15
+
+Depending on how recently you added books, you might want to
+try a higher or lower value.
+
+
+=head2 Chaining ResultSets
+
+One of the most helpful and powerful features in DBIx::Class is that
+it allows you to "chain together" a series of queries (note that this
+has nothing to do with the "Chained Dispatch" for Catalyst that we
+were discussing above). Because each ResultSet returns another
+ResultSet, you can take an initial query and immediately feed that
+into a second query (and so on for as many queries you need). Note
+that no matter how many ResultSets you chain together, the database
+itself will not be hit until you use a method that attempts to access
+the data. And, because this technique carries over to the ResultSet
+Class feature we implemented in the previous section for our "canned
+search", we can combine the two capabilities. For example, let's add
+an action to our C<Books> controller that lists books that are both
+recent I<and> have "TCP" in the title. Open up
+C<lib/MyApp/Controller/Books.pm> and add the following method:
+
+ =head2 list_recent_tcp
+
+ List recently created books
+
+ =cut
+
+ sub list_recent_tcp :Chained('base') :PathPart('list_recent_tcp') :Args(1) {
+ my ($self, $c, $mins) = @_;
+
+ # Retrieve all of the book records as book model objects and store in the
+ # stash where they can be accessed by the TT template, but only
+ # retrieve books created within the last $min number of minutes
+ # AND that have 'TCP' in the title
+ $c->stash->{books} = [$c->model('DB::Book')
+ ->created_after(DateTime->now->subtract(minutes => $mins))
+ ->search({title => {'like', '%TCP%'}})
+ ];
+
+ # Set the TT template to use. You will almost always want to do this
+ # in your action methods (action methods respond to user input in
+ # your controllers).
+ $c->stash->{template} = 'books/list.tt2';
+ }
+
+To try this out, restart the development server with:
+
+ DBIC_TRACE=1 script/myapp_server.pl
+
+And enter the following URL into your browser:
+
+ http://localhost:3000/books/list_recent_tcp/100
+
+And you should get a list of books added in the last 100 minutes that
+contain the string "TCP" in the title. However, if you look at all
+books within the last 100 minutes, you should get a longer list
+(again, you might have to adjust the number of minutes depending on
+how recently you added books to your database):
+
+ http://localhost:3000/books/list_recent/100
+
+Take a look at the DBIC_TRACE output in the development server log for
+the first URL and you should see something similar to the following:
+
+ SELECT me.id, me.title, me.rating, me.created, me.updated FROM book me
+ WHERE ( ( ( title LIKE ? ) AND ( created > ? ) ) ): '%TCP%', '2009-03-08 14:52:54'
+
+However, let's not pollute our controller code with this raw "TCP"
+query -- it would be cleaner to encapsulate that code in a method on
+our ResultSet Class. To do this, open
+C<lib/MyApp/Schema/ResultSet/Book.pm> and add the following method:
+
+ =head2 title_like
+
+ A predefined search for books with a 'LIKE' search in the string
+
+ =cut
+
+ sub title_like {
+ my ($self, $title_str) = @_;
+
+ return $self->search({
+ title => { 'like' => "%$title_str%" }
+ });
+ }
+
+We defined the search string as C<$title_str> to make the method more
+flexible. Now update the C<list_recent_tcp> method in
+C<lib/MyApp/Controller/Books.pm> to match the following (we have
+replaced the C<-E<gt>search> line with the C<-E<gt>title_like> line
+shown here -- the rest of the method should be the same):
+
+ =head2 list_recent_tcp
+
+ List recently created books
+
+ =cut
+
+ sub list_recent_tcp :Chained('base') :PathPart('list_recent_tcp') :Args(1) {
+ my ($self, $c, $mins) = @_;
+
+ # Retrieve all of the book records as book model objects and store in the
+ # stash where they can be accessed by the TT template, but only
+ # retrieve books created within the last $min number of minutes
+ # AND that have 'TCP' in the title
+ $c->stash->{books} = [$c->model('DB::Book')
+ ->created_after(DateTime->now->subtract(minutes => $mins))
+ ->title_like('TCP')
+ ];
+
+ # Set the TT template to use. You will almost always want to do this
+ # in your action methods (action methods respond to user input in
+ # your controllers).
+ $c->stash->{template} = 'books/list.tt2';
+ }
+
+Then restart the development server and try out the C<list_recent_tcp>
+and C<list_recent> URL as we did above. It should work just the same,
+but our code is obviously cleaner and more modular, while also being
+more flexible at the same time.
+
+
+=head2 Adding Methods to Result Classes
+
+In the previous two sections we saw a good example of how we could use
+DBIx::Class ResultSet Classes to clean up our code for an entire query
+(for example, our "canned searches" that filtered the entire query).
+We can do a similar improvement when working with individual rows as
+well. Whereas the ResultSet construct is used in DBIC to correspond
+to an entire query, the Result Class construct is used to represent a
+row. Therefore, we can add row-specific "helper methods" to our Result
+Classes stored in C<lib/MyApp/Schema/Result/>. For example, open
+C<lib/MyApp/Schema/Result/Author.pm> and add the following method (as
+always, it must be above the closing "C<1;>"):
+
+ #
+ # Helper methods
+ #
+ sub full_name {
+ my ($self) = @_;
+
+ return $self->first_name . ' ' . $self->last_name;
+ }
+
+This will allow us to conveniently retrieve both the first and last
+name for an author in one shot. Now open C<root/src/books/list.tt2>
+and change the definition of C<tt_authors> from this:
+
+ ...
+ [% tt_authors = [ ];
+ tt_authors.push(author.last_name) FOREACH author = book.author %]
+ ...
+
+to:
+
+ ...
+ [% tt_authors = [ ];
+ tt_authors.push(author.full_name) FOREACH author = book.author %]
+ ...
+
+(Only C<author.last_name> was changed to C<author.full_name> -- the
+rest of the file should remain the same.)
+
+Now restart the development server and go to the standard book list
+URL:
+
+ http://localhost:3000/books/list
+
+The "Author(s)" column will now contain both the first and last name.
+And, because the concatenation logic was encapsulated inside our
+Result Class, it keeps the code inside our .tt template nice and clean
+(remember, we want the templates to be as close to pure HTML markup as
+possible). Obviously, this capability becomes even more useful as you
+use to to remove even more complicated row-specific logic from your
+templates!
+
+
+=head1 AUTHOR
+
+Kennedy Clark, C<hkclark at gmail.com>
+
+Please report any errors, issues or suggestions to the author. The
+most recent version of the Catalyst Tutorial can be found at
+L<http://dev.catalyst.perl.org/repos/Catalyst/Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/>.
+
+Copyright 2006-2008, Kennedy Clark, under Creative Commons License
+(L<http://creativecommons.org/licenses/by-sa/3.0/us/>).
Copied: Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/05_Authentication.pod (from rev 10275, Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/Authentication.pod)
===================================================================
--- Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/05_Authentication.pod (rev 0)
+++ Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/05_Authentication.pod 2009-05-24 22:30:47 UTC (rev 10277)
@@ -0,0 +1,916 @@
+=head1 NAME
+
+Catalyst::Manual::Tutorial::05_Authentication - Catalyst Tutorial - Chapter 5: Authentication
+
+
+=head1 OVERVIEW
+
+This is B<Chapter 5 of 10> for the Catalyst tutorial.
+
+L<Tutorial Overview|Catalyst::Manual::Tutorial>
+
+=over 4
+
+=item 1
+
+L<Introduction|Catalyst::Manual::Tutorial::01_Intro>
+
+=item 2
+
+L<Catalyst Basics|Catalyst::Manual::Tutorial::02_CatalystBasics>
+
+=item 3
+
+L<More Catalyst Basics|Catalyst::Manual::Tutorial::03_MoreCatalystBasics>
+
+=item 4
+
+L<Basic CRUD|Catalyst::Manual::Tutorial::04_BasicCRUD>
+
+=item 5
+
+B<05_Authentication>
+
+=item 6
+
+L<Authorization|Catalyst::Manual::Tutorial::06_Authorization>
+
+=item 7
+
+L<Debugging|Catalyst::Manual::Tutorial::07_Debugging>
+
+=item 8
+
+L<Testing|Catalyst::Manual::Tutorial::08_Testing>
+
+=item 9
+
+L<Advanced CRUD|Catalyst::Manual::Tutorial::09_AdvancedCRUD>
+
+=item 10
+
+L<Appendices|Catalyst::Manual::Tutorial::10_Appendices>
+
+=back
+
+
+=head1 DESCRIPTION
+
+Now that we finally have a simple yet functional application, we can
+focus on providing authentication (with authorization coming next in
+Chapter 6).
+
+This chapter of the tutorial is divided into two main sections: 1) basic,
+cleartext authentication and 2) hash-based authentication.
+
+You can checkout the source code for this example from the catalyst
+subversion repository as per the instructions in
+L<Catalyst::Manual::Tutorial::01_Intro|Catalyst::Manual::Tutorial::01_Intro>.
+
+
+=head1 BASIC AUTHENTICATION
+
+This section explores how to add authentication logic to a Catalyst
+application.
+
+
+=head2 Add Users and Roles to the Database
+
+First, we add both user and role information to the database (we will
+add the role information here although it will not be used until the
+authorization section, Chapter 6). Create a new SQL script file by opening
+C<myapp02.sql> in your editor and insert:
+
+ --
+ -- Add user and role tables, along with a many-to-many join table
+ --
+ CREATE TABLE user (
+ id INTEGER PRIMARY KEY,
+ username TEXT,
+ password TEXT,
+ email_address TEXT,
+ first_name TEXT,
+ last_name TEXT,
+ active INTEGER
+ );
+ CREATE TABLE role (
+ id INTEGER PRIMARY KEY,
+ role TEXT
+ );
+ CREATE TABLE user_role (
+ user_id INTEGER,
+ role_id INTEGER,
+ PRIMARY KEY (user_id, role_id)
+ );
+ --
+ -- Load up some initial test data
+ --
+ INSERT INTO user VALUES (1, 'test01', 'mypass', 't01 at na.com', 'Joe', 'Blow', 1);
+ INSERT INTO user VALUES (2, 'test02', 'mypass', 't02 at na.com', 'Jane', 'Doe', 1);
+ INSERT INTO user VALUES (3, 'test03', 'mypass', 't03 at na.com', 'No', 'Go', 0);
+ INSERT INTO role VALUES (1, 'user');
+ INSERT INTO role VALUES (2, 'admin');
+ INSERT INTO user_role VALUES (1, 1);
+ INSERT INTO user_role VALUES (1, 2);
+ INSERT INTO user_role VALUES (2, 1);
+ INSERT INTO user_role VALUES (3, 1);
+
+Then load this into the C<myapp.db> database with the following command:
+
+ $ sqlite3 myapp.db < myapp02.sql
+
+=head2 Add User and Role Information to DBIC Schema
+
+Although we could manually edit the DBIC schema information to include
+the new tables added in the previous step, let's use the C<create=static>
+option on the DBIC model helper to do most of the work for us:
+
+ $ script/myapp_create.pl model DB DBIC::Schema MyApp::Schema \
+ create=static components=TimeStamp dbi:SQLite:myapp.db
+ exists "/root/dev/MyApp/script/../lib/MyApp/Model"
+ exists "/root/dev/MyApp/script/../t"
+ Dumping manual schema for MyApp::Schema to directory /root/dev/MyApp/script/../lib ...
+ Schema dump completed.
+ exists "/root/dev/MyApp/script/../lib/MyApp/Model/DB.pm"
+ $
+ $ ls lib/MyApp/Schema/Result
+ Author.pm BookAuthor.pm Book.pm Role.pm User.pm UserRole.pm
+
+Notice how the helper has added three new table-specific result source
+files to the C<lib/MyApp/Schema/Result> directory. And, more
+importantly, even if there were changes to the existing result source
+files, those changes would have only been written above the C<# DO NOT
+MODIFY THIS OR ANYTHING ABOVE!> comment and your hand-edited
+enhancements would have been preserved.
+
+Speaking of "hand-editted enhancements," we should now add
+relationship information to the three new result source files. Edit
+each of these files and add the following information between the C<#
+DO NOT MODIFY THIS OR ANYTHING ABOVE!> comment and the closing C<1;>:
+
+C<lib/MyApp/Schema/Result/User.pm>:
+
+ #
+ # Set relationships:
+ #
+
+ # has_many():
+ # args:
+ # 1) Name of relationship, DBIC will create accessor with this name
+ # 2) Name of the model class referenced by this relationship
+ # 3) Column name in *foreign* table (aka, foreign key in peer table)
+ __PACKAGE__->has_many(map_user_role => 'MyApp::Schema::Result::UserRole', 'user_id');
+
+ # many_to_many():
+ # args:
+ # 1) Name of relationship, DBIC will create accessor with this name
+ # 2) Name of has_many() relationship this many_to_many() is shortcut for
+ # 3) Name of belongs_to() relationship in model class of has_many() above
+ # You must already have the has_many() defined to use a many_to_many().
+ __PACKAGE__->many_to_many(roles => 'map_user_role', 'role');
+
+
+C<lib/MyApp/Schema/Result/Role.pm>:
+
+ #
+ # Set relationships:
+ #
+
+ # has_many():
+ # args:
+ # 1) Name of relationship, DBIC will create accessor with this name
+ # 2) Name of the model class referenced by this relationship
+ # 3) Column name in *foreign* table (aka, foreign key in peer table)
+ __PACKAGE__->has_many(map_user_role => 'MyApp::Schema::Result::UserRole', 'role_id');
+
+
+C<lib/MyApp/Schema/Result/UserRole.pm>:
+
+ #
+ # Set relationships:
+ #
+
+ # belongs_to():
+ # args:
+ # 1) Name of relationship, DBIC will create accessor with this name
+ # 2) Name of the model class referenced by this relationship
+ # 3) Column name in *this* table
+ __PACKAGE__->belongs_to(user => 'MyApp::Schema::Result::User', 'user_id');
+
+ # belongs_to():
+ # args:
+ # 1) Name of relationship, DBIC will create accessor with this name
+ # 2) Name of the model class referenced by this relationship
+ # 3) Column name in *this* table
+ __PACKAGE__->belongs_to(role => 'MyApp::Schema::Result::Role', 'role_id');
+
+The code for these three sets of updates is obviously very similar to
+the edits we made to the C<Book>, C<Author>, and C<BookAuthor>
+classes created in Chapter 3.
+
+Note that we do not need to make any change to the
+C<lib/MyApp/Schema.pm> schema file. It simply tells DBIC to load all
+of the Result Class and ResultSet Class files it finds in below the
+C<lib/MyApp/Schema> directory, so it will automatically pick up our
+new table information.
+
+
+=head2 Sanity-Check Reload of Development Server
+
+We aren't ready to try out the authentication just yet; we only want
+to do a quick check to be sure our model loads correctly. Press
+C<Ctrl-C> to kill the previous server instance (if it's still running)
+and restart it:
+
+ $ script/myapp_server.pl
+
+Look for the three new model objects in the startup debug output:
+
+ ...
+ .-------------------------------------------------------------------+----------.
+ | Class | Type |
+ +-------------------------------------------------------------------+----------+
+ | MyApp::Controller::Books | instance |
+ | MyApp::Controller::Root | instance |
+ | MyApp::Model::DB | instance |
+ | MyApp::Model::DB::Author | class |
+ | MyApp::Model::DB::Book | class |
+ | MyApp::Model::DB::BookAuthor | class |
+ | MyApp::Model::DB::Role | class |
+ | MyApp::Model::DB::User | class |
+ | MyApp::Model::DB::UserRole | class |
+ | MyApp::View::TT | instance |
+ '-------------------------------------------------------------------+----------'
+ ...
+
+Again, notice that your "Result Class" classes have been "re-loaded"
+by Catalyst under C<MyApp::Model>.
+
+
+=head2 Include Authentication and Session Plugins
+
+Edit C<lib/MyApp.pm> and update it as follows (everything below
+C<StackTrace> is new):
+
+ # Load plugins
+ use Catalyst qw/-Debug
+ ConfigLoader
+ Static::Simple
+
+ StackTrace
+
+ Authentication
+
+ Session
+ Session::Store::FastMmap
+ Session::State::Cookie
+ /;
+
+B<Note:> As discussed in MoreCatalystBasics, different versions of
+C<Catalyst::Devel> have used a variety of methods to load the plugins.
+You can put the plugins in the C<use Catalyst> statement if you prefer.
+
+The C<Authentication> plugin supports Authentication while the
+C<Session> plugins are required to maintain state across multiple HTTP
+requests.
+
+Note that the only required Authentication class is the main one. This
+is a change that occurred in version 0.09999_01 of the
+C<Authentication> plugin. You B<do not need> to specify a particular
+Authentication::Store or Authentication::Credential plugin. Instead,
+indicate the Store and Credential you want to use in your application
+configuration (see below).
+
+Make sure you include the additional plugins as new dependencies in
+the Makefile.PL file something like this:
+
+ requires (
+ 'Catalyst::Plugin::Authentication' => '0',
+ 'Catalyst::Plugin::Session' => '0',
+ 'Catalyst::Plugin::Session::Store::FastMmap' => '0',
+ 'Catalyst::Plugin::Session::State::Cookie' => '0',
+ );
+
+Note that there are several options for
+L<Session::Store|Catalyst::Plugin::Session::Store>
+(L<Session::Store::FastMmap|Catalyst::Plugin::Session::Store::FastMmap>
+is generally a good choice if you are on Unix; try
+L<Session::Store::File|Catalyst::Plugin::Session::Store::File> if you
+are on Win32) -- consult
+L<Session::Store|Catalyst::Plugin::Session::Store> and its subclasses
+for additional information and options (for example to use a database-
+backed session store).
+
+
+=head2 Configure Authentication
+
+There are a variety of ways to provide configuration information to
+L<Catalyst::Plugin::Authentication|Catalyst::Plugin::Authentication>.
+Here we will use
+L<Catalyst::Authentication::Realm::SimpleDB|Catalyst::Authentication::Realm::SimpleDB>
+because it automatically sets a reasonable set of defaults for us. Open
+C<lib/MyApp.pm> and place the following text above the call to
+C<__PACKAGE__-E<gt>setup();>:
+
+ # Configure SimpleDB Authentication
+ __PACKAGE__->config->{'Plugin::Authentication'} = {
+ default => {
+ class => 'SimpleDB',
+ user_model => 'DB::User',
+ password_type => 'clear',
+ },
+ };
+
+We could have placed this configuration in C<myapp.conf>, but placing
+it in C<lib/MyApp.pm> is probably a better place since it's not likely
+something that users of your application will want to change during
+deployment (or you could use a mixture: leave C<class> and
+C<user_model> defined in C<lib/MyApp.pm> as we show above, but place
+C<password_type> in C<myapp.conf> to allow the type of password to be
+easily modified during deployment). We will stick with putting
+all of the authentication-related configuration in C<lib/MyApp.pm>
+for the tutorial, but if you wish to use C<myapp.conf>, just convert
+to the following code:
+
+ <Plugin::Authentication>
+ use_session 1
+ <default>
+ password_type self_check
+ user_model DB::User
+ class SimpleDB
+ </default>
+ </Plugin::Authentication>
+
+B<TIP:> Here is a short script that will dump the contents of
+C<MyApp->config> to L<Config::General|Config::General> format in
+C<myapp.conf>:
+
+ $ perl -Ilib -e 'use MyApp; use Config::General;
+ Config::General->new->save_file("myapp.conf", MyApp->config);'
+
+B<NOTE:> Because we are using SimpleDB along with a database layout
+that complies with its default assumptions, we don't need to specify
+the names of the columns where our username and password information
+is stored (hence, the "Simple" part of "SimpleDB"). That being said,
+SimpleDB lets you specify that type of information if you need to.
+Take a look at
+C<Catalyst::Authentication::Realm::SimpleDB|Catalyst::Authentication::Realm::SimpleDB>
+for details.
+
+
+=head2 Add Login and Logout Controllers
+
+Use the Catalyst create script to create two stub controller files:
+
+ $ script/myapp_create.pl controller Login
+ $ script/myapp_create.pl controller Logout
+
+You could easily use a single controller here. For example, you could
+have a C<User> controller with both C<login> and C<logout> actions.
+Remember, Catalyst is designed to be very flexible, and leaves such
+matters up to you, the designer and programmer.
+
+Then open C<lib/MyApp/Controller/Login.pm>, locate the
+C<sub index :Path :Args(0)> method (or C<sub index : Private> if you
+are using an older version of Catalyst) that was automatically
+inserted by the helpers when we created the Login controller above,
+and update the definition of C<sub index> to match:
+
+ =head2 index
+
+ Login logic
+
+ =cut
+
+ sub index :Path :Args(0) {
+ my ($self, $c) = @_;
+
+ # Get the username and password from form
+ my $username = $c->request->params->{username} || "";
+ my $password = $c->request->params->{password} || "";
+
+ # If the username and password values were found in form
+ if ($username && $password) {
+ # Attempt to log the user in
+ if ($c->authenticate({ username => $username,
+ password => $password } )) {
+ # If successful, then let them use the application
+ $c->response->redirect($c->uri_for(
+ $c->controller('Books')->action_for('list')));
+ return;
+ } else {
+ # Set an error message
+ $c->stash->{error_msg} = "Bad username or password.";
+ }
+ }
+
+ # If either of above don't work out, send to the login page
+ $c->stash->{template} = 'login.tt2';
+ }
+
+Be sure to remove the C<$c-E<gt>response-E<gt>body('Matched MyApp::Controller::Login in Login.');>
+line of the C<sub index>.
+
+This controller fetches the C<username> and C<password> values from the
+login form and attempts to authenticate the user. If successful, it
+redirects the user to the book list page. If the login fails, the user
+will stay at the login page and receive an error message. If the
+C<username> and C<password> values are not present in the form, the
+user will be taken to the empty login form.
+
+Note that we could have used something like "C<sub default :Path>",
+however, it is generally recommended (partly for historical reasons,
+and partly for code clarity) only to use C<default> in
+C<MyApp::Controller::Root>, and then mainly to generate the 404 not
+found page for the application.
+
+Instead, we are using "C<sub somename :Path :Args(0) {...}>" here to
+specifically match the URL C</login>. C<Path> actions (aka, "literal
+actions") create URI matches relative to the namespace of the
+controller where they are defined. Although C<Path> supports
+arguments that allow relative and absolute paths to be defined, here
+we use an empty C<Path> definition to match on just the name of the
+controller itself. The method name, C<index>, is arbitrary. We make
+the match even more specific with the C<:Args(0)> action modifier --
+this forces the match on I<only> C</login>, not
+C</login/somethingelse>.
+
+Next, update the corresponding method in
+C<lib/MyApp/Controller/Logout.pm> to match:
+
+ =head2 index
+
+ Logout logic
+
+ =cut
+
+ sub index :Path :Args(0) {
+ my ($self, $c) = @_;
+
+ # Clear the user's state
+ $c->logout;
+
+ # Send the user to the starting point
+ $c->response->redirect($c->uri_for('/'));
+ }
+
+As with the login controller, be sure to delete the
+C<$c-E<gt>response-E<gt>body('Matched MyApp::Controller::Logout in Logout.');>
+line of the C<sub index>.
+
+
+=head2 Add a Login Form TT Template Page
+
+Create a login form by opening C<root/src/login.tt2> and inserting:
+
+ [% META title = 'Login' %]
+
+ <!-- Login form -->
+ <form method="post" action="[% c.uri_for('/login') %]">
+ <table>
+ <tr>
+ <td>Username:</td>
+ <td><input type="text" name="username" size="40" /></td>
+ </tr>
+ <tr>
+ <td>Password:</td>
+ <td><input type="password" name="password" size="40" /></td>
+ </tr>
+ <tr>
+ <td colspan="2"><input type="submit" name="submit" value="Submit" /></td>
+ </tr>
+ </table>
+ </form>
+
+
+=head2 Add Valid User Check
+
+We need something that provides enforcement for the authentication
+mechanism -- a I<global> mechanism that prevents users who have not
+passed authentication from reaching any pages except the login page.
+This is generally done via an C<auto> action/method (prior to Catalyst
+v5.66, this sort of thing would go in C<MyApp.pm>, but starting in
+v5.66, the preferred location is C<lib/MyApp/Controller/Root.pm>).
+
+Edit the existing C<lib/MyApp/Controller/Root.pm> class file and insert
+the following method:
+
+ =head2 auto
+
+ Check if there is a user and, if not, forward to login page
+
+ =cut
+
+ # Note that 'auto' runs after 'begin' but before your actions and that
+ # 'auto's "chain" (all from application path to most specific class are run)
+ # See the 'Actions' section of 'Catalyst::Manual::Intro' for more info.
+ sub auto : Private {
+ my ($self, $c) = @_;
+
+ # Allow unauthenticated users to reach the login page. This
+ # allows unauthenticated users to reach any action in the Login
+ # controller. To lock it down to a single action, we could use:
+ # if ($c->action eq $c->controller('Login')->action_for('index'))
+ # to only allow unauthenticated access to the 'index' action we
+ # added above.
+ if ($c->controller eq $c->controller('Login')) {
+ return 1;
+ }
+
+ # If a user doesn't exist, force login
+ if (!$c->user_exists) {
+ # Dump a log message to the development server debug output
+ $c->log->debug('***Root::auto User not found, forwarding to /login');
+ # Redirect the user to the login page
+ $c->response->redirect($c->uri_for('/login'));
+ # Return 0 to cancel 'post-auto' processing and prevent use of application
+ return 0;
+ }
+
+ # User found, so return 1 to continue with processing after this 'auto'
+ return 1;
+ }
+
+As discussed in
+L<Catalyst::Manual::Tutorial::03_MoreCatalystBasics/CREATE A CATALYST CONTROLLER>,
+every C<auto> method from the application/root controller down to the
+most specific controller will be called. By placing the
+authentication enforcement code inside the C<auto> method of
+C<lib/MyApp/Controller/Root.pm> (or C<lib/MyApp.pm>), it will be
+called for I<every> request that is received by the entire
+application.
+
+
+=head2 Displaying Content Only to Authenticated Users
+
+Let's say you want to provide some information on the login page that
+changes depending on whether the user has authenticated yet. To do
+this, open C<root/src/login.tt2> in your editor and add the following
+lines to the bottom of the file:
+
+ ...
+ <p>
+ [%
+ # This code illustrates how certain parts of the TT
+ # template will only be shown to users who have logged in
+ %]
+ [% IF c.user_exists %]
+ Please Note: You are already logged in as '[% c.user.username %]'.
+ You can <a href="[% c.uri_for('/logout') %]">logout</a> here.
+ [% ELSE %]
+ You need to log in to use this application.
+ [% END %]
+ [%#
+ Note that this whole block is a comment because the "#" appears
+ immediate after the "[%" (with no spaces in between). Although it
+ can be a handy way to temporarily "comment out" a whole block of
+ TT code, it's probably a little too subtle for use in "normal"
+ comments.
+ %]
+ </p>
+
+Although most of the code is comments, the middle few lines provide a
+"you are already logged in" reminder if the user returns to the login
+page after they have already authenticated. For users who have not yet
+authenticated, a "You need to log in..." message is displayed (note the
+use of an IF-THEN-ELSE construct in TT).
+
+
+=head2 Try Out Authentication
+
+Press C<Ctrl-C> to kill the previous server instance (if it's still
+running) and restart it:
+
+ $ script/myapp_server.pl
+
+B<IMPORTANT NOTE:> If you are having issues with authentication on
+Internet Explorer, be sure to check the system clocks on both your
+server and client machines. Internet Explorer is very picky about
+timestamps for cookies. You can quickly sync a Debian system by
+installing the "ntpdate" package:
+
+ sudo aptitude -y install ntpdate
+
+And then run the following command:
+
+ sudo ntpdate-debian
+
+Or, depending on your firewall configuration:
+
+ sudo ntpdate-debian -u
+
+Note: NTP can be a little more finicky about firewalls because it uses
+UDP vs. the more common TCP that you see with most Internet protocols.
+Worse case, you might have to manually set the time on your development
+box instead of using NTP.
+
+Now trying going to L<http://localhost:3000/books/list> and you should
+be redirected to the login page, hitting Shift+Reload or Ctrl+Reload
+if necessary (the "You are already logged in" message should I<not>
+appear -- if it does, click the C<logout> button and try again). Note
+the C<***Root::auto User not found...> debug message in the
+development server output. Enter username C<test01> and password
+C<mypass>, and you should be taken to the Book List page.
+
+Open C<root/src/books/list.tt2> and add the following lines to the
+bottom (below the closing </table> tag):
+
+ <p>
+ <a href="[% c.uri_for('/login') %]">Login</a>
+ <a href="[% c.uri_for(c.controller.action_for('form_create')) %]">Create</a>
+ </p>
+
+Reload your browser and you should now see a "Login" and "Create" links
+at the bottom of the page (as mentioned earlier, you can update template
+files without reloading the development server). Click the first link
+to return to the login page. This time you I<should> see the "You are
+already logged in" message.
+
+Finally, click the C<You can logout here> link on the C</login> page.
+You should stay at the login page, but the message should change to "You
+need to log in to use this application."
+
+
+=head1 USING PASSWORD HASHES
+
+In this section we increase the security of our system by converting
+from cleartext passwords to SHA-1 password hashes that include a
+random "salt" value to make them extremely difficult to crack with
+dictionary and "rainbow table" attacks.
+
+B<Note:> This section is optional. You can skip it and the rest of the
+tutorial will function normally.
+
+Be aware that even with the techniques shown in this section, the browser
+still transmits the passwords in cleartext to your application. We are
+just avoiding the I<storage> of cleartext passwords in the database by
+using a salted SHA-1 hash. If you are concerned about cleartext passwords
+between the browser and your application, consider using SSL/TLS, made
+easy with the Catalyst plugin Catalyst::Plugin:RequireSSL.
+
+
+=head2 Install DBIx::Class::EncodedColumn
+
+L<DBIx::Class::EncodedColumn|DBIx::Class::EncodedColumn> provides features
+that can greatly simplify the maintenance of passwords. It's currently
+not available as a .deb package in the normal Debian repositories, so let's
+install it directly from CPAN:
+
+ $ sudo cpan DBIx::Class::EncodedColumn
+
+
+=head2 Re-Run the DBIC::Schema Model Helper to Include DBIx::Class::EncodedColumn
+
+Next, we can re-run the model helper to have it include
+L<DBIx::Class::EncodedColumn|DBIx::Class::EncodedColumn> in all of the
+Result Classes it generates for us. Simply use the same command we
+saw in Chapters 3 and 4, but add C<,EncodedColumn> to the C<components>
+argument:
+
+ $ script/myapp_create.pl model DB DBIC::Schema MyApp::Schema \
+ create=static components=TimeStamp,EncodedColumn dbi:SQLite:myapp.db
+
+If you then open one of the Result Classes, you will see that it
+includes EncodedColumn in the C<load_components> line. Take a look at
+C<lib/MyApp/Schema/Result/User.pm> since that's the main class where we
+want to use hashed and salted passwords:
+
+ __PACKAGE__->load_components("InflateColumn::DateTime", "TimeStamp", "EncodedColumn", "Core");
+
+
+=head2 Modify the "password" Column to Use EncodedColumn
+
+Open the file C<lib/MyApp/Schema/Result/User.pm> and enter the following
+text below the "# DO NOT MODIFY THIS OR ANYTHING ABOVE!" line but above
+the closing "1;":
+
+ # Have the 'password' column use a SHA-1 hash and 10-character salt
+ # with hex encoding; Generate the 'check_password" method
+ __PACKAGE__->add_columns(
+ 'password' => {
+ data_type => "TEXT",
+ size => undef,
+ encode_column => 1,
+ encode_class => 'Digest',
+ encode_args => {salt_length => 10},
+ encode_check_method => 'check_password',
+ },
+ );
+
+This redefines the automatically generated definition for the password
+fields at the top of the Result Class file to now use EncodedColumn
+logic (C<encoded_column> is set to 1). C<encode_class> can be set to
+either C<Digest> to use
+L<DBIx::Class::EncodedColumn::Digest|DBIx::Class::EncodedColumn::Digest>,
+or C<Crypt::Eksblowfish::Bcrypt> for
+L<DBIx::Class::EncodedColumn::Crypt::Eksblowfish::Bcrypt|DBIx::Class::EncodedColumn::Crypt::Eksblowfish::Bcrypt>.
+C<encode_args> is then used to customize the type of Digest you
+selected. Here we only specified the size of the salt to use, but
+we could have also modified the hashing algorithm ('SHA-256' is
+the default) and the format to use ('base64' is the default, but
+'hex' and 'binary' are other options). To use these, you could
+change the C<encode_args> to something like:
+
+ encode_args => {algorithm => 'SHA-1',
+ format => 'hex',
+ salt_length => 10},
+
+
+=head2 Load Hashed Passwords in the Database
+
+Next, let's create a quick script to load some hashed and salted passwords
+into the C<password> column of our C<users> table. Open the file
+C<set_hashed_passwords.pl> in your editor and enter the following text:
+
+ #!/usr/bin/perl
+
+ use strict;
+ use warnings;
+
+ use MyApp::Schema;
+
+ my $schema = MyApp::Schema->connect('dbi:SQLite:myapp.db');
+
+ my @users = $schema->resultset('User')->all;
+
+ foreach my $user (@users) {
+ $user->password('mypass');
+ $user->update;
+ }
+
+EncodedColumn lets us simple call C<$user->check_password($password)>
+to see if the user has supplied the correct password, or, as we show
+above, call C<$user->update($new_password)> to update the hashed
+password stored for this user.
+
+Then run the following command:
+
+ $ perl -Ilib set_hashed_passwords.pl
+
+We had to use the C<-Ilib> arguement to tell perl to look under the
+C<lib> directory for our C<MyApp::Schema> model.
+
+Then dump the users table to verify that it worked:
+
+ $ sqlite3 myapp.db "select * from user"
+ 1|test01|38d3974fa9e9263099f7bc2574284b2f55473a9bM=fwpX2NR8|t01 at na.com|Joe|Blow|1
+ 2|test02|6ed8586587e53e0d7509b1cfed5df08feadc68cbMJlnPyPt0I|t02 at na.com|Jane|Doe|1
+ 3|test03|af929a151340c6aed4d54d7e2651795d1ad2e2f7UW8dHoGv9z|t03 at na.com|No|Go|0
+
+As you can see, the passwords are much harder to steal from the
+database. Also note that this demonstrates how to use a DBIx::Class
+model outside of your web application -- a very useful feature in many
+situations.
+
+
+=head2 Enable Hashed and Salted Passwords
+
+Edit C<lib/MyApp.pm> and update it to match the following text (the only change
+is to the C<password_type> field):
+
+ # Configure SimpleDB Authentication
+ __PACKAGE__->config->{'Plugin::Authentication'} = {
+ default => {
+ class => 'SimpleDB',
+ user_model => 'DB::User',
+ password_type => 'self_check',
+ },
+ };
+
+The use of C<self_check> will cause
+Catalyst::Plugin::Authentication::Store::DBIC to call the
+C<check_password> method we enabled on our C<password> columns.
+
+
+=head2 Try Out the Hashed Passwords
+
+Press C<Ctrl-C> to kill the previous server instance (if it's still
+running) and restart it:
+
+ $ script/myapp_server.pl
+
+You should now be able to go to L<http://localhost:3000/books/list> and
+login as before. When done, click the "logout" link on the login page
+(or point your browser at L<http://localhost:3000/logout>).
+
+
+=head1 USING THE SESSION FOR FLASH
+
+As discussed in the previous chapter of the tutorial, C<flash> allows
+you to set variables in a way that is very similar to C<stash>, but it
+will remain set across multiple requests. Once the value is read, it
+is cleared (unless reset). Although C<flash> has nothing to do with
+authentication, it does leverage the same session plugins. Now that
+those plugins are enabled, let's go back and update the "delete and
+redirect with query parameters" code seen at the end of the L<Basic
+CRUD|Catalyst::Manual::Tutorial::04_BasicCRUD> chapter of the tutorial to
+take advantage of C<flash>.
+
+First, open C<lib/MyApp/Controller/Books.pm> and modify C<sub delete>
+to match the following (everything after the model search line of code
+has changed):
+
+ =head2 delete
+
+ Delete a book
+
+ =cut
+
+ sub delete :Chained('object') :PathPart('delete') :Args(0) {
+ my ($self, $c) = @_;
+
+ # Use the book object saved by 'object' and delete it along
+ # with related 'book_authors' entries
+ $c->stash->{object}->delete;
+
+ # Use 'flash' to save information across requests until it's read
+ $c->flash->{status_msg} = "Book deleted";
+
+ # Redirect the user back to the list page
+ $c->response->redirect($c->uri_for($self->action_for('list')));
+ }
+
+Next, open C<root/src/wrapper.tt2> and update the TT code to pull from
+flash vs. the C<status_msg> query parameter:
+
+ ...
+ <div id="content">
+ [%# Status and error messages %]
+ <span class="message">[% status_msg || c.flash.status_msg %]</span>
+ <span class="error">[% error_msg %]</span>
+ [%# This is where TT will stick all of your template's contents. -%]
+ [% content %]
+ </div><!-- end content -->
+ ...
+
+Although the sample above only shows the C<content> div, leave the
+rest of the file intact -- the only change we made to the C<wrapper.tt2>
+was to add "C<|| c.request.params.status_msg>" to the
+C<E<lt>span class="message"E<gt>> line.
+
+
+=head2 Try Out Flash
+
+Restart the development server, log in, and then point your browser to
+L<http://localhost:3000/books/url_create/Test/1/4> to create an extra
+several books. Click the "Return to list" link and delete one of the
+"Test" books you just added. The C<flash> mechanism should retain our
+"Book deleted" status message across the redirect.
+
+B<NOTE:> While C<flash> will save information across multiple requests,
+I<it does get cleared the first time it is read>. In general, this is
+exactly what you want -- the C<flash> message will get displayed on
+the next screen where it's appropriate, but it won't "keep showing up"
+after that first time (unless you reset it). Please refer to
+L<Catalyst::Plugin::Session|Catalyst::Plugin::Session> for additional
+information.
+
+
+=head2 Switch To Flash-To-Stash
+
+Although the a use of flash above works well, the
+C<status_msg || c.flash.status_msg> statement is a little ugly. A nice
+alternative is to use the C<flash_to_stash> feature that automatically
+copies the content of flash to stash. This makes your controller
+and template code work regardless of where it was directly access, a
+forward, or a redirect. To enable C<flash_to_stash>, you can either
+set the value in C<lib/MyApp.pm> by changing the default
+C<__PACKAGE__-E<gt>config> setting to something like:
+
+ __PACKAGE__->config(
+ name => 'MyApp',
+ session => {flash_to_stash => 1}
+ );
+
+B<or> add the following to C<myapp.conf>:
+
+ <session>
+ flash_to_stash 1
+ </session>
+
+The C<__PACKAGE__-E<gt>config> option is probably preferable here
+since it's not something you will want to change at runtime without it
+possibly breaking some of your code.
+
+Then edit C<root/src/wrapper.tt2> and change the C<status_msg> line
+to match the following:
+
+ <span class="message">[% status_msg %]</span>
+
+Restart the development server and go to
+L<http://localhost:3000/books/list> in your browser. Delete another
+of the "Test" books you added in the previous step. Flash should still
+maintain the status message across the redirect even though you are no
+longer explicitly accessing C<c.flash>.
+
+
+=head1 AUTHOR
+
+Kennedy Clark, C<hkclark at gmail.com>
+
+Please report any errors, issues or suggestions to the author. The
+most recent version of the Catalyst Tutorial can be found at
+L<http://dev.catalyst.perl.org/repos/Catalyst/Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/>.
+
+Copyright 2006-2008, Kennedy Clark, under Creative Commons License
+(L<http://creativecommons.org/licenses/by-sa/3.0/us/>).
Copied: Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/06_Authorization.pod (from rev 10275, Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/Authorization.pod)
===================================================================
--- Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/06_Authorization.pod (rev 0)
+++ Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/06_Authorization.pod 2009-05-24 22:30:47 UTC (rev 10277)
@@ -0,0 +1,362 @@
+=head1 NAME
+
+Catalyst::Manual::Tutorial::06_Authorization - Catalyst Tutorial - Chapter 6: Authorization
+
+
+=head1 OVERVIEW
+
+This is B<Chapter 6 of 10> for the Catalyst tutorial.
+
+L<Tutorial Overview|Catalyst::Manual::Tutorial>
+
+=over 4
+
+=item 1
+
+L<Introduction|Catalyst::Manual::Tutorial::01_Intro>
+
+=item 2
+
+L<Catalyst Basics|Catalyst::Manual::Tutorial::02_CatalystBasics>
+
+=item 3
+
+L<More Catalyst Basics|Catalyst::Manual::Tutorial::03_MoreCatalystBasics>
+
+=item 4
+
+L<Basic CRUD|Catalyst::Manual::Tutorial::04_BasicCRUD>
+
+=item 5
+
+L<Authentication|Catalyst::Manual::Tutorial::05_Authentication>
+
+=item 6
+
+B<06_Authorization>
+
+=item 7
+
+L<Debugging|Catalyst::Manual::Tutorial::07_Debugging>
+
+=item 8
+
+L<Testing|Catalyst::Manual::Tutorial::08_Testing>
+
+=item 9
+
+L<Advanced CRUD|Catalyst::Manual::Tutorial::09_AdvancedCRUD>
+
+=item 10
+
+L<Appendices|Catalyst::Manual::Tutorial::10_Appendices>
+
+=back
+
+
+=head1 DESCRIPTION
+
+This chapter of the tutorial adds role-based authorization to the
+existing authentication implemented in Chapter 5. It provides simple
+examples of how to use roles in both TT templates and controller
+actions. The first half looks at basic authorization concepts. The
+second half looks at how moving your authorization code to your model
+can simplify your code and make things easier to maintain.
+
+You can checkout the source code for this example from the catalyst
+subversion repository as per the instructions in
+L<Catalyst::Manual::Tutorial::01_Intro|Catalyst::Manual::Tutorial::01_Intro>.
+
+
+=head1 BASIC AUTHORIZATION
+
+In this section you learn the basics of how authorization works under
+Catalyst.
+
+
+=head2 Update Plugins to Include Support for Authorization
+
+Edit C<lib/MyApp.pm> and add C<Authorization::Roles> to the list:
+
+ # Load plugins
+ use Catalyst qw/-Debug
+ ConfigLoader
+ Static::Simple
+
+ StackTrace
+
+ Authentication
+ Authorization::Roles
+
+ Session
+ Session::Store::FastMmap
+ Session::State::Cookie
+ /;
+
+B<Note:> As discussed in MoreCatalystBasics, different versions of
+C<Catalyst::Devel> have used a variety of methods to load the plugins.
+You can put the plugins in the C<use Catalyst> statement if you
+prefer.
+
+Once again (remain sharp, by now you should be getting the hang of things)
+include this additional plugin as a new dependency in the Makefile.PL file
+like this:
+
+ requires (
+ ...
+ 'Catalyst::Plugin::Authorization::Roles' => '0',
+ );
+
+=head2 Add Role-Specific Logic to the "Book List" Template
+
+Open C<root/src/books/list.tt2> in your editor and add the following
+lines to the bottom of the file:
+
+ ...
+ <p>Hello [% c.user.username %], you have the following roles:</p>
+
+ <ul>
+ [% # Dump list of roles -%]
+ [% FOR role = c.user.role %]<li>[% role %]</li>[% END %]
+ </ul>
+
+ <p>
+ [% # Add some simple role-specific logic to template %]
+ [% # Use $c->check_user_roles() to check authz -%]
+ [% IF c.check_user_roles('user') %]
+ [% # Give normal users a link for 'logout' %]
+ <a href="[% c.uri_for('/logout') %]">User Logout</a>
+ [% END %]
+
+ [% # Can also use $c->user->check_roles() to check authz -%]
+ [% IF c.check_user_roles('admin') %]
+ [% # Give admin users a link for 'create' %]
+ <a href="[% c.uri_for(c.controller.action_for('form_create')) %]">Admin Create</a>
+ [% END %]
+ </p>
+
+This code displays a different combination of links depending on the
+roles assigned to the user.
+
+
+=head2 Limit Books::add to 'admin' Users
+
+C<IF> statements in TT templates simply control the output that is sent
+to the user's browser; it provides no real enforcement (if users know or
+guess the appropriate URLs, they are still perfectly free to hit any
+action within your application). We need to enhance the controller
+logic to wrap restricted actions with role-validation logic.
+
+For example, we might want to restrict the "formless create" action to
+admin-level users by editing C<lib/MyApp/Controller/Books.pm> and
+updating C<url_create> to match the following code:
+
+ =head2 url_create
+
+ Create a book with the supplied title and rating,
+ with manual authorization
+
+ =cut
+
+ sub url_create :Chained('base') :PathPart('url_create') :Args(3) {
+ # In addition to self & context, get the title, rating & author_id args
+ # from the URL. Note that Catalyst automatically puts extra information
+ # after the "/<controller_name>/<action_name/" into @_
+ my ($self, $c, $title, $rating, $author_id) = @_;
+
+ # Check the user's roles
+ if ($c->check_user_roles('admin')) {
+ # Call create() on the book model object. Pass the table
+ # columns/field values we want to set as hash values
+ my $book = $c->model('DB::Book')->create({
+ title => $title,
+ rating => $rating
+ });
+
+ # Add a record to the join table for this book, mapping to
+ # appropriate author
+ $book->add_to_book_author({author_id => $author_id});
+ # Note: Above is a shortcut for this:
+ # $book->create_related('book_author', {author_id => $author_id});
+
+ # Assign the Book object to the stash for display in the view
+ $c->stash->{book} = $book;
+
+ # Set the TT template to use
+ $c->stash->{template} = 'books/create_done.tt2';
+ } else {
+ # Provide very simple feedback to the user.
+ $c->response->body('Unauthorized!');
+ }
+ }
+
+
+To add authorization, we simply wrap the main code of this method in an
+C<if> statement that calls C<check_user_roles>. If the user does not
+have the appropriate permissions, they receive an "Unauthorized!"
+message. Note that we intentionally chose to display the message this
+way to demonstrate that TT templates will not be used if the response
+body has already been set. In reality you would probably want to use a
+technique that maintains the visual continuity of your template layout
+(for example, using the "status" or "error" message feature added in
+Chapter 3 or C<detach> to an action that shows an "unauthorized" page).
+
+B<TIP>: If you want to keep your existing C<url_create> method, you can
+create a new copy and comment out the original by making it look like a
+Pod comment. For example, put something like C<=begin> before
+C<sub add : Local {> and C<=end> after the closing C<}>.
+
+
+=head2 Try Out Authentication And Authorization
+
+Press C<Ctrl-C> to kill the previous server instance (if it's still
+running) and restart it:
+
+ $ script/myapp_server.pl
+
+Now trying going to L<http://localhost:3000/books/list> and you should
+be taken to the login page (you might have to C<Shift+Reload> or
+C<Ctrl+Reload> your browser and/or click the "User Logout" link on the book
+list page). Try logging in with both C<test01> and C<test02> (both
+use a password of C<mypass>) and notice how the roles information
+updates at the bottom of the "Book List" page. Also try the "User Logout"
+link on the book list page.
+
+Now the "url_create" URL will work if you are already logged in as user
+C<test01>, but receive an authorization failure if you are logged in as
+C<test02>. Try:
+
+ http://localhost:3000/books/url_create/test/1/6
+
+while logged in as each user. Use one of the "logout" links (or go to
+L<http://localhost:3000/logout> in your browser directly) when you are
+done.
+
+
+=head1 ENABLE MODEL-BASED AUTHORIZATION
+
+Hopefully it's fairly obvious that adding detailed permission checking
+logic to our controllers and view templates isn't a very clean or
+scalable way to build role-based permissions into out application. As
+with many other aspects of MVC web development, the goal is to have
+your controllers and views be an "thin" as possible, with all of the
+"fancy business logic" built into your model.
+
+For example, let's add a method to our C<Books.pm> Result Class to
+check if a user is allowed to delete a book. Open
+C<lib/MyApp/Schema/Result/Book.pm> and add the following method
+(be sure to add it below the "C<DO NOT MODIFY ...>" line):
+
+ =head2 delete_allowed_by
+
+ Can the specified user delete the current book?
+
+ =cut
+
+ sub delete_allowed_by {
+ my ($self, $user) = @_;
+
+ # Only allow delete if user has 'admin' role
+ return $user->has_role('admin');
+ }
+
+Here we call a C<has_role> method on our user object, so we should add
+this method to our Result Class. Open
+C<lib/MyApp/Schema/Result/User.pm> and add the following method below
+the "C<DO NOT MODIFY ...>" line:
+
+ =head 2 has_role
+
+ Check if a user has the specified role
+
+ =cut
+
+ use Perl6::Junction qw/any/;
+ sub has_role {
+ my ($self, $role) = @_;
+
+ # Does this user posses the required role?
+ return any(map { $_->role } $self->roles) eq $role;
+ }
+
+Now we need to add some enforcement inside our controller. Open
+C<lib/MyApp/Controller/Books.pm> and update the C<delete> method to
+match the following code:
+
+ =head2 delete
+
+ Delete a book
+
+ =cut
+
+ sub delete :Chained('object') :PathPart('delete') :Args(0) {
+ my ($self, $c) = @_;
+
+ # Check permissions
+ $c->detach('/error_noperms')
+ unless $c->stash->{object}->delete_allowed_by($c->user->get_object);
+
+ # Use the book object saved by 'object' and delete it along
+ # with related 'book_authors' entries
+ $c->stash->{object}->delete;
+
+ # Use 'flash' to save information across requests until it's read
+ $c->flash->{status_msg} = "Book deleted";
+
+ # Redirect the user back to the list page
+ $c->response->redirect($c->uri_for($self->action_for('list')));
+ }
+
+Here, we C<detach> to an error page if the user is lacking the
+appropriate permissions. For this to work, we need to make
+arrangements for the '/error_noperms' action to work. Open
+C<lib/MyApp/Controller/Root.pm> and add this method:
+
+ =head2 error_noperms
+
+ Permissions error screen
+
+ =cut
+
+ sub error_noperms :Chained('/') :PathPath('error_noperms') :Args(0) {
+ my ($self, $c) = @_;
+
+ $c->stash->{template} = 'error_noperms.tt2';
+ }
+
+And also add the template file by putting the following text into
+C<root/src/error_noperms.tt2>:
+
+ <span class="error">Permission Denied</span>
+
+Then run the Catalyst development server script:
+
+ $ script/myapp_server.pl
+
+Log in as C<test01> and create several new books using the C<url_create>
+feature:
+
+ http://localhost:3000/books/url_create/Test/1/4
+
+Then, while still logged in as C<test01>, click the "Delete" link next
+to one of these books. The book should be removed and you should see
+the usual green "Book deleted" message. Next, click the "User Logout"
+link and log back in as C<test02>. Now try deleting one of the books.
+You should be taken to the red "Permission Denied" message on our
+error page.
+
+Use one of the 'Logout' links (or go to the
+L<http://localhost:3000/logout> URL directly) when you are done.
+
+
+=head1 AUTHOR
+
+Kennedy Clark, C<hkclark at gmail.com>
+
+Please report any errors, issues or suggestions to the author. The
+most recent version of the Catalyst Tutorial can be found at
+L<http://dev.catalyst.perl.org/repos/Catalyst/Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/>.
+
+Copyright 2006-2008, Kennedy Clark, under Creative Commons License
+(L<http://creativecommons.org/licenses/by-sa/3.0/us/>).
+
Copied: Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/07_Debugging.pod (from rev 10275, Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/Debugging.pod)
===================================================================
--- Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/07_Debugging.pod (rev 0)
+++ Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/07_Debugging.pod 2009-05-24 22:30:47 UTC (rev 10277)
@@ -0,0 +1,380 @@
+=head1 NAME
+
+Catalyst::Manual::Tutorial::07_Debugging - Catalyst Tutorial - Chapter 7: Debugging
+
+
+=head1 OVERVIEW
+
+This is B<Chapter 7 of 10> for the Catalyst tutorial.
+
+L<Tutorial Overview|Catalyst::Manual::Tutorial>
+
+=over 4
+
+=item 1
+
+L<Introduction|Catalyst::Manual::Tutorial::01_Intro>
+
+=item 2
+
+L<Catalyst Basics|Catalyst::Manual::Tutorial::02_CatalystBasics>
+
+=item 3
+
+L<More Catalyst Basics|Catalyst::Manual::Tutorial::03_MoreCatalystBasics>
+
+=item 4
+
+L<Basic CRUD|Catalyst::Manual::Tutorial::04_BasicCRUD>
+
+=item 5
+
+L<Authentication|Catalyst::Manual::Tutorial::05_Authentication>
+
+=item 6
+
+L<Authorization|Catalyst::Manual::Tutorial::06_Authorization>
+
+=item 7
+
+B<07_Debugging>
+
+=item 8
+
+L<Testing|Catalyst::Manual::Tutorial::08_Testing>
+
+=item 9
+
+L<Advanced CRUD|Catalyst::Manual::Tutorial::09_AdvancedCRUD>
+
+=item 10
+
+L<Appendices|Catalyst::Manual::Tutorial::10_Appendices>
+
+=back
+
+
+=head1 DESCRIPTION
+
+This chapter of the tutorial takes a brief look at the primary options
+available for troubleshooting Catalyst applications.
+
+Note that when it comes to debugging and troubleshooting, there are two
+camps:
+
+=over 4
+
+=item *
+
+Fans of C<log> and C<print> statements embedded in the code.
+
+=item *
+
+Fans of interactive debuggers.
+
+=back
+
+Catalyst is able to easily accommodate both styles of debugging.
+
+
+=head1 LOG STATEMENTS
+
+Folks in the former group can use Catalyst's C<$c-E<gt>log> facility.
+(See L<Catalyst::Log|Catalyst::Log> for more detail.) For example, if
+you add the following code to a controller action method:
+
+ $c->log->info("Starting the foreach loop here");
+
+ $c->log->debug("Value of \$id is: ".$id);
+
+Then the Catalyst development server will display your message along
+with the other debug output. To accomplish the same thing in a TT
+template view use:
+
+ [% c.log.debug("This is a test log message") %]
+
+As with many other logging facilities, you a method is defined for
+each of the following "logging levels" (in increasing order of
+severity/importance):
+
+ $c->log->debug
+ $c->log->info
+ $c->log->warn
+ $c->log->error
+ $c->log->fatal
+
+You can also use L<Data::Dumper|Data::Dumper> in both Catalyst code
+(C<use Data::Dumper; $c-E<gt>log-E<gt>debug("\$var is: ".Dumper($var));)>)
+and TT templates (C<[% Dumper.dump(book) %]>.
+
+
+=head1 RUNNING CATALYST UNDER THE PERL DEBUGGER
+
+Members of the interactive-debugger fan club will also be at home with
+Catalyst applications. One approach to this style of Perl debugging is
+to embed breakpoints in your code. For example, open
+C<lib/MyApp/Controller/Books.pm> in your editor and add the
+C<DB::single=1> line as follows inside the C<list> method (I like to
+"left-justify" my debug statements so I don't forget to remove them, but
+you can obviously indent them if you prefer):
+
+ sub list : Local {
+ # Retrieve the usual Perl OO '$self' for this object. $c is the Catalyst
+ # 'Context' that's used to 'glue together' the various components
+ # that make up the application
+ my ($self, $c) = @_;
+
+ $DB::single=1;
+
+ # Retrieve all of the book records as book model objects and store in the
+ # stash where they can be accessed by the TT template
+ $c->stash->{books} = [$c->model('DB::Book')->all];
+
+ # Set the TT template to use. You will almost always want to do this
+ # in your action methods.
+ $c->stash->{template} = 'books/list.tt2';
+ }
+
+This causes the Perl Debugger to enter "single step mode" when this command is
+encountered (it has no effect when Perl is run without the C<-d> flag).
+
+B<NOTE:> The C<DB> here is the Perl Debugger, not the DB model.
+
+If you haven't done it already, enable SQL logging as before:
+
+ $ export DBIC_TRACE=1
+
+To now run the Catalyst development server under the Perl debugger, simply
+prepend C<perl -d> to the front of C<script/myapp_server.pl>:
+
+ $ perl -d script/myapp_server.pl
+
+This will start the interactive debugger and produce output similar to:
+
+ $ perl -d script/myapp_server.pl
+
+ Loading DB routines from perl5db.pl version 1.3
+ Editor support available.
+
+ Enter h or `h h' for help, or `man perldebug' for more help.
+
+ main::(script/myapp_server.pl:16): my $debug = 0;
+
+ DB<1>
+
+Press the C<c> key and hit C<Enter> to continue executing the Catalyst
+development server under the debugger. Although execution speed will be
+slightly slower than normal, you should soon see the usual Catalyst
+startup debug information.
+
+Now point your browser to L<http://localhost:3000/books/list> and log
+in. Once the breakpoint is encountered in the
+C<MyApp::Controller::list> method, the console session running the
+development server will drop to the Perl debugger prompt:
+
+ MyApp::Controller::Books::list(/home/me/MyApp/script/../lib/MyApp/Controller/Books.pm:48):
+ 48: $c->stash->{books} = [$c->model('DB::Book')->all];
+
+ DB<1>
+
+You now have the full Perl debugger at your disposal. First use the
+C<next> feature by typing C<n> to execute the C<all> method on the Book
+model (C<n> jumps over method/subroutine calls; you can also use C<s> to
+C<single-step> into methods/subroutines):
+
+ DB<1> n
+ SELECT me.id, me.title, me.rating, me.created, me.updated FROM book me:
+ MyApp::Controller::Books::list(/home/me/MyApp/script/../lib/MyApp/Controller/Books.pm:53):
+ 53: $c->stash->{template} = 'books/list.tt2';
+
+ DB<1>
+
+This takes you to the next line of code where the template name is set.
+Notice that because we enabled C<DBIC_TRACE=1> earlier, SQL debug
+output also shows up in the development server debug information.
+
+Next, list the methods available on our C<Book> model:
+
+ DB<1> m $c->model('DB::Book')
+ ()
+ (0+
+ (bool
+ __result_class_accessor
+ __source_handle_accessor
+ _add_alias
+ __bool
+ _build_unique_query
+ _calculate_score
+ _collapse_cond
+ <lines removed for brevity>
+
+ DB<2>
+
+We can also play with the model directly:
+
+ DB<2> x ($c->model('DB::Book')->all)[1]->title
+ SELECT me.id, me.title, me.rating, me.created, me.updated FROM book me:
+ 0 'TCP/IP Illustrated, Volume 1'
+
+This uses the Perl debugger C<x> command to display the title of a book.
+
+Next we inspect the C<books> element of the Catalyst C<stash> (the C<4>
+argument to the C<x> command limits the depth of the dump to 4 levels):
+
+ DB<3> x 4 $c->stash->{books}
+ 0 ARRAY(0xa8f3b7c)
+ 0 MyApp::Model::DB::Book=HASH(0xb8e702c)
+ '_column_data' => HASH(0xb8e5e2c)
+ 'created' => '2009-05-08 10:19:46'
+ 'id' => 1
+ 'rating' => 5
+ 'title' => 'CCSP SNRS Exam Certification Guide'
+ 'updated' => '2009-05-08 10:19:46'
+ '_in_storage' => 1
+ <lines removed for brevity>
+
+Then enter the C<c> command to continue processing until the next
+breakpoint is hit (or the application exits):
+
+ DB<4> c
+ SELECT author.id, author.first_name, author.last_name FROM ...
+
+Finally, press C<Ctrl+C> to break out of the development server.
+Because we are running inside the Perl debugger, you will drop to the
+debugger prompt.
+
+ ^CCatalyst::Engine::HTTP::run(/usr/local/share/perl/5.10.0/Catalyst/Engine/HTTP.pm:260):
+ 260: while ( accept( Remote, $daemon ) ) {
+
+ DB<4>
+
+Finally, press C<q> to exit the debugger and return to your OS
+shell prompt:
+
+ DB<4> q
+ $
+
+For more information on using the Perl debugger, please see C<perldebug>
+and C<perldebtut>. For those daring souls out there, you can dive down
+even deeper into the magical depths of this fine debugger by checking
+out C<perldebguts>.
+
+You can also type C<h> or C<h h> at the debugger prompt to view the
+built-in help screens.
+
+For an excellent book covering all aspects of the Perl debugger, we highly
+recommend reading 'Pro Perl Debugging' by Richard Foley.
+
+Oh yeah, before you forget, be sure to remove the C<DB::single=1> line you
+added above in C<lib/MyApp/Controller/Books.pm>.
+
+=head1 DEBUGGING MODULES FROM CPAN
+
+Although the techniques discussed above work well for code you are
+writing, what if you want to use print/log/warn messages or set
+breakpoints in code that you have installed from CPAN (or in module that
+ship with Perl)? One helpful approach is to place a copy of the module
+inside the C<lib> directory of your Catalyst project. When Catalyst
+loads, it will load from inside your C<lib> directory first, only
+turning to the global modules if a local copy cannot be found. You can
+then make modifications such as adding a C<$DB::single=1> to the local
+copy of the module without risking the copy in the original location.
+This can also be a great way to "locally override" bugs in modules while
+you wait for a fix on CPAN.
+
+
+Matt Trout has suggested the following shortcut to create a local
+copy of an installed module:
+
+ mkdir -p lib/Module; cp `perldoc -l Module::Name` lib/Module/
+
+Note: If you are following along in Debian 5 or Ubuntu, you will
+need to install the C<perl-doc> package to use the C<perldoc> command.
+Use C<sudo aptitude install perl-doc> to do that.
+
+For example, you could make a copy of
+L<Catalyst::Plugin::Authentication|Catalyst::Plugin::Authentication>
+with the following command:
+
+ mkdir -p lib/Catalyst/Plugin; cp \
+ `perldoc -l Catalyst::Plugin::Authentication` lib/Catalyst/Plugin
+
+You can then use the local copy inside your project to place logging
+messages and/or breakpoints for further study of that module.
+
+B<Note:> Matt has also suggested the following tips for Perl
+debugging:
+
+=over 4
+
+=item *
+
+Check the version of an installed module:
+
+ perl -ME<lt>mod_nameE<gt> -e '"print $E<lt>mod_nameE<gt>::VERSION\n"'
+
+For example:
+
+ $ perl -MCatalyst::Plugin::Authentication -e \
+ 'print $Catalyst::Plugin::Authentication::VERSION;'
+ 0.07
+
+and if you are using bash aliases:
+
+ alias pmver="perl -le '\$m = shift; eval qq(require \$m) \
+ or die qq(module \"\$m\" is not installed\\n); \
+ print \$m->VERSION'"
+
+=item *
+
+Check if a modules contains a given method:
+
+ perl -MModule::Name -e 'print Module::Name->can("method");'
+
+For example:
+
+ $ perl -MCatalyst::Plugin::Authentication -e \
+ 'print Catalyst::Plugin::Authentication->can("user");'
+ CODE(0x9c8db2c)
+
+If the method exists, the Perl C<can> method returns a coderef.
+Otherwise, it returns undef and nothing will be printed.
+
+=back
+
+
+=head1 TT DEBUGGING
+
+If you run into issues during the rendering of your template, it might
+be helpful to enable TT C<DEBUG> options. You can do this in a Catalyst
+environment by adding a C<DEBUG> line to the C<__PACKAGE__->config>
+declaration in C<lib/MyApp/View/TT.pm>:
+
+ __PACKAGE__->config({
+ TEMPLATE_EXTENSION => '.tt2',
+ DEBUG => 'undef',
+ });
+
+There are a variety of options you can use, such as 'undef', 'all',
+'service', 'context', 'parser' and 'provider'. See
+L<Template::Constants|Template::Constants> for more information
+(remove the C<DEBUG_> portion of the name shown in the TT docs and
+convert to lower case for use inside Catalyst).
+
+B<NOTE:> B<Please be sure to disable TT debug options before continuing
+with the tutorial> (especially the 'undef' option -- leaving this
+enabled will conflict with several of the conventions used by this
+tutorial to leave some variables undefined on purpose).
+
+Happy debugging.
+
+=head1 AUTHOR
+
+Kennedy Clark, C<hkclark at gmail.com>
+
+Please report any errors, issues or suggestions to the author. The
+most recent version of the Catalyst Tutorial can be found at
+L<http://dev.catalyst.perl.org/repos/Catalyst/Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/>.
+
+Copyright 2006-2008, Kennedy Clark, under Creative Commons License
+(L<http://creativecommons.org/licenses/by-sa/3.0/us/>).
Copied: Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/08_Testing.pod (from rev 10275, Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/Testing.pod)
===================================================================
--- Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/08_Testing.pod (rev 0)
+++ Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/08_Testing.pod 2009-05-24 22:30:47 UTC (rev 10277)
@@ -0,0 +1,406 @@
+=head1 NAME
+
+Catalyst::Manual::Tutorial::08_Testing - Catalyst Tutorial - Chapter 8: Testing
+
+
+=head1 OVERVIEW
+
+This is B<Chapter 8 of 10> for the Catalyst tutorial.
+
+L<Tutorial Overview|Catalyst::Manual::Tutorial>
+
+=over 4
+
+=item 1
+
+L<Introduction|Catalyst::Manual::Tutorial::01_Intro>
+
+=item 2
+
+L<Catalyst Basics|Catalyst::Manual::Tutorial::02_CatalystBasics>
+
+=item 3
+
+L<More Catalyst Basics|Catalyst::Manual::Tutorial::03_MoreCatalystBasics>
+
+=item 4
+
+L<Basic CRUD|Catalyst::Manual::Tutorial::04_BasicCRUD>
+
+=item 5
+
+L<Authentication|Catalyst::Manual::Tutorial::05_Authentication>
+
+=item 6
+
+L<Authorization|Catalyst::Manual::Tutorial::06_Authorization>
+
+=item 7
+
+L<Debugging|Catalyst::Manual::Tutorial::07_Debugging>
+
+=item 8
+
+B<08_Testing>
+
+=item 9
+
+L<Advanced CRUD|Catalyst::Manual::Tutorial::09_AdvancedCRUD>
+
+=item 10
+
+L<Appendices|Catalyst::Manual::Tutorial::10_Appendices>
+
+=back
+
+
+=head1 DESCRIPTION
+
+You may have noticed that the Catalyst Helper scripts automatically
+create basic C<.t> test scripts under the C<t> directory. This
+chapter of the tutorial briefly looks at how these tests can be used
+not only to ensure that your application is working correctly at the
+present time, but also provide automated regression testing as you
+upgrade various pieces of your application over time.
+
+You can check out the source code for this example from the Catalyst
+Subversion repository as per the instructions in
+L<Catalyst::Manual::Tutorial::01_Intro|Catalyst::Manual::Tutorial::01_Intro>.
+
+For an excellent introduction to learning the many benefits of testing
+your Perl applications and modules, you might want to read 'Perl Testing:
+A Developer's Notebook' by Ian Langworth and chromatic.
+
+
+=head1 RUNNING THE "CANNED" CATALYST TESTS
+
+There are a variety of ways to run Catalyst and Perl tests (for example,
+C<perl Makefile.PL> and C<make test>), but one of the easiest is with the
+C<prove> command. For example, to run all of the tests in the C<t>
+directory, enter:
+
+ $ prove --lib lib t
+
+There will be a lot of output because we have the C<-Debug> flag
+enabled in C<lib/MyApp.pm> (see the C<CATALYST_DEBUG=0> tip below for
+a quick and easy way to reduce the clutter). Look for lines like this
+for errors:
+
+ # Failed test 'Request should succeed'
+ # at t/controller_Books.t line 8.
+ # Looks like you failed 1 test of 3.
+
+The redirection used by the Authentication plugins will cause several
+failures in the default tests. You can fix this by making the following
+changes:
+
+1) Change the line in C<t/01app.t> that reads:
+
+ ok( request('/')->is_success, 'Request should succeed' );
+
+to:
+
+ ok( request('/login')->is_success, 'Request should succeed' );
+
+2) Change the line in C<t/controller_Logout.t> that reads:
+
+ ok( request('/logout')->is_success, 'Request should succeed' );
+
+to:
+
+ ok( request('/logout')->is_redirect, 'Request should succeed' );
+
+3) Change the line in C<t/controller_Books.t> that reads:
+
+ ok( request('/books')->is_success, 'Request should succeed' );
+
+to:
+
+ ok( request('/books')->is_redirect, 'Request should succeed' );
+
+4) Add the following statement to the top of C<t/view_TT.t>:
+
+ use MyApp;
+
+As you can see in the C<prove> command line above, the C<--lib> option
+is used to set the location of the Catalyst C<lib> directory. With this
+command, you will get all of the usual development server debug output,
+something most people prefer to disable while running tests cases.
+Although you can edit the C<lib/MyApp.pm> to comment out the C<-Debug>
+plugin, it's generally easier to simply set the C<CATALYST_DEBUG=0>
+environment variable. For example:
+
+ $ CATALYST_DEBUG=0 prove --lib lib t
+
+B<Note:> Depending on the versions of various modules you have
+installed, you might get some C<used only once> warnings -- you can
+ignore these. If you want to eliminate the warnings, you can
+edit C<Template::Base> to disable and then re-enable warnings
+are the C</usr/lib/perl5/Template/Base.pm> line in C<sub new>.
+You can locate where C<Template::Base> is located with the
+following command (it's probably in a place similar to
+C</usr/lib/perl5/Template/Base.pm>):
+
+ perldoc -l Template::Base
+
+Edit the file and modify C<sub new> to match:
+
+ ...
+ { no strict qw( refs );
+ # Disable warnings
+ no warnings;
+ $argnames = \@{"$class\::BASEARGS"} || [ ];
+ # Turn warnings back on
+ use warnings;
+ }
+ ...
+
+During the C<t/02pod> and C<t/03podcoverage> tests, you might notice the
+C<all skipped: set TEST_POD to enable this test> warning message. To
+execute the Pod-related tests, add C<TEST_POD=1> to the C<prove>
+command:
+
+ $ CATALYST_DEBUG=0 TEST_POD=1 prove --lib lib t
+
+If you omitted the Pod comments from any of the methods that were
+inserted, you might have to go back and fix them to get these tests to
+pass. :-)
+
+Another useful option is the C<verbose> (C<-v>) option to C<prove>. It
+prints the name of each test case as it is being run:
+
+ $ CATALYST_DEBUG=0 TEST_POD=1 prove --lib lib -v t
+
+
+=head1 RUNNING A SINGLE TEST
+
+You can also run a single script by appending its name to the C<prove>
+command. For example:
+
+ $ CATALYST_DEBUG=0 prove --lib lib t/01app.t
+
+Also note that you can also run tests directly from Perl without C<prove>.
+For example:
+
+ $ CATALYST_DEBUG=0 perl -Ilib t/01app.t
+
+
+=head1 ADDING YOUR OWN TEST SCRIPT
+
+Although the Catalyst helper scripts provide a basic level of checks
+"for free," testing can become significantly more helpful when you write
+your own script to exercise the various parts of your application. The
+L<Test::WWW::Mechanize::Catalyst|Test::WWW::Mechanize::Catalyst> module
+is very popular for writing these sorts of test cases. This module
+extends L<Test::WWW::Mechanize|Test::WWW::Mechanize> (and therefore
+L<WWW::Mechanize|WWW::Mechanize>) to allow you to automate the action of
+a user "clicking around" inside your application. It gives you all the
+benefits of testing on a live system without the messiness of having to
+use an actual web server, and a real person to do the clicking.
+
+To create a sample test case, open the C<t/live_app01.t> file in your
+editor and enter the following:
+
+ #!/usr/bin/perl
+
+ use strict;
+ use warnings;
+
+ # Load testing framework and use 'no_plan' to dynamically pick up
+ # all tests. Better to replace "'no_plan'" with "tests => 30" so it
+ # knows exactly how many tests need to be run (and will tell you if
+ # not), but 'no_plan' is nice for quick & dirty tests
+
+ use Test::More 'no_plan';
+
+ # Need to specify the name of your app as arg on next line
+ # Can also do:
+ # use Test::WWW::Mechanize::Catalyst "MyApp";
+
+ use ok "Test::WWW::Mechanize::Catalyst" => "MyApp";
+
+ # Create two 'user agents' to simulate two different users ('test01' & 'test02')
+ my $ua1 = Test::WWW::Mechanize::Catalyst->new;
+ my $ua2 = Test::WWW::Mechanize::Catalyst->new;
+
+ # Use a simplified for loop to do tests that are common to both users
+ # Use get_ok() to make sure we can hit the base URL
+ # Second arg = optional description of test (will be displayed for failed tests)
+ # Note that in test scripts you send everything to 'http://localhost'
+ $_->get_ok("http://localhost/", "Check redirect of base URL") for $ua1, $ua2;
+ # Use title_is() to check the contents of the <title>...</title> tags
+ $_->title_is("Login", "Check for login title") for $ua1, $ua2;
+ # Use content_contains() to match on text in the html body
+ $_->content_contains("You need to log in to use this application",
+ "Check we are NOT logged in") for $ua1, $ua2;
+
+ # Log in as each user
+ # Specify username and password on the URL
+ $ua1->get_ok("http://localhost/login?username=test01&password=mypass", "Login 'test01'");
+ $ua1->get_ok("http://localhost/login?username=test02&password=mypass", "Login 'test02'");
+
+ # Go back to the login page and it should show that we are already logged in
+ $_->get_ok("http://localhost/login", "Return to '/login'") for $ua1, $ua2;
+ $_->title_is("Login", "Check for login page") for $ua1, $ua2;
+ $_->content_contains("Please Note: You are already logged in as ",
+ "Check we ARE logged in" ) for $ua1, $ua2;
+
+ # 'Click' the 'Logout' link (see also 'text_regex' and 'url_regex' options)
+ $_->follow_link_ok({n => 4}, "Logout via first link on page") for $ua1, $ua2;
+ $_->title_is("Login", "Check for login title") for $ua1, $ua2;
+ $_->content_contains("You need to log in to use this application",
+ "Check we are NOT logged in") for $ua1, $ua2;
+
+ # Log back in
+ $ua1->get_ok("http://localhost/login?username=test01&password=mypass", "Login 'test01'");
+ $ua2->get_ok("http://localhost/login?username=test02&password=mypass", "Login 'test02'");
+ # Should be at the Book List page... do some checks to confirm
+ $_->title_is("Book List", "Check for book list title") for $ua1, $ua2;
+
+ $ua1->get_ok("http://localhost/books/list", "'test01' book list");
+ $ua1->get_ok("http://localhost/login", "Login Page");
+ $ua1->get_ok("http://localhost/books/list", "'test01' book list");
+
+ $_->content_contains("Book List", "Check for book list title") for $ua1, $ua2;
+ # Make sure the appropriate logout buttons are displayed
+ $_->content_contains("/logout\">User Logout</a>",
+ "Both users should have a 'User Logout'") for $ua1, $ua2;
+ $ua1->content_contains("/books/form_create\">Create</a>",
+ "Only 'test01' should have a create link");
+
+ $ua1->get_ok("http://localhost/books/list", "View book list as 'test01'");
+
+ # User 'test01' should be able to create a book with the "formless create" URL
+ $ua1->get_ok("http://localhost/books/url_create/TestTitle/2/4",
+ "'test01' formless create");
+ $ua1->title_is("Book Created", "Book created title");
+ $ua1->content_contains("Added book 'TestTitle'", "Check title added OK");
+ $ua1->content_contains("by 'Stevens'", "Check author added OK");
+ $ua1->content_contains("with a rating of 2.", "Check rating added");
+ # Try a regular expression to combine the previous 3 checks & account for whitespace
+ $ua1->content_like(qr/Added book 'TestTitle'\s+by 'Stevens'\s+with a rating of 2./, "Regex check");
+
+ # Make sure the new book shows in the list
+ $ua1->get_ok("http://localhost/books/list", "'test01' book list");
+ $ua1->title_is("Book List", "Check logged in and at book list");
+ $ua1->content_contains("Book List", "Book List page test");
+ $ua1->content_contains("TestTitle", "Look for 'TestTitle'");
+
+ # Make sure the new book can be deleted
+ # Get all the Delete links on the list page
+ my @delLinks = $ua1->find_all_links(text => 'Delete');
+ # Use the final link to delete the last book
+ $ua1->get_ok($delLinks[$#delLinks]->url, 'Delete last book');
+ # Check that delete worked
+ $ua1->content_contains("Book List", "Book List page test");
+ $ua1->content_contains("Book deleted", "Book was deleted");
+
+ # User 'test02' should not be able to add a book
+ $ua2->get_ok("http://localhost/books/url_create/TestTitle2/2/5", "'test02' add");
+ $ua2->content_contains("Unauthorized!", "Check 'test02' cannot add");
+
+The C<live_app.t> test cases uses copious comments to explain each step
+of the process. In addition to the techniques shown here, there are a
+variety of other methods available in
+L<Test::WWW::Mechanize::Catalyst|Test::WWW::Mechanize::Catalyst> (for
+example, regex-based matching). Consult the documentation for more
+detail.
+
+B<TIP>: For I<unit tests> vs. the "full application tests" approach used
+by L<Test::WWW::Mechanize::Catalyst|Test::WWW::Mechanize::Catalyst>, see
+L<Catalyst::Test|Catalyst::Test>.
+
+B<Note:> The test script does not test the C<form_create> and
+C<form_create_do> actions. That is left as an exercise for the reader
+(you should be able to complete that logic using the existing code as a
+template).
+
+To run the new test script, use a command such as:
+
+ $ CATALYST_DEBUG=0 prove --lib lib -v t/live_app01.t
+
+or
+
+ $ DBIC_TRACE=0 CATALYST_DEBUG=0 prove --lib lib -v t/live_app01.t
+
+Experiment with the C<DBIC_TRACE>, C<CATALYST_DEBUG> and C<-v>
+settings. If you find that there are errors, use the techniques
+discussed in the "Catalyst Debugging" section (Chapter 7) to isolate
+and fix any problems.
+
+If you want to run the test case under the Perl interactive debugger,
+try a command such as:
+
+ $ DBIC_TRACE=0 CATALYST_DEBUG=0 perl -d -Ilib t/live_app01.t
+
+Note that although this tutorial uses a single custom test case for
+simplicity, you may wish to break your tests into different files for
+better organization.
+
+B<TIP:> If you have a test case that fails, you will receive an error
+similar to the following:
+
+ # Failed test 'Check we are NOT logged in'
+ # in t/live_app01.t at line 31.
+ # searched: "\x{0a}<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Tran"...
+ # can't find: "You need to log in to use this application."
+
+Unfortunately, this only shows us the first 50 characters of the HTML
+returned by the request -- not enough to determine where the problem
+lies. A simple technique that can be used in such situations is to
+temporarily insert a line similar to the following right after the
+failed test:
+
+ diag $ua1->content;
+
+This will cause the full HTML returned by the request to be displayed.
+
+Another approach to see the full HTML content at the failure point in
+a series of tests would be to insert a "C<$DB::single=1;> right above
+the location of the failure and run the test under the perl debugger
+(with C<-d>) as shown above. Then you can use the debugger to explore
+the state of the application right before or after the failure.
+
+
+=head1 SUPPORTING BOTH PRODUCTION AND TEST DATABASES
+
+You may wish to leverage the techniques discussed in this tutorial to
+maintain both a "production database" for your live application and a
+"testing database" for your test cases. One advantage to
+L<Test::WWW::Mechanize::Catalyst|Test::WWW::Mechanize::Catalyst> is that
+it runs your full application; however, this can complicate things when
+you want to support multiple databases. One solution is to allow the
+database specification to be overridden with an environment variable.
+For example, open C<lib/MyApp/Model/DB.pm> in your editor and
+change the C<__PACKAGE__-E<gt>config(...> declaration to resemble:
+
+ my $dsn = $ENV{MYAPP_DSN} ||= 'dbi:SQLite:myapp.db';
+ __PACKAGE__->config(
+ schema_class => 'MyApp::Schema',
+ connect_info => [
+ $dsn,
+ ],
+ );
+
+Then, when you run your test case, you can use commands such as:
+
+ $ cp myapp.db myappTEST.db
+ $ CATALYST_DEBUG=0 MYAPP_DSN="dbi:SQLite:myappTEST.db" prove --lib lib -v t/live_app01.t
+
+This will modify the DSN only while the test case is running. If you
+launch your normal application without the C<MYAPP_DSN> environment
+variable defined, it will default to the same C<dbi:SQLite:myapp.db> as
+before.
+
+
+=head1 AUTHOR
+
+Kennedy Clark, C<hkclark at gmail.com>
+
+Please report any errors, issues or suggestions to the author. The
+most recent version of the Catalyst Tutorial can be found at
+L<http://dev.catalyst.perl.org/repos/Catalyst/Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/>.
+
+Copyright 2006-2008, Kennedy Clark, under Creative Commons License
+(L<http://creativecommons.org/licenses/by-sa/3.0/us/>).
+
Copied: Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/09_AdvancedCRUD (from rev 10274, Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/AdvancedCRUD)
Copied: Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/09_AdvancedCRUD/09_FormBuilder.pod (from rev 10274, Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/AdvancedCRUD/FormBuilder.pod)
===================================================================
--- Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/09_AdvancedCRUD/09_FormBuilder.pod (rev 0)
+++ Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/09_AdvancedCRUD/09_FormBuilder.pod 2009-05-24 22:30:47 UTC (rev 10277)
@@ -0,0 +1,60 @@
+=head1 NAME
+
+Catalyst::Manual::Tutorial::09_AdvancedCRUD::09_FormBuilder - Catalyst Tutorial - Chapter 9: Advanced CRUD - FormBuilder
+
+NOTE: This chapter of the tutorial is in progress. Feel free to
+volunteer to help out. :-)
+
+=head1 OVERVIEW
+
+This is B<Chapter 9 of 10> for the Catalyst tutorial.
+
+L<Tutorial Overview|Catalyst::Manual::Tutorial>
+
+=over 4
+
+=item 1
+
+L<Introduction|Catalyst::Manual::Tutorial::01_Intro>
+
+=item 2
+
+L<Catalyst Basics|Catalyst::Manual::Tutorial::02_CatalystBasics>
+
+=item 3
+
+L<More Catalyst Basics|Catalyst::Manual::Tutorial::03_MoreCatalystBasics>
+
+=item 4
+
+L<Basic CRUD|Catalyst::Manual::Tutorial::04_BasicCRUD>
+
+=item 5
+
+L<Authentication|Catalyst::Manual::Tutorial::05_Authentication>
+
+=item 6
+
+L<Authorization|Catalyst::Manual::Tutorial::06_Authorization>
+
+=item 7
+
+L<Debugging|Catalyst::Manual::Tutorial::07_Debugging>
+
+=item 8
+
+L<Testing|Catalyst::Manual::Tutorial::08_Testing>
+
+=item 9
+
+B<09_Advanced CRUD::09_FormBuilder>
+
+=item 10
+
+L<Appendices|Catalyst::Manual::Tutorial::10_Appendices>
+
+=back
+
+
+=head1 DESCRIPTION
+
Copied: Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/09_AdvancedCRUD/09_FormFu.pod (from rev 10274, Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/AdvancedCRUD/FormFu.pod)
===================================================================
--- Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/09_AdvancedCRUD/09_FormFu.pod (rev 0)
+++ Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/09_AdvancedCRUD/09_FormFu.pod 2009-05-24 22:30:47 UTC (rev 10277)
@@ -0,0 +1,675 @@
+=head1 NAME
+
+Catalyst::Manual::Tutorial::09_AdvancedCRUD::09_FormFu - Catalyst Tutorial - Chapter 9: Advanced CRUD - FormFu
+
+
+=head1 OVERVIEW
+
+This is B<Chapter 9 of 10> for the Catalyst tutorial.
+
+L<Tutorial Overview|Catalyst::Manual::Tutorial>
+
+=over 4
+
+=item 1
+
+L<Introduction|Catalyst::Manual::Tutorial::01_Intro>
+
+=item 2
+
+L<Catalyst Basics|Catalyst::Manual::Tutorial::02_CatalystBasics>
+
+=item 3
+
+L<More Catalyst Basics|Catalyst::Manual::Tutorial::03_MoreCatalystBasics>
+
+=item 4
+
+L<Basic CRUD|Catalyst::Manual::Tutorial::04_BasicCRUD>
+
+=item 5
+
+L<Authentication|Catalyst::Manual::Tutorial::05_Authentication>
+
+=item 6
+
+L<Authorization|Catalyst::Manual::Tutorial::06_Authorization>
+
+=item 7
+
+L<Debugging|Catalyst::Manual::Tutorial::07_Debugging>
+
+=item 8
+
+L<Testing|Catalyst::Manual::Tutorial::08_Testing>
+
+=item 9
+
+B<09_Advanced CRUD::09_FormFu>
+
+=item 10
+
+L<Appendices|Catalyst::Manual::Tutorial::10_Appendices>
+
+=back
+
+
+=head1 DESCRIPTION
+
+This portion of the tutorial explores L<HTML::FormFu|HTML::FormFu> and
+how it can be used to manage forms, perform validation of form input,
+as well as save and restore data to/from the database. This was written
+using HTML::FormFu version 0.03007.
+
+See
+L<Catalyst::Manual::Tutorial::09_AdvancedCRUD|Catalyst::Manual::Tutorial::09_AdvancedCRUD>
+for additional form management options other than
+L<HTML::FormFu|HTML::FormFu>.
+
+
+=head1 Install HTML::FormFu
+
+If you are following along in Debian 5, it turns out that some of the
+modules we need are not yet available as Debian packages at the time
+this was written. To install it with a combination of Debian packages
+and traditional CPAN modules, first use C<aptitude> to install most of
+the modules:
+
+we need to install the
+L<HTML::FormFu|HTML::FormFu> package:
+
+ sudo aptitude -y install libhtml-formfu-perl libmoose-perl \
+ libregexp-assemble-perl libhtml-formfu-model-dbic-perl
+
+ ...
+
+ sudo aptitude clean
+
+Then use the following command to install directly from CPAN the modules
+that aren't available as Debian packages:
+
+ sudo cpan Catalyst::Component::InstancePerContext Catalyst::Controller::HTML::FormFu
+
+
+=head1 HTML::FormFu FORM CREATION
+
+This section looks at how L<HTML::FormFu|HTML::FormFu> can be used to
+add additional functionality to the manually created form from Chapter 4.
+
+
+=head2 Inherit From Catalyst::Controller::HTML::FormFu
+
+First, change your C<lib/MyApp/Controller/Books.pm> to inherit from
+L<Catalyst::Controller::HTML::FormFu|Catalyst::Controller::HTML::FormFu>
+by changing the C<use parent> line from the default of:
+
+ use parent 'Catalyst::Controller';
+
+to use the FormFu base controller class:
+
+ use parent 'Catalyst::Controller::HTML::FormFu';
+
+
+=head2 Add Action to Display and Save the Form
+
+Open C<lib/MyApp/Controller/Books.pm> in your editor and add the
+following method:
+
+ =head2 formfu_create
+
+ Use HTML::FormFu to create a new book
+
+ =cut
+
+ sub formfu_create :Chained('base') :PathPart('formfu_create') :Args(0) :FormConfig {
+ my ($self, $c) = @_;
+
+ # Get the form that the :FormConfig attribute saved in the stash
+ my $form = $c->stash->{form};
+
+ # Check if the form has been submitted (vs. displaying the initial
+ # form) and if the data passed validation. "submitted_and_valid"
+ # is shorthand for "$form->submitted && !$form->has_errors"
+ if ($form->submitted_and_valid) {
+ # Create a new book
+ my $book = $c->model('DB::Book')->new_result({});
+ # Save the form data for the book
+ $form->model->update($book);
+ # Set a status message for the user
+ $c->flash->{status_msg} = 'Book created';
+ # Return to the books list
+ $c->response->redirect($c->uri_for($self->action_for('list')));
+ $c->detach;
+ } else {
+ # Get the authors from the DB
+ my @author_objs = $c->model("DB::Author")->all();
+ # Create an array of arrayrefs where each arrayref is an author
+ my @authors;
+ foreach (sort {$a->last_name cmp $b->last_name} @author_objs) {
+ push(@authors, [$_->id, $_->last_name]);
+ }
+ # Get the select added by the config file
+ my $select = $form->get_element({type => 'Select'});
+ # Add the authors to it
+ $select->options(\@authors);
+ }
+
+ # Set the template
+ $c->stash->{template} = 'books/formfu_create.tt2';
+ }
+
+
+=head2 Create a Form Config File
+
+Although C<HTML::FormFu> supports any configuration file handled by
+L<Config::Any|Config::Any>, most people tend to use YAML. First
+create a directory to hold your form configuration files:
+
+ mkdir -p root/forms/books
+
+Then create the file C<root/forms/books/formfu_create.yml> and enter the
+following text:
+
+ ---
+ # indicator is the field that is used to test for form submission
+ indicator: submit
+ # Start listing the form elements
+ elements:
+ # The first element will be a text field for the title
+ - type: Text
+ name: title
+ label: Title
+ # This is an optional 'mouse over' title pop-up
+ attributes:
+ title: Enter a book title here
+
+ # Another text field for the numeric rating
+ - type: Text
+ name: rating
+ label: Rating
+ attributes:
+ title: Enter a rating between 1 and 5 here
+
+ # Add a drop-down list for the author selection. Note that we will
+ # dynamically fill in all the authors from the controller but we
+ # could manually set items in the drop-list by adding this YAML code:
+ # options:
+ # - [ '1', 'Bastien' ]
+ # - [ '2', 'Nasseh' ]
+ - type: Select
+ name: authors
+ label: Author
+
+ # The submit button
+ - type: Submit
+ name: submit
+ value: Submit
+
+B<NOTE:> Copying and pasting YAML from perl documentation is sometimes
+tricky. See the L<Config::General Config for this tutorial> section of
+this document for a more foolproof config format.
+
+
+=head2 Update the CSS
+
+Edit C<root/static/css/main.css> and add the following lines to the bottom of
+the file:
+
+ ...
+ input {
+ display: block;
+ }
+ select {
+ display: block;
+ }
+ .submit {
+ padding-top: .5em;
+ display: block;
+ }
+
+These changes will display form elements vertically. Note that the
+existing definition of the C<.error> class is pulling the color scheme
+settings from the C<root/lib/config/col> file that was created by the
+TTSite helper. This allows control over the CSS color settings from a
+single location.
+
+
+=head2 Create a Template Page To Display The Form
+
+Open C<root/src/books/formfu_create.tt2> in your editor and enter the following:
+
+ [% META title = 'Create/Update Book' %]
+
+ [%# Render the HTML::FormFu Form %]
+ [% form %]
+
+ <p><a href="[% c.uri_for(c.controller.action_for('list')) %]">Return to book list</a></p>
+
+
+=head2 Add Links for Create and Update via C<HTML::FormFu>
+
+Open C<root/src/books/list.tt2> in your editor and add the following to
+the bottom of the existing file:
+
+ ...
+ <p>
+ HTML::FormFu:
+ <a href="[% c.uri_for(c.controller.action_for('formfu_create')) %]">Create</a>
+ </p>
+
+This adds a new link to the bottom of the book list page that we can
+use to easily launch our HTML::FormFu-based form.
+
+
+=head2 Test The HTML::FormFu Create Form
+
+Press C<Ctrl-C> to kill the previous server instance (if it's still
+running) and restart it:
+
+ $ script/myapp_server.pl
+
+Login as C<test01> (password: mypass). Once at the Book List page,
+click the new HTML::FormFu "Create" link at the bottom to display the
+form. Fill in the following values:
+
+ Title = "Internetworking with TCP/IP Vol. II"
+ Rating = "4"
+ Author = "Comer"
+
+Click the Submit button, and you will be returned to the Book List page
+with a "Book created" status message displayed.
+
+Also note that this implementation allows you to create books with any
+bogus information. Although we have constrained the authors with the
+drop-down list (note that this isn't bulletproof because we still have
+not prevented a user from "hacking" the form to specify other values),
+there are no restrictions on items such as the length of the title (for
+example, you can create a one-letter title) and the value of the rating
+(you can use any number you want, and even non-numeric values with
+SQLite). The next section will address this concern.
+
+B<Note:> Depending on the database you are using and how you established
+the columns in your tables, the database could obviously provide various
+levels of "type enforcement" on your data. The key point being made in
+the previous paragraph is that the I<web application> itself is not
+performing any validation.
+
+
+=head1 HTML::FormFu VALIDATION AND FILTERING
+
+Although the use of L<HTML::FormFu|HTML::FormFu> in the previous section
+did provide an automated mechanism to build the form, the real power of
+this module stems from functionality that can automatically validate and
+filter the user input. Validation uses constraints to be sure that
+users input appropriate data (for example, that the email field of a
+form contains a valid email address). Filtering can also be used to
+remove extraneous whitespace from fields or to escape meta-characters in
+user input.
+
+
+=head2 Add Constraints
+
+Open C<root/forms/books/formfu_create.yml> in your editor and update it
+to match:
+
+ ---
+ # indicator is the field that is used to test for form submission
+ indicator: submit
+ # Start listing the form elements
+ elements:
+ # The first element will be a text field for the title
+ - type: Text
+ name: title
+ label: Title
+ # This is an optional 'mouse over' title pop-up
+ attributes:
+ title: Enter a book title here
+ # Add constraints for the field
+ constraints:
+ # Force the length to be between 5 and 40 chars
+ - type: Length
+ min: 5
+ max: 40
+ # Override the default of 'Invalid input'
+ message: Length must be between 5 and 40 characters
+
+ # Another text field for the numeric rating
+ - type: Text
+ name: rating
+ label: Rating
+ attributes:
+ title: Enter a rating between 1 and 5 here
+ # Use Filter to clean up the input data
+ # Could use 'NonNumeric' below, but since Filters apply *before*
+ # constraints, it would conflict with the 'Integer' constraint below.
+ # So let's skip this and just use the constraint.
+ #filter:
+ # Remove everything except digits
+ #- NonNumeric
+ # Add constraints to the field
+ constraints:
+ # Make sure it's a number
+ - type: Integer
+ message: "Required. Digits only, please."
+ # Check the min & max values
+ - type: Range
+ min: 1
+ max: 5
+ message: "Must be between 1 and 5."
+
+ # Add a select list for the author selection. Note that we will
+ # dynamically fill in all the authors from the controller but we
+ # could manually set items in the select by adding this YAML code:
+ # options:
+ # - [ '1', 'Bastien' ]
+ # - [ '2', 'Nasseh' ]
+ - type: Select
+ name: authors
+ label: Author
+ # Convert the drop-down to a multi-select list
+ multiple: 1
+ # Display 3 entries (user can scroll to see others)
+ size: 3
+ # One could argue we don't need to do filters or constraints for
+ # a select list, but it's smart to do validation and sanity
+ # checks on this data in case a user "hacks" the input
+ # Add constraints to the field
+ constraints:
+ # Make sure it's a number
+ - Integer
+
+ # The submit button
+ - type: Submit
+ name: submit
+ value: Submit
+
+ # Global filters and constraints.
+ constraints:
+ # The user cannot leave any fields blank
+ - Required
+ # If not all fields are required, move the Required constraint to the
+ # fields that are
+ filter:
+ # Remove whitespace at both ends
+ - TrimEdges
+ # Escape HTML characters for safety
+ - HTMLEscape
+
+B<NOTE:> Copying and pasting YAML from perl documentation is sometimes
+tricky. See the L<Config::General Config for this tutorial> section of
+this document for a more foolproof config format.
+
+The main changes are:
+
+=over 4
+
+=item *
+
+The C<Select> element for C<authors> is changed from a single-select
+drop-down to a multi-select list by adding configuration for the
+C<multiple> and C<size> options in C<formfu_create.yml>.
+
+=item *
+
+Constraints are added to provide validation of the user input. See
+L<HTML::FormFu::Constraint|HTML::FormFu::Constraint> for other
+constraints that are available.
+
+=item *
+
+A variety of filters are run on every field to remove and escape
+unwanted input. See L<HTML::FormFu::Filter|HTML::FormFu::Filter>
+for more filter options.
+
+=back
+
+
+=head2 Try Out the Updated Form
+
+Press C<Ctrl-C> to kill the previous server instance (if it's still
+running) and restart it:
+
+ $ script/myapp_server.pl
+
+Make sure you are still logged in as C<test01> and try adding a book
+with various errors: title less than 5 characters, non-numeric rating, a
+rating of 0 or 6, etc. Also try selecting one, two, and zero authors.
+When you click Submit, the HTML::FormFu C<constraint> items will
+validate the logic and insert feedback as appropriate. Try adding blank
+spaces at the front or the back of the title and note that it will be
+removed.
+
+
+=head1 CREATE AND UPDATE/EDIT ACTION
+
+Let's expand the work done above to add an edit action. First, open
+C<lib/MyApp/Controller/Books.pm> and add the following method to the
+bottom:
+
+ =head2 formfu_edit
+
+ Use HTML::FormFu to update an existing book
+
+ =cut
+
+ sub formfu_edit :Chained('object') :PathPart('formfu_edit') :Args(0)
+ :FormConfig('books/formfu_create.yml') {
+ my ($self, $c) = @_;
+
+ # Get the specified book already saved by the 'object' method
+ my $book = $c->stash->{object};
+
+ # Make sure we were able to get a book
+ unless ($book) {
+ $c->flash->{error_msg} = "Invalid book -- Cannot edit";
+ $c->response->redirect($c->uri_for($self->action_for('list')));
+ $c->detach;
+ }
+
+ # Get the form that the :FormConfig attribute saved in the stash
+ my $form = $c->stash->{form};
+
+ # Check if the form has been submitted (vs. displaying the initial
+ # form) and if the data passed validation. "submitted_and_valid"
+ # is shorthand for "$form->submitted && !$form->has_errors"
+ if ($form->submitted_and_valid) {
+ # Save the form data for the book
+ $form->model->update($book);
+ # Set a status message for the user
+ $c->flash->{status_msg} = 'Book edited';
+ # Return to the books list
+ $c->response->redirect($c->uri_for($self->action_for('list')));
+ $c->detach;
+ } else {
+ # Get the authors from the DB
+ my @author_objs = $c->model("DB::Author")->all();
+ # Create an array of arrayrefs where each arrayref is an author
+ my @authors;
+ foreach (sort {$a->last_name cmp $b->last_name} @author_objs) {
+ push(@authors, [$_->id, $_->last_name]);
+ }
+ # Get the select added by the config file
+ my $select = $form->get_element({type => 'Select'});
+ # Add the authors to it
+ $select->options(\@authors);
+ # Populate the form with existing values from DB
+ $form->model->default_values($book);
+ }
+
+ # Set the template
+ $c->stash->{template} = 'books/formfu_create.tt2';
+ }
+
+Most of this code should look familiar to what we used in the
+C<formfu_create> method (in fact, we should probably centralize some of
+the common code in separate methods). The main differences are:
+
+=over 4
+
+=item *
+
+We have to manually specify the name of the FormFu .yml file as an
+argument to C<:FormConfig> because the name can no longer be
+automatically deduced from the name of our action/method (by default,
+FormFu would look for a file named C<books/formfu_edit.yml>).
+
+=item *
+
+We load the book object from the stash (found using the $id passed to
+the Chained object method)
+
+=item *
+
+We use C<$id> to look up the existing book from the database.
+
+=item *
+
+We make sure the book lookup returned a valid book. If not, we set
+the error message and return to the book list.
+
+=item *
+
+If the form has been submitted and passes validation, we skip creating a
+new book and just use C<$form-E<gt>model-E<gt>update> to update the existing
+book.
+
+=item *
+
+If the form is being displayed for the first time (or has failed
+validation and it being redisplayed), we use
+ C<$form-E<gt>model-E<gt>default_values> to populate the form with data from the
+database.
+
+=back
+
+Then, edit C<root/src/books/list.tt2> and add a new link below the
+existing "Delete" link that allows us to edit/update each existing book.
+The last E<lt>tdE<gt> cell in the book list table should look like the
+following:
+
+ ...
+ <td>
+ [% # Add a link to delete a book %]
+ <a href="[% c.uri_for(c.controller.action_for('delete'), [book.id]) %]">Delete</a>
+ [% # Add a link to edit a book %]
+ <a href="[% c.uri_for(c.controller.action_for('formfu_edit'), [book.id]) %]">Edit</a>
+ </td>
+ ...
+
+B<Note:> Only add two lines (the "Add a link to edit a book" comment
+and the href for C<formfu_edit>). Make sure you add it below the
+existing C<delete> link.
+
+
+=head2 Try Out the Edit/Update Feature
+
+Press C<Ctrl-C> to kill the previous server instance (if it's still
+running) and restart it:
+
+ $ script/myapp_server.pl
+
+Make sure you are still logged in as C<test01> and go to the
+L<http://localhost:3000/books/list> URL in your browser. Click the
+"Edit" link next to "Internetworking with TCP/IP Vol. II", change the
+rating to a 3, the "II" at end of the title to the number "2", add
+Stevens as a co-author (control-click), and click Submit. You will then
+be returned to the book list with a "Book edited" message at the top in
+green. Experiment with other edits to various books.
+
+
+=head2 More Things to Try
+
+You are now armed with enough knowledge to be dangerous. You can keep
+tweaking the example application; some things you might want to do:
+
+=over 4
+
+=item *
+
+Add an appropriate authorization check to the new Edit function.
+
+=item *
+
+Cleanup the List page so that the Login link only displays when the user
+isn't logged in and the Logout link only displays when a user is logged
+in.
+
+=item *
+
+Add a more sensible policy for when and how users and admins can do
+things in the CRUD cycle.
+
+=item *
+
+Support the CRUD cycle for authors.
+
+=back
+
+Or you can proceed to write your own application, which is probably the
+real reason you worked through this Tutorial in the first place.
+
+
+=head2 Config::General Config for this tutorial
+
+If you are having difficulty with YAML config above, please save the
+below into the file C<formfu_create.conf> and delete the
+C<formfu_create.yml> file. The below is in
+L<Config::General|Config::General> format which follows the syntax of
+Apache config files.
+
+ constraints Required
+ <elements>
+ <constraints>
+ min 5
+ max 40
+ type Length
+ message Length must be between 5 and 40 characters
+ </constraints>
+ filter TrimEdges
+ filter HTMLEscape
+ name title
+ type Text
+ label Title
+ <attributes>
+ title Enter a book title here
+ </attributes>
+ </elements>
+ <elements>
+ constraints Integer
+ filter TrimEdges
+ filter NonNumeric
+ name rating
+ type Text
+ label Rating
+ <attributes>
+ title Enter a rating between 1 and 5 here
+ </attributes>
+ </elements>
+ <elements>
+ constraints Integer
+ filter TrimEdges
+ filter HTMLEscape
+ name authors
+ type Select
+ label Author
+ multiple 1
+ size 3
+ </elements>
+ <elements>
+ value Submit
+ name submit
+ type Submit
+ </elements>
+ indicator submit
+
+
+=head1 AUTHOR
+
+Kennedy Clark, C<hkclark at gmail.com>
+
+Please report any errors, issues or suggestions to the author. The
+most recent version of the Catalyst Tutorial can be found at
+L<http://dev.catalyst.perl.org/repos/Catalyst/Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/>.
+
+Copyright 2006-2008, Kennedy Clark, under Creative Commons License
+(L<http://creativecommons.org/licenses/by-sa/3.0/us/>).
Deleted: Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/09_AdvancedCRUD/FormBuilder.pod
===================================================================
--- Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/AdvancedCRUD/FormBuilder.pod 2009-05-24 20:40:11 UTC (rev 10274)
+++ Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/09_AdvancedCRUD/FormBuilder.pod 2009-05-24 22:30:47 UTC (rev 10277)
@@ -1,60 +0,0 @@
-=head1 NAME
-
-Catalyst::Manual::Tutorial::AdvancedCRUD::FormBuilder - Catalyst Tutorial - Chapter 9: Advanced CRUD - FormBuilder
-
-NOTE: This chapter of the tutorial is in progress. Feel free to
-volunteer to help out. :-)
-
-=head1 OVERVIEW
-
-This is B<Chapter 9 of 10> for the Catalyst tutorial.
-
-L<Tutorial Overview|Catalyst::Manual::Tutorial>
-
-=over 4
-
-=item 1
-
-L<Introduction|Catalyst::Manual::Tutorial::Intro>
-
-=item 2
-
-L<Catalyst Basics|Catalyst::Manual::Tutorial::CatalystBasics>
-
-=item 3
-
-L<More Catalyst Basics|Catalyst::Manual::Tutorial::MoreCatalystBasics>
-
-=item 4
-
-L<Basic CRUD|Catalyst::Manual::Tutorial::BasicCRUD>
-
-=item 5
-
-L<Authentication|Catalyst::Manual::Tutorial::Authentication>
-
-=item 6
-
-L<Authorization|Catalyst::Manual::Tutorial::Authorization>
-
-=item 7
-
-L<Debugging|Catalyst::Manual::Tutorial::Debugging>
-
-=item 8
-
-L<Testing|Catalyst::Manual::Tutorial::Testing>
-
-=item 9
-
-B<Advanced CRUD>
-
-=item 10
-
-L<Appendices|Catalyst::Manual::Tutorial::Appendices>
-
-=back
-
-
-=head1 DESCRIPTION
-
Deleted: Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/09_AdvancedCRUD/FormFu.pod
===================================================================
--- Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/AdvancedCRUD/FormFu.pod 2009-05-24 20:40:11 UTC (rev 10274)
+++ Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/09_AdvancedCRUD/FormFu.pod 2009-05-24 22:30:47 UTC (rev 10277)
@@ -1,671 +0,0 @@
-=head1 NAME
-
-Catalyst::Manual::Tutorial::AdvancedCRUD::FormFu - Catalyst Tutorial - Chapter 9: Advanced CRUD - FormFu
-
-
-=head1 OVERVIEW
-
-This is B<Chapter 9 of 10> for the Catalyst tutorial.
-
-L<Tutorial Overview|Catalyst::Manual::Tutorial>
-
-=over 4
-
-=item 1
-
-L<Introduction|Catalyst::Manual::Tutorial::Intro>
-
-=item 2
-
-L<Catalyst Basics|Catalyst::Manual::Tutorial::CatalystBasics>
-
-=item 3
-
-L<More Catalyst Basics|Catalyst::Manual::Tutorial::MoreCatalystBasics>
-
-=item 4
-
-L<Basic CRUD|Catalyst::Manual::Tutorial::BasicCRUD>
-
-=item 5
-
-L<Authentication|Catalyst::Manual::Tutorial::Authentication>
-
-=item 6
-
-L<Authorization|Catalyst::Manual::Tutorial::Authorization>
-
-=item 7
-
-L<Debugging|Catalyst::Manual::Tutorial::Debugging>
-
-=item 8
-
-L<Testing|Catalyst::Manual::Tutorial::Testing>
-
-=item 9
-
-B<Advanced CRUD::FormFu>
-
-=item 10
-
-L<Appendices|Catalyst::Manual::Tutorial::Appendices>
-
-=back
-
-
-=head1 DESCRIPTION
-
-This portion of the tutorial explores L<HTML::FormFu|HTML::FormFu> and
-how it can be used to manage forms, perform validation of form input,
-as well as save and restore data to/from the database. This was written
-using HTML::FormFu version 0.03007.
-
-See
-L<Catalyst::Manual::Tutorial::AdvancedCRUD|Catalyst::Manual::Tutorial::AdvancedCRUD>
-for additional form management options other than
-L<HTML::FormFu|HTML::FormFu>.
-
-
-=head1 Install HTML::FormFu
-
-If you are following along in Debian 5, it turns out that some of the
-modules we need are not yet available as Debian packages at the time
-this was written. To install it with a combination of Debian packages
-and traditional CPAN modules, first use C<aptitude> to install most of
-the modules:
-
-we need to install the
-L<HTML::FormFu|HTML::FormFu> package:
-
- sudo aptitude -y install libhtml-formfu-perl libmoose-perl \
- libregexp-assemble-perl libhtml-formfu-model-dbic-perl
-
- ...
-
- sudo aptitude clean
-
-Then use the following command to install directly from CPAN the modules
-that aren't available as Debian packages:
-
- sudo cpan Catalyst::Component::InstancePerContext Catalyst::Controller::HTML::FormFu
-
-
-=head1 HTML::FormFu FORM CREATION
-
-This section looks at how L<HTML::FormFu|HTML::FormFu> can be used to
-add additional functionality to the manually created form from Chapter 4.
-
-
-=head2 Inherit From Catalyst::Controller::HTML::FormFu
-
-First, change your C<lib/MyApp/Controller/Books.pm> to inherit from
-L<Catalyst::Controller::HTML::FormFu|Catalyst::Controller::HTML::FormFu>
-by changing the C<use parent> line from the default of:
-
- use parent 'Catalyst::Controller';
-
-to use the FormFu base controller class:
-
- use parent 'Catalyst::Controller::HTML::FormFu';
-
-
-=head2 Add Action to Display and Save the Form
-
-Open C<lib/MyApp/Controller/Books.pm> in your editor and add the
-following method:
-
- =head2 formfu_create
-
- Use HTML::FormFu to create a new book
-
- =cut
-
- sub formfu_create :Chained('base') :PathPart('formfu_create') :Args(0) :FormConfig {
- my ($self, $c) = @_;
-
- # Get the form that the :FormConfig attribute saved in the stash
- my $form = $c->stash->{form};
-
- # Check if the form has been submitted (vs. displaying the initial
- # form) and if the data passed validation. "submitted_and_valid"
- # is shorthand for "$form->submitted && !$form->has_errors"
- if ($form->submitted_and_valid) {
- # Create a new book
- my $book = $c->model('DB::Books')->new_result({});
- # Save the form data for the book
- $form->model->update($book);
- # Set a status message for the user
- $c->flash->{status_msg} = 'Book created';
- # Return to the books list
- $c->response->redirect($c->uri_for($self->action_for('list')));
- $c->detach;
- } else {
- # Get the authors from the DB
- my @author_objs = $c->model("DB::Authors")->all();
- # Create an array of arrayrefs where each arrayref is an author
- my @authors;
- foreach (sort {$a->last_name cmp $b->last_name} @author_objs) {
- push(@authors, [$_->id, $_->last_name]);
- }
- # Get the select added by the config file
- my $select = $form->get_element({type => 'Select'});
- # Add the authors to it
- $select->options(\@authors);
- }
-
- # Set the template
- $c->stash->{template} = 'books/formfu_create.tt2';
- }
-
-
-=head2 Create a Form Config File
-
-Although C<HTML::FormFu> supports any configuration file handled by
-L<Config::Any|Config::Any>, most people tend to use YAML. First
-create a directory to hold your form configuration files:
-
- mkdir -p root/forms/books
-
-Then create the file C<root/forms/books/formfu_create.yml> and enter the
-following text:
-
- ---
- # indicator is the field that is used to test for form submission
- indicator: submit
- # Start listing the form elements
- elements:
- # The first element will be a text field for the title
- - type: Text
- name: title
- label: Title
- # This is an optional 'mouse over' title pop-up
- attributes:
- title: Enter a book title here
-
- # Another text field for the numeric rating
- - type: Text
- name: rating
- label: Rating
- attributes:
- title: Enter a rating between 1 and 5 here
-
- # Add a drop-down list for the author selection. Note that we will
- # dynamically fill in all the authors from the controller but we
- # could manually set items in the drop-list by adding this YAML code:
- # options:
- # - [ '1', 'Bastien' ]
- # - [ '2', 'Nasseh' ]
- - type: Select
- name: authors
- label: Author
-
- # The submit button
- - type: Submit
- name: submit
- value: Submit
-
-B<NOTE:> Copying and pasting YAML from perl documentation is sometimes
-tricky. See the L<Config::General Config for this tutorial> section of
-this document for a more foolproof config format.
-
-
-=head2 Update the CSS
-
-Edit C<root/static/css/main.css> and add the following lines to the bottom of
-the file:
-
- ...
- input {
- display: block;
- }
- select {
- display: block;
- }
- .submit {
- padding-top: .5em;
- display: block;
- }
-
-These changes will display form elements vertically. Note that the
-existing definition of the C<.error> class is pulling the color scheme
-settings from the C<root/lib/config/col> file that was created by the
-TTSite helper. This allows control over the CSS color settings from a
-single location.
-
-
-=head2 Create a Template Page To Display The Form
-
-Open C<root/src/books/formfu_create.tt2> in your editor and enter the following:
-
- [% META title = 'Create/Update Book' %]
-
- [%# Render the HTML::FormFu Form %]
- [% form %]
-
- <p><a href="[% c.uri_for(c.controller.action_for('list')) %]">Return to book list</a></p>
-
-
-=head2 Add Links for Create and Update via C<HTML::FormFu>
-
-Open C<root/src/books/list.tt2> in your editor and add the following to
-the bottom of the existing file:
-
- ...
- <p>
- HTML::FormFu:
- <a href="[% c.uri_for(c.controller.action_for('formfu_create')) %]">Create</a>
- </p>
-
-This adds a new link to the bottom of the book list page that we can
-use to easily launch our HTML::FormFu-based form.
-
-
-=head2 Test The HTML::FormFu Create Form
-
-Press C<Ctrl-C> to kill the previous server instance (if it's still
-running) and restart it:
-
- $ script/myapp_server.pl
-
-Login as C<test01> (password: mypass). Once at the Book List page,
-click the new HTML::FormFu "Create" link at the bottom to display the
-form. Fill in the following values: Title = "Internetworking with
-TCP/IP Vol. II", Rating = "4", and Author = "Comer". Click Submit,
-and you will be returned to the Book List page with a "Book created"
-status message displayed.
-
-Also note that this implementation allows you to can create books with
-bogus information. Although we have constrained the authors with the
-drop-down list (note that this isn't bulletproof because we still have
-not prevented a user from "hacking" the form to specify other values),
-there are no restrictions on items such as the length of the title (for
-example, you can create a one-letter title) and value for the rating
-(you can use any number you want, and even non-numeric values with
-SQLite). The next section will address this concern.
-
-B<Note:> Depending on the database you are using and how you established
-the columns in your tables, the database could obviously provide various
-levels of "type enforcement" on your data. The key point being made in
-the previous paragraph is that the I<web application> itself is not
-performing any validation.
-
-
-=head1 HTML::FormFu VALIDATION AND FILTERING
-
-Although the use of L<HTML::FormFu|HTML::FormFu> in the previous section
-did provide an automated mechanism to build the form, the real power of
-this module stems from functionality that can automatically validate and
-filter the user input. Validation uses constraints to be sure that
-users input appropriate data (for example, that the email field of a
-form contains a valid email address). Filtering can also be used to
-remove extraneous whitespace from fields or to escape meta-characters in
-user input.
-
-
-=head2 Add Constraints
-
-Open C<root/forms/books/formfu_create.yml> in your editor and update it
-to match:
-
- ---
- # indicator is the field that is used to test for form submission
- indicator: submit
- # Start listing the form elements
- elements:
- # The first element will be a text field for the title
- - type: Text
- name: title
- label: Title
- # This is an optional 'mouse over' title pop-up
- attributes:
- title: Enter a book title here
- # Add constraints for the field
- constraints:
- # Force the length to be between 5 and 40 chars
- - type: Length
- min: 5
- max: 40
- # Override the default of 'Invalid input'
- message: Length must be between 5 and 40 characters
-
- # Another text field for the numeric rating
- - type: Text
- name: rating
- label: Rating
- attributes:
- title: Enter a rating between 1 and 5 here
- # Use Filter to clean up the input data
- # Could use 'NonNumeric' below, but since Filters apply *before*
- # constraints, it would conflict with the 'Integer' constraint below.
- # So let's skip this and just use the constraint.
- #filter:
- # Remove everything except digits
- #- NonNumeric
- # Add constraints to the field
- constraints:
- # Make sure it's a number
- - type: Integer
- message: "Required. Digits only, please."
- # Check the min & max values
- - type: Range
- min: 1
- max: 5
- message: "Must be between 1 and 5."
-
- # Add a select list for the author selection. Note that we will
- # dynamically fill in all the authors from the controller but we
- # could manually set items in the select by adding this YAML code:
- # options:
- # - [ '1', 'Bastien' ]
- # - [ '2', 'Nasseh' ]
- - type: Select
- name: authors
- label: Author
- # Convert the drop-down to a multi-select list
- multiple: 1
- # Display 3 entries (user can scroll to see others)
- size: 3
- # One could argue we don't need to do filters or constraints for
- # a select list, but it's smart to do validation and sanity
- # checks on this data in case a user "hacks" the input
- # Add constraints to the field
- constraints:
- # Make sure it's a number
- - Integer
-
- # The submit button
- - type: Submit
- name: submit
- value: Submit
-
- # Global filters and constraints.
- constraints:
- # The user cannot leave any fields blank
- - Required
- # If not all fields are required, move the Required constraint to the
- # fields that are
- filter:
- # Remove whitespace at both ends
- - TrimEdges
- # Escape HTML characters for safety
- - HTMLEscape
-
-B<NOTE:> Copying and pasting YAML from perl documentation is sometimes
-tricky. See the L<Config::General Config for this tutorial> section of
-this document for a more foolproof config format.
-
-The main changes are:
-
-=over 4
-
-=item *
-
-The C<Select> element for C<authors> is changed from a single-select
-drop-down to a multi-select list by adding configuration for the
-C<multiple> and C<size> options in C<formfu_create.yml>.
-
-=item *
-
-Constraints are added to provide validation of the user input. See
-L<HTML::FormFu::Constraint|HTML::FormFu::Constraint> for other
-constraints that are available.
-
-=item *
-
-A variety of filters are run on every field to remove and escape
-unwanted input. See L<HTML::FormFu::Filter|HTML::FormFu::Filter>
-for more filter options.
-
-=back
-
-
-=head2 Try Out the Updated Form
-
-Press C<Ctrl-C> to kill the previous server instance (if it's still
-running) and restart it:
-
- $ script/myapp_server.pl
-
-Make sure you are still logged in as C<test01> and try adding a book
-with various errors: title less than 5 characters, non-numeric rating, a
-rating of 0 or 6, etc. Also try selecting one, two, and zero authors.
-When you click Submit, the HTML::FormFu C<constraint> items will
-validate the logic and insert feedback as appropriate. Try adding blank
-spaces at the front or the back of the title and note that it will be
-removed.
-
-
-=head1 CREATE AND UPDATE/EDIT ACTION
-
-Let's expand the work done above to add an edit action. First, open
-C<lib/MyApp/Controller/Books.pm> and add the following method to the
-bottom:
-
- =head2 formfu_edit
-
- Use HTML::FormFu to update an existing book
-
- =cut
-
- sub formfu_edit :Chained('object') :PathPart('formfu_edit') :Args(0)
- :FormConfig('books/formfu_create.yml') {
- my ($self, $c) = @_;
-
- # Get the specified book already saved by the 'object' method
- my $book = $c->stash->{object};
-
- # Make sure we were able to get a book
- unless ($book) {
- $c->flash->{error_msg} = "Invalid book -- Cannot edit";
- $c->response->redirect($c->uri_for($self->action_for('list')));
- $c->detach;
- }
-
- # Get the form that the :FormConfig attribute saved in the stash
- my $form = $c->stash->{form};
-
- # Check if the form has been submitted (vs. displaying the initial
- # form) and if the data passed validation. "submitted_and_valid"
- # is shorthand for "$form->submitted && !$form->has_errors"
- if ($form->submitted_and_valid) {
- # Save the form data for the book
- $form->model->update($book);
- # Set a status message for the user
- $c->flash->{status_msg} = 'Book edited';
- # Return to the books list
- $c->response->redirect($c->uri_for($self->action_for('list')));
- $c->detach;
- } else {
- # Get the authors from the DB
- my @author_objs = $c->model("DB::Authors")->all();
- # Create an array of arrayrefs where each arrayref is an author
- my @authors;
- foreach (sort {$a->last_name cmp $b->last_name} @author_objs) {
- push(@authors, [$_->id, $_->last_name]);
- }
- # Get the select added by the config file
- my $select = $form->get_element({type => 'Select'});
- # Add the authors to it
- $select->options(\@authors);
- # Populate the form with existing values from DB
- $form->model->default_values($book);
- }
-
- # Set the template
- $c->stash->{template} = 'books/formfu_create.tt2';
- }
-
-Most of this code should look familiar to what we used in the
-C<formfu_create> method (in fact, we should probably centralize some of
-the common code in separate methods). The main differences are:
-
-=over 4
-
-=item *
-
-We have to manually specify the name of the FormFu .yml file as an
-argument to C<:FormConfig> because the name can no longer be
-automatically deduced from the name of our action/method (by default,
-FormFu would look for a file named C<books/formfu_edit.yml>).
-
-=item *
-
-We load the book object from the stash (found using the $id passed to
-the Chained object method)
-
-=item *
-
-We use C<$id> to look up the existing book from the database.
-
-=item *
-
-We make sure the book lookup returned a valid book. If not, we set
-the error message and return to the book list.
-
-=item *
-
-If the form has been submitted and passes validation, we skip creating a
-new book and just use C<$form-E<gt>model-E<gt>update> to update the existing
-book.
-
-=item *
-
-If the form is being displayed for the first time (or has failed
-validation and it being redisplayed), we use
- C<$form-E<gt>model-E<gt>default_values> to populate the form with data from the
-database.
-
-=back
-
-Then, edit C<root/src/books/list.tt2> and add a new link below the
-existing "Delete" link that allows us to edit/update each existing book.
-The last E<lt>tdE<gt> cell in the book list table should look like the
-following:
-
- ...
- <td>
- [% # Add a link to delete a book %]
- <a href="[% c.uri_for(c.controller.action_for('delete'), [book.id]) %]">Delete</a>
- [% # Add a link to edit a book %]
- <a href="[% c.uri_for(c.controller.action_for('formfu_edit'), [book.id]) %]">Edit</a>
- </td>
- ...
-
-B<Note:> Only add two lines (the "Add a link to edit a book" comment
-and the href for C<formfu_edit>). Make sure you add it below the
-existing C<delete> link.
-
-
-=head2 Try Out the Edit/Update Feature
-
-Press C<Ctrl-C> to kill the previous server instance (if it's still
-running) and restart it:
-
- $ script/myapp_server.pl
-
-Make sure you are still logged in as C<test01> and go to the
-L<http://localhost:3000/books/list> URL in your browser. Click the
-"Edit" link next to "Internetworking with TCP/IP Vol. II", change the
-rating to a 3, the "II" at end of the title to the number "2", add
-Stevens as a co-author (control-click), and click Submit. You will then
-be returned to the book list with a "Book edited" message at the top in
-green. Experiment with other edits to various books.
-
-
-=head2 More Things to Try
-
-You are now armed with enough knowledge to be dangerous. You can keep
-tweaking the example application; some things you might want to do:
-
-=over 4
-
-=item *
-
-Add an appropriate authorization check to the new Edit function.
-
-=item *
-
-Cleanup the List page so that the Login link only displays when the user
-isn't logged in and the Logout link only displays when a user is logged
-in.
-
-=item *
-
-Add a more sensible policy for when and how users and admins can do
-things in the CRUD cycle.
-
-=item *
-
-Support the CRUD cycle for authors.
-
-=back
-
-Or you can proceed to write your own application, which is probably the
-real reason you worked through this Tutorial in the first place.
-
-
-=head2 Config::General Config for this tutorial
-
-If you are having difficulty with YAML config above, please save the
-below into the file C<formfu_create.conf> and delete the
-C<formfu_create.yml> file. The below is in
-L<Config::General|Config::General> format which follows the syntax of
-Apache config files.
-
- constraints Required
- <elements>
- <constraints>
- min 5
- max 40
- type Length
- message Length must be between 5 and 40 characters
- </constraints>
- filter TrimEdges
- filter HTMLEscape
- name title
- type Text
- label Title
- <attributes>
- title Enter a book title here
- </attributes>
- </elements>
- <elements>
- constraints Integer
- filter TrimEdges
- filter NonNumeric
- name rating
- type Text
- label Rating
- <attributes>
- title Enter a rating between 1 and 5 here
- </attributes>
- </elements>
- <elements>
- constraints Integer
- filter TrimEdges
- filter HTMLEscape
- name authors
- type Select
- label Author
- multiple 1
- size 3
- </elements>
- <elements>
- value Submit
- name submit
- type Submit
- </elements>
- indicator submit
-
-
-=head1 AUTHOR
-
-Kennedy Clark, C<hkclark at gmail.com>
-
-Please report any errors, issues or suggestions to the author. The
-most recent version of the Catalyst Tutorial can be found at
-L<http://dev.catalyst.perl.org/repos/Catalyst/Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/>.
-
-Copyright 2006-2008, Kennedy Clark, under Creative Commons License
-(L<http://creativecommons.org/licenses/by-sa/3.0/us/>).
Copied: Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/09_AdvancedCRUD.pod (from rev 10274, Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/AdvancedCRUD.pod)
===================================================================
--- Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/09_AdvancedCRUD.pod (rev 0)
+++ Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/09_AdvancedCRUD.pod 2009-05-24 22:30:47 UTC (rev 10277)
@@ -0,0 +1,99 @@
+=head1 NAME
+
+Catalyst::Manual::Tutorial::09_AdvancedCRUD - Catalyst Tutorial - Chapter 9: Advanced CRUD
+
+
+=head1 OVERVIEW
+
+This is B<Chapter 9 of 10> for the Catalyst tutorial.
+
+L<Tutorial Overview|Catalyst::Manual::Tutorial>
+
+=over 4
+
+=item 1
+
+L<Introduction|Catalyst::Manual::Tutorial::01_Intro>
+
+=item 2
+
+L<Catalyst Basics|Catalyst::Manual::Tutorial::02_CatalystBasics>
+
+=item 3
+
+L<More Catalyst Basics|Catalyst::Manual::Tutorial::03_MoreCatalystBasics>
+
+=item 4
+
+L<Basic CRUD|Catalyst::Manual::Tutorial::04_BasicCRUD>
+
+=item 5
+
+L<Authentication|Catalyst::Manual::Tutorial::05_Authentication>
+
+=item 6
+
+L<Authorization|Catalyst::Manual::Tutorial::06_Authorization>
+
+=item 7
+
+L<Debugging|Catalyst::Manual::Tutorial::07_Debugging>
+
+=item 8
+
+L<Testing|Catalyst::Manual::Tutorial::08_Testing>
+
+=item 9
+
+B<09_Advanced CRUD>
+
+=item 10
+
+L<Appendices|Catalyst::Manual::Tutorial::10_Appendices>
+
+=back
+
+
+=head1 DESCRIPTION
+
+This chapter of the tutorial explores more advanced functionality for
+Create, Read, Update, and Delete (CRUD) than we saw in Chapter 4. In
+particular, it looks at a number of techniques that can be useful for
+the Update portion of CRUD, such as automated form generation,
+validation of user-entered data, and automated transfer of data between
+forms and model objects.
+
+In keeping with the Catalyst (and Perl) spirit of flexibility, there are
+many different ways to approach advanced CRUD operations in a Catalyst
+environment. Therefore, this section of the tutorial allows you to pick
+from one of several modules that that cover different form management
+tools. Select one or more options from the list below.
+
+=head1 ADVANCED CRUD OPTIONS
+
+=over 4
+
+=item *
+
+L<FormFu|Catalyst::Manual::Tutorial::09_AdvancedCRUD::09_FormFu>
+
+=item *
+
+L<FormBuilder|Catalyst::Manual::Tutorial::09_AdvancedCRUD::09_FormBuilder>
+
+=back
+
+B<NOTE:> Please contact the author if you would like to assist with
+writing a new module.
+
+
+=head1 AUTHOR
+
+Kennedy Clark, C<hkclark at gmail.com>
+
+Please report any errors, issues or suggestions to the author. The
+most recent version of the Catalyst Tutorial can be found at
+L<http://dev.catalyst.perl.org/repos/Catalyst/Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/>.
+
+Copyright 2006-2008, Kennedy Clark, under Creative Commons License
+(L<http://creativecommons.org/licenses/by-sa/3.0/us/>).
Copied: Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/10_Appendices.pod (from rev 10274, Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/Appendices.pod)
===================================================================
--- Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/10_Appendices.pod (rev 0)
+++ Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/10_Appendices.pod 2009-05-24 22:30:47 UTC (rev 10277)
@@ -0,0 +1,784 @@
+=head1 NAME
+
+Catalyst::Manual::Tutorial::10_Appendices - Catalyst Tutorial - Chapter 10: Appendices
+
+
+=head1 OVERVIEW
+
+This is B<Chapter 10 of 10> for the Catalyst tutorial.
+
+L<Tutorial Overview|Catalyst::Manual::Tutorial>
+
+=over 4
+
+=item 1
+
+L<Introduction|Catalyst::Manual::Tutorial::01_Intro>
+
+=item 2
+
+L<Catalyst Basics|Catalyst::Manual::Tutorial::02_CatalystBasics>
+
+=item 3
+
+L<More Catalyst Basics|Catalyst::Manual::Tutorial::03_MoreCatalystBasics>
+
+=item 4
+
+L<Basic CRUD|Catalyst::Manual::Tutorial::04_BasicCRUD>
+
+=item 5
+
+L<Authentication|Catalyst::Manual::Tutorial::05_Authentication>
+
+=item 6
+
+L<Authorization|Catalyst::Manual::Tutorial::06_Authorization>
+
+=item 7
+
+L<Debugging|Catalyst::Manual::Tutorial::07_Debugging>
+
+=item 8
+
+L<Testing|Catalyst::Manual::Tutorial::08_Testing>
+
+=item 9
+
+L<Advanced CRUD|Catalyst::Manual::Tutorial::09_AdvancedCRUD>
+
+=item 10
+
+B<10_Appendices>
+
+=back
+
+
+=head1 DESCRIPTION
+
+This chapter of the tutorial provides supporting information relevant to
+the Catalyst tutorial.
+
+
+=head1 APPENDIX 1: CUT AND PASTE FOR POD-BASED EXAMPLES
+
+You may notice that Pod indents example code with four spaces. This
+section provides some quick advice to "un-indent" this text in common
+editors.
+
+=head2 "Un-indenting" with Vi/Vim
+
+When cutting and pasting multi-line text from Pod-based documents, the
+following vi/vim regexs can be helpful to "un-indent" the inserted text
+(do NOT type the quotes, they are only included to show spaces in the
+regex patterns). I<Note that all 3 of the regexs end in 4 spaces>:
+
+=over 4
+
+=item *
+
+":0,$s/^ "
+
+Removes four leading spaces from the entire file (from the first line,
+C<0>, to the last line, C<$>).
+
+=item *
+
+"%s/^ "
+
+A shortcut for the previous item (C<%> specifies the entire file; so
+this removes four leading spaces from every line).
+
+=item *
+
+":.,$s/^ "
+
+Removes the first four spaces from the line the cursor is on at the time
+the regex command is executed (".") to the last line of the file.
+
+=item *
+
+":.,44s/^ "
+
+Removes four leading space from the current line through line 44
+(obviously adjust the C<44> to the appropriate value in your example).
+
+=back
+
+=head2 "Un-indenting" with Emacs
+
+Although the author has not used Emacs for many years (apologies to
+the Emacs fans out there), here is a quick hint to get you started. To
+replace the leading spaces of every line in a file, use:
+
+ M-x replace-regexp<RET>
+ Replace regexp: ^ <RET>
+ with: <RET>
+
+All of that will occur on the single line at the bottom of your screen.
+Note that "<RET>" represents the return key/enter. Also, there are
+four spaces after the "^" on the "Replace regexp:" line and no spaces
+entered on the last line.
+
+You can limit the replacement operation by selecting text first (depending
+on your version of Emacs, you can either use the mouse or experiment with
+commands such as C<C-SPC> to set the mark at the cursor location and
+C<C-E<lt>> and C<C-E<gt>> to set the mark at the beginning and end of the
+file respectively.
+
+Also, Stefan Kangas sent in the following tip about an alternate
+approach using the command C<indent-region> to redo the indentation
+for the currently selected region (adhering to indent rules in the
+current major mode). You can run the command by typing M-x indent-
+region or pressing the default keybinding C-M-\ in cperl-mode.
+Additional details can be found here:
+
+L<http://www.gnu.org/software/emacs/manual/html_node/emacs/Indentation-Comman>
+
+
+=head1 APPENDIX 2: USING POSTGRESQL AND MYSQL
+
+The main database used in this tutorial is the very simple yet powerful
+SQLite. This section provides information that can be used to "convert"
+the tutorial to use PostgreSQL and MySQL. However, note that part of
+the beauty of the MVC architecture is that very little database-specific
+code is spread throughout the system (at least when MVC is "done
+right"). Consequently, converting from one database to another is
+relatively painless with most Catalyst applications. In general, you
+just need to adapt the schema definition C<.sql> file you use to
+initialize your database and adjust a few configuration parameters.
+
+Also note that the purpose of the data definition statements for this
+section are not designed to take maximum advantage of the various
+features in each database for issues such as referential integrity and
+field types/constraints.
+
+
+=head2 PostgreSQL
+
+Use the following steps to adapt the tutorial to PostgreSQL. Thanks
+to Caelum (Rafael Kitover) for assistance with the most recent
+updates, and Louis Moore, Marcello Romani and Tom Lanyon for help with
+earlier versions.
+
+=over 4
+
+=item *
+
+Chapter 3: More Catalyst Basics
+
+=over 4
+
+=item *
+
+Install the PostgreSQL server and client and DBD::Pg:
+
+If you are following along in Debian 5, you can quickly install these
+items via this command:
+
+ sudo aptitude install postgresql libdbd-pg-perl libdatetime-format-pg-perl
+
+To configure the permissions, you can open
+C</etc/postgresql/8.3/main/pg_hba.conf> and change this line (near the
+bottom):
+
+ # "local" is for Unix domain socket connections only
+ local all all ident sameuser
+
+to:
+
+ # "local" is for Unix domain socket connections only
+ local all all trust
+
+And then restart PostgreSQL:
+
+ sudo /etc/init.d/postgresql-8.3 restart
+
+
+=item *
+
+Create the database and a user for the database (note that we are
+using "E<lt>catalystE<gt>" to represent the hidden password of
+"catalyst"):
+
+ $ sudo -u postgres createuser -P catappuser
+ Enter password for new role: <catalyst>
+ Enter it again: <catalyst>
+ Shall the new role be a superuser? (y/n) n
+ Shall the new role be allowed to create databases? (y/n) n
+ Shall the new role be allowed to create more new roles? (y/n) n
+ CREATE ROLE
+ $ sudo -u postgres createdb -O catappuser catappdb
+ CREATE DATABASE
+
+=item *
+
+Create the C<.sql> file and load the data:
+
+=over 4
+
+=item *
+
+Open the C<myapp01_psql.sql> in your editor and enter:
+
+ --
+ -- Drops just in case you are reloading
+ ---
+ DROP TABLE IF EXISTS books CASCADE;
+ DROP TABLE IF EXISTS authors CASCADE;
+ DROP TABLE IF EXISTS book_authors CASCADE;
+ DROP TABLE IF EXISTS users CASCADE;
+ DROP TABLE IF EXISTS roles CASCADE;
+ DROP TABLE IF EXISTS user_roles CASCADE;
+
+ --
+ -- Create a very simple database to hold book and author information
+ --
+ CREATE TABLE books (
+ id SERIAL PRIMARY KEY,
+ title TEXT ,
+ rating INTEGER,
+ -- Manually add these later
+ -- created TIMESTAMP NOT NULL DEFAULT now(),
+ -- updated TIMESTAMP
+ );
+
+ CREATE TABLE authors (
+ id SERIAL PRIMARY KEY,
+ first_name TEXT,
+ last_name TEXT
+ );
+
+ -- 'book_authors' is a many-to-many join table between books & authors
+ CREATE TABLE book_authors (
+ book_id INTEGER REFERENCES books(id) ON DELETE CASCADE ON UPDATE CASCADE,
+ author_id INTEGER REFERENCES authors(id) ON DELETE CASCADE ON UPDATE CASCADE,
+ PRIMARY KEY (book_id, author_id)
+ );
+
+ ---
+ --- Load some sample data
+ ---
+ INSERT INTO books (title, rating) VALUES ('CCSP SNRS Exam Certification Guide', 5);
+ INSERT INTO books (title, rating) VALUES ('TCP/IP Illustrated, Volume 1', 5);
+ INSERT INTO books (title, rating) VALUES ('Internetworking with TCP/IP Vol.1', 4);
+ INSERT INTO books (title, rating) VALUES ('Perl Cookbook', 5);
+ INSERT INTO books (title, rating) VALUES ('Designing with Web Standards', 5);
+ INSERT INTO authors (first_name, last_name) VALUES ('Greg', 'Bastien');
+ INSERT INTO authors (first_name, last_name) VALUES ('Sara', 'Nasseh');
+ INSERT INTO authors (first_name, last_name) VALUES ('Christian', 'Degu');
+ INSERT INTO authors (first_name, last_name) VALUES ('Richard', 'Stevens');
+ INSERT INTO authors (first_name, last_name) VALUES ('Douglas', 'Comer');
+ INSERT INTO authors (first_name, last_name) VALUES ('Tom', 'Christiansen');
+ INSERT INTO authors (first_name, last_name) VALUES ('Nathan', 'Torkington');
+ INSERT INTO authors (first_name, last_name) VALUES ('Jeffrey', 'Zeldman');
+ INSERT INTO book_authors VALUES (1, 1);
+ INSERT INTO book_authors VALUES (1, 2);
+ INSERT INTO book_authors VALUES (1, 3);
+ INSERT INTO book_authors VALUES (2, 4);
+ INSERT INTO book_authors VALUES (3, 5);
+ INSERT INTO book_authors VALUES (4, 6);
+ INSERT INTO book_authors VALUES (4, 7);
+ INSERT INTO book_authors VALUES (5, 8);
+
+=item *
+
+Load the data:
+
+ $ psql -U catappuser -W catappdb -f myapp01_psql.sql
+ Password for user catappuser:
+ psql:myapp01_psql.sql:8: NOTICE: CREATE TABLE will create implicit sequence "books_id_seq" for serial column "books.id"
+ psql:myapp01_psql.sql:8: NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "books_pkey" for table "books"
+ CREATE TABLE
+ psql:myapp01_psql.sql:15: NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "book_authors_pkey" for table "book_authors"
+ CREATE TABLE
+ psql:myapp01_psql.sql:21: NOTICE: CREATE TABLE will create implicit sequence "authors_id_seq" for serial column "authors.id"
+ psql:myapp01_psql.sql:21: NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "authors_pkey" for table "authors"
+ CREATE TABLE
+ INSERT 0 1
+ INSERT 0 1
+ INSERT 0 1
+ ...
+
+=item *
+
+Make sure the data loaded correctly:
+
+ $ psql -U catappuser -W catappdb
+ Password for user catappuser: <catalyst>
+ Welcome to psql 8.3.7, the PostgreSQL interactive terminal.
+
+ Type: \copyright for distribution terms
+ \h for help with SQL commands
+ \? for help with psql commands
+ \g or terminate with semicolon to execute query
+ \q to quit
+
+ catappdb=> \dt
+ List of relations
+ Schema | Name | Type | Owner
+ --------+--------------+-------+------------
+ public | authors | table | catappuser
+ public | book_authors | table | catappuser
+ public | books | table | catappuser
+ (3 rows)
+
+ catappdb=> select * from books;
+ id | title | rating
+ ----+------------------------------------+--------
+ 1 | CCSP SNRS Exam Certification Guide | 5
+ 2 | TCP/IP Illustrated, Volume 1 | 5
+ 3 | Internetworking with TCP/IP Vol.1 | 4
+ 4 | Perl Cookbook | 5
+ 5 | Designing with Web Standards | 5
+ (5 rows)
+
+ catappdb=>
+
+=back
+
+=item *
+
+After the steps where you:
+
+ edit lib/MyApp.pm
+
+ create lib/MyAppDB.pm
+
+ create lib/MyAppDB/Book.pm
+
+ create lib/MyAppDB/Author.pm
+
+ create lib/MyAppDB/BookAuthor.pm
+
+
+=item *
+
+Generate the model using the Catalyst "_create.pl" script:
+
+ $ rm lib/MyApp/Model/DB.pm # Delete just in case already there
+ $ script/myapp_create.pl model DB DBIC::Schema MyApp::Schema \
+ create=static components=TimeStamp,EncodedColumn \
+ 'dbi:Pg:dbname=catappdb' 'catappuser' 'catalyst' '{ AutoCommit => 1 }'
+
+=back
+
+=item *
+
+Chapter 4: Basic CRUD
+
+Add Datetime Columns to Our Existing Books Table
+
+ $ psql -U catappuser -W catappdb
+ ...
+ catappdb=> ALTER TABLE books ADD created TIMESTAMP NOT NULL DEFAULT now();
+ ALTER TABLE
+ catappdb=> ALTER TABLE books ADD updated TIMESTAMP;
+ ALTER TABLE
+ catappdb=> \q
+
+Re-generate the model using the Catalyst "_create.pl" script:
+
+ $ script/myapp_create.pl model DB DBIC::Schema MyApp::Schema \
+ create=static components=TimeStamp,EncodedColumn \
+ 'dbi:Pg:dbname=catappdb' 'catappuser' 'catalyst' '{ AutoCommit => 1 }'
+
+
+=item *
+
+Chapter 5: Authentication
+
+=over 4
+
+=item *
+
+Create the C<.sql> file for the user/roles data:
+
+Open C<myapp02_psql.sql> in your editor and enter:
+
+ --
+ -- Add users and roles tables, along with a many-to-many join table
+ --
+
+ CREATE TABLE users (
+ id SERIAL PRIMARY KEY,
+ username TEXT,
+ password TEXT,
+ email_address TEXT,
+ first_name TEXT,
+ last_name TEXT,
+ active INTEGER
+ );
+
+ CREATE TABLE roles (
+ id SERIAL PRIMARY KEY,
+ role TEXT
+ );
+
+ CREATE TABLE user_roles (
+ user_id INTEGER REFERENCES users(id) ON DELETE CASCADE ON UPDATE CASCADE,
+ role_id INTEGER REFERENCES roles(id) ON DELETE CASCADE ON UPDATE CASCADE,
+ PRIMARY KEY (user_id, role_id)
+ );
+
+ --
+ -- Load up some initial test data
+ --
+ INSERT INTO users (username, password, email_address, first_name, last_name, active)
+ VALUES ('test01', 'mypass', 't01 at na.com', 'Joe', 'Blow', 1);
+ INSERT INTO users (username, password, email_address, first_name, last_name, active)
+ VALUES ('test02', 'mypass', 't02 at na.com', 'Jane', 'Doe', 1);
+ INSERT INTO users (username, password, email_address, first_name, last_name, active)
+ VALUES ('test03', 'mypass', 't03 at na.com', 'No', 'Go', 0);
+ INSERT INTO roles (role) VALUES ('user');
+ INSERT INTO roles (role) VALUES ('admin');
+ INSERT INTO user_roles VALUES (1, 1);
+ INSERT INTO user_roles VALUES (1, 2);
+ INSERT INTO user_roles VALUES (2, 1);
+ INSERT INTO user_roles VALUES (3, 1);
+
+=item *
+
+Load the data:
+
+ $ psql -U catappuser -W catappdb -f myapp02_psql.sql
+ Password for user catappuser: <catalyst>
+ psql:myapp02_psql.sql:13: NOTICE: CREATE TABLE will create implicit sequence "users_id_seq" for serial column "users.id"
+ psql:myapp02_psql.sql:13: NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "users_pkey" for table "users"
+ CREATE TABLE
+ psql:myapp02_psql.sql:18: NOTICE: CREATE TABLE will create implicit sequence "roles_id_seq" for serial column "roles.id"
+ psql:myapp02_psql.sql:18: NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "roles_pkey" for table "roles"
+ CREATE TABLE
+ psql:myapp02_psql.sql:24: NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "user_roles_pkey" for table "user_roles"
+ CREATE TABLE
+ INSERT 0 1
+ INSERT 0 1
+ INSERT 0 1
+ INSERT 0 1
+ INSERT 0 1
+ INSERT 0 1
+ INSERT 0 1
+ INSERT 0 1
+ INSERT 0 1
+
+Confirm with:
+
+ $ psql -U catappuser -W catappdb -c "select * from users"
+ Password for user catappuser: <catalyst>
+ id | username | password | email_address | first_name | last_name | active
+ ----+----------+----------+---------------+------------+-----------+--------
+ 1 | test01 | mypass | t01 at na.com | Joe | Blow | 1
+ 2 | test02 | mypass | t02 at na.com | Jane | Doe | 1
+ 3 | test03 | mypass | t03 at na.com | No | Go | 0
+ (3 rows)
+
+
+=item *
+
+Modify C<set_hashed_passwords.pl> to match the following (the only difference
+is the C<connect> line):
+
+ #!/usr/bin/perl
+
+ use strict;
+ use warnings;
+
+ use MyApp::Schema;
+
+ my $schema = MyApp::Schema->connect('dbi:Pg:dbname=catappdb', 'catappuser', 'catalyst');
+
+ my @users = $schema->resultset('Users')->all;
+
+ foreach my $user (@users) {
+ $user->password('mypass');
+ $user->update;
+ }
+
+Run the C<set_hashed_passwords.pl> as per the "normal" flow of the
+tutorial:
+
+ $ perl -Ilib set_hashed_passwords.pl
+
+You can verify that it worked with this command:
+
+ $ psql -U catappuser -W catappdb -c "select * from users"
+
+
+=back
+
+=back
+
+
+=head2 MySQL
+
+B<NOTE:> This section is out of data with the rest of the tutorial.
+Consider using SQLite or PostgreSQL since they are current.
+
+Use the following steps to adapt the tutorial to MySQL. Thanks to Jim
+Howard for the help.
+
+=over 4
+
+=item *
+
+Chapter 3: Catalyst Basics
+
+=over 4
+
+=item *
+
+Install the required software:
+
+=over 4
+
+=item *
+
+The MySQL database server and client utility.
+
+=item *
+
+The Perl C<DBD::MySQL> module
+
+=back
+
+For CentOS users (see
+L<Catalyst::Manual::Installation::CentOS4|Catalyst::Manual::Installation::CentOS4>),
+you can use the following commands to install the software and start the MySQL
+daemon:
+
+ yum -y install mysql mysql-server
+ service mysqld start
+
+=item *
+
+Create the database and set the permissions:
+
+ $ mysql
+ Welcome to the MySQL monitor. Commands end with ; or \g.
+ Your MySQL connection id is 2 to server version: 4.1.20
+
+ Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
+
+ mysql> create database myapp;
+ Query OK, 1 row affected (0.01 sec)
+
+ mysql> grant all on myapp.* to tutorial@'localhost';
+ Query OK, 0 rows affected (0.00 sec)
+
+ mysql> flush privileges;
+ Query OK, 0 rows affected (0.00 sec)
+
+ mysql> quit
+ Bye
+
+=item *
+
+Create the C<.sql> file and load the data:
+
+=over 4
+
+=item *
+
+Open the C<myapp01_mysql.sql> in your editor and enter:
+
+ --
+ -- Create a very simple database to hold book and author information
+ --
+ DROP TABLE IF EXISTS books;
+ DROP TABLE IF EXISTS book_authors;
+ DROP TABLE IF EXISTS authors;
+ CREATE TABLE books (
+ id INT(11) PRIMARY KEY AUTO_INCREMENT,
+ title TEXT ,
+ rating INT(11)
+ );
+ -- 'book_authors' is a many-to-many join table between books & authors
+ CREATE TABLE book_authors (
+ book_id INT(11),
+ author_id INT(11),
+ PRIMARY KEY (book_id, author_id)
+ );
+ CREATE TABLE authors (
+ id INT(11) PRIMARY KEY AUTO_INCREMENT,
+ first_name TEXT,
+ last_name TEXT
+ );
+ ---
+ --- Load some sample data
+ ---
+ INSERT INTO books VALUES (1, 'CCSP SNRS Exam Certification Guide', 5);
+ INSERT INTO books VALUES (2, 'TCP/IP Illustrated, Volume 1', 5);
+ INSERT INTO books VALUES (3, 'Internetworking with TCP/IP Vol.1', 4);
+ INSERT INTO books VALUES (4, 'Perl Cookbook', 5);
+ INSERT INTO books VALUES (5, 'Designing with Web Standards', 5);
+ INSERT INTO authors VALUES (1, 'Greg', 'Bastien');
+ INSERT INTO authors VALUES (2, 'Sara', 'Nasseh');
+ INSERT INTO authors VALUES (3, 'Christian', 'Degu');
+ INSERT INTO authors VALUES (4, 'Richard', 'Stevens');
+ INSERT INTO authors VALUES (5, 'Douglas', 'Comer');
+ INSERT INTO authors VALUES (6, 'Tom', 'Christiansen');
+ INSERT INTO authors VALUES (7, ' Nathan', 'Torkington');
+ INSERT INTO authors VALUES (8, 'Jeffrey', 'Zeldman');
+ INSERT INTO book_authors VALUES (1, 1);
+ INSERT INTO book_authors VALUES (1, 2);
+ INSERT INTO book_authors VALUES (1, 3);
+ INSERT INTO book_authors VALUES (2, 4);
+ INSERT INTO book_authors VALUES (3, 5);
+ INSERT INTO book_authors VALUES (4, 6);
+ INSERT INTO book_authors VALUES (4, 7);
+ INSERT INTO book_authors VALUES (5, 8);
+
+=item *
+
+Load the data:
+
+ mysql -ututorial myapp < myapp01_mysql.sql
+
+=item *
+
+Make sure the data loaded correctly:
+
+ $ mysql -ututorial myapp
+ Reading table information for completion of table and column names
+ You can turn off this feature to get a quicker startup with -A
+
+ Welcome to the MySQL monitor. Commands end with ; or \g.
+ Your MySQL connection id is 4 to server version: 4.1.20
+
+ Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
+
+ mysql> show tables;
+ +-----------------+
+ | Tables_in_myapp |
+ +-----------------+
+ | authors |
+ | book_authors |
+ | books |
+ +-----------------+
+ 3 rows in set (0.00 sec)
+
+ mysql> select * from books;
+ +----+------------------------------------+--------+
+ | id | title | rating |
+ +----+------------------------------------+--------+
+ | 1 | CCSP SNRS Exam Certification Guide | 5 |
+ | 2 | TCP/IP Illustrated, Volume 1 | 5 |
+ | 3 | Internetworking with TCP/IP Vol.1 | 4 |
+ | 4 | Perl Cookbook | 5 |
+ | 5 | Designing with Web Standards | 5 |
+ +----+------------------------------------+--------+
+ 5 rows in set (0.00 sec)
+
+ mysql>
+
+=back
+
+=item *
+
+Update the model:
+
+=over 4
+
+=item *
+
+Delete the existing model:
+
+ rm lib/MyApp/Model/MyAppDB.pm
+
+=item *
+
+Regenerate the model using the Catalyst "_create.pl" script:
+
+ script/myapp_create.pl model DB DBIC::Schema MyApp::Schema \
+ dbi:mysql:myapp '_username_here_' '_password_here_' '{ AutoCommit => 1 }'
+
+=back
+
+=back
+
+=item *
+
+Chapter 5: Authentication
+
+=over 4
+
+=item *
+
+Create the C<.sql> file for the user/roles data:
+
+Open C<myapp02_mysql.sql> in your editor and enter:
+
+ --
+ -- Add users and roles tables, along with a many-to-many join table
+ --
+ CREATE TABLE users (
+ id INT(11) PRIMARY KEY,
+ username TEXT,
+ password TEXT,
+ email_address TEXT,
+ first_name TEXT,
+ last_name TEXT,
+ active INT(11)
+ );
+ CREATE TABLE roles (
+ id INTEGER PRIMARY KEY,
+ role TEXT
+ );
+ CREATE TABLE user_roles (
+ user_id INT(11),
+ role_id INT(11),
+ PRIMARY KEY (user_id, role_id)
+ );
+ --
+ -- Load up some initial test data
+ --
+ INSERT INTO users VALUES (1, 'test01', 'mypass', 't01 at na.com', 'Joe', 'Blow', 1);
+ INSERT INTO users VALUES (2, 'test02', 'mypass', 't02 at na.com', 'Jane', 'Doe', 1);
+ INSERT INTO users VALUES (3, 'test03', 'mypass', 't03 at na.com', 'No', 'Go', 0);
+ INSERT INTO roles VALUES (1, 'user');
+ INSERT INTO roles VALUES (2, 'admin');
+ INSERT INTO user_roles VALUES (1, 1);
+ INSERT INTO user_roles VALUES (1, 2);
+ INSERT INTO user_roles VALUES (2, 1);
+ INSERT INTO user_roles VALUES (3, 1);
+
+=item *
+
+Load the user/roles data:
+
+ mysql -ututorial myapp < myapp02_mysql.sql
+
+=item *
+
+Create the C<.sql> file for the hashed password data:
+
+Open C<myapp03_mysql.sql> in your editor and enter:
+
+ --
+ -- Convert passwords to SHA-1 hashes
+ --
+ UPDATE users SET password = 'e727d1464ae12436e899a726da5b2f11d8381b26' WHERE id = 1;
+ UPDATE users SET password = 'e727d1464ae12436e899a726da5b2f11d8381b26' WHERE id = 2;
+ UPDATE users SET password = 'e727d1464ae12436e899a726da5b2f11d8381b26' WHERE id = 3;
+
+=item *
+
+Load the user/roles data:
+
+ mysql -ututorial myapp < myapp03_mysql.sql
+
+=back
+
+=back
+
+
+=head1 AUTHOR
+
+Kennedy Clark, C<hkclark at gmail.com>
+
+Please report any errors, issues or suggestions to the author. The
+most recent version of the Catalyst Tutorial can be found at
+L<http://dev.catalyst.perl.org/repos/Catalyst/Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/>.
+
+Copyright 2006-2008, Kennedy Clark, under Creative Commons License
+(L<http://creativecommons.org/licenses/by-sa/3.0/us/>).
Deleted: Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/AdvancedCRUD.pod
===================================================================
--- Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/AdvancedCRUD.pod 2009-05-24 22:09:09 UTC (rev 10276)
+++ Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/AdvancedCRUD.pod 2009-05-24 22:30:47 UTC (rev 10277)
@@ -1,99 +0,0 @@
-=head1 NAME
-
-Catalyst::Manual::Tutorial::AdvancedCRUD - Catalyst Tutorial - Chapter 9: Advanced CRUD
-
-
-=head1 OVERVIEW
-
-This is B<Chapter 9 of 10> for the Catalyst tutorial.
-
-L<Tutorial Overview|Catalyst::Manual::Tutorial>
-
-=over 4
-
-=item 1
-
-L<Introduction|Catalyst::Manual::Tutorial::Intro>
-
-=item 2
-
-L<Catalyst Basics|Catalyst::Manual::Tutorial::CatalystBasics>
-
-=item 3
-
-L<More Catalyst Basics|Catalyst::Manual::Tutorial::MoreCatalystBasics>
-
-=item 4
-
-L<Basic CRUD|Catalyst::Manual::Tutorial::BasicCRUD>
-
-=item 5
-
-L<Authentication|Catalyst::Manual::Tutorial::Authentication>
-
-=item 6
-
-L<Authorization|Catalyst::Manual::Tutorial::Authorization>
-
-=item 7
-
-L<Debugging|Catalyst::Manual::Tutorial::Debugging>
-
-=item 8
-
-L<Testing|Catalyst::Manual::Tutorial::Testing>
-
-=item 9
-
-B<Advanced CRUD>
-
-=item 10
-
-L<Appendices|Catalyst::Manual::Tutorial::Appendices>
-
-=back
-
-
-=head1 DESCRIPTION
-
-This chapter of the tutorial explores more advanced functionality for
-Create, Read, Update, and Delete (CRUD) than we saw in Chapter 4. In
-particular, it looks at a number of techniques that can be useful for
-the Update portion of CRUD, such as automated form generation,
-validation of user-entered data, and automated transfer of data between
-forms and model objects.
-
-In keeping with the Catalyst (and Perl) spirit of flexibility, there are
-many different ways to approach advanced CRUD operations in a Catalyst
-environment. Therefore, this section of the tutorial allows you to pick
-from one of several modules that that cover different form management
-tools. Select one or more options from the list below.
-
-=head1 ADVANCED CRUD OPTIONS
-
-=over 4
-
-=item *
-
-L<FormFu|Catalyst::Manual::Tutorial::AdvancedCRUD::FormFu>
-
-=item *
-
-L<FormBuilder|Catalyst::Manual::Tutorial::AdvancedCRUD::FormBuilder>
-
-=back
-
-B<NOTE:> Please contact the author if you would like to assist with
-writing a new module.
-
-
-=head1 AUTHOR
-
-Kennedy Clark, C<hkclark at gmail.com>
-
-Please report any errors, issues or suggestions to the author. The
-most recent version of the Catalyst Tutorial can be found at
-L<http://dev.catalyst.perl.org/repos/Catalyst/Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/>.
-
-Copyright 2006-2008, Kennedy Clark, under Creative Commons License
-(L<http://creativecommons.org/licenses/by-sa/3.0/us/>).
Deleted: Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/Appendices.pod
===================================================================
--- Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/Appendices.pod 2009-05-24 22:09:09 UTC (rev 10276)
+++ Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/Appendices.pod 2009-05-24 22:30:47 UTC (rev 10277)
@@ -1,784 +0,0 @@
-=head1 NAME
-
-Catalyst::Manual::Tutorial::Appendices - Catalyst Tutorial - Chapter 10: Appendices
-
-
-=head1 OVERVIEW
-
-This is B<Chapter 10 of 10> for the Catalyst tutorial.
-
-L<Tutorial Overview|Catalyst::Manual::Tutorial>
-
-=over 4
-
-=item 1
-
-L<Introduction|Catalyst::Manual::Tutorial::Intro>
-
-=item 2
-
-L<Catalyst Basics|Catalyst::Manual::Tutorial::CatalystBasics>
-
-=item 3
-
-L<More Catalyst Basics|Catalyst::Manual::Tutorial::MoreCatalystBasics>
-
-=item 4
-
-L<Basic CRUD|Catalyst::Manual::Tutorial::BasicCRUD>
-
-=item 5
-
-L<Authentication|Catalyst::Manual::Tutorial::Authentication>
-
-=item 6
-
-L<Authorization|Catalyst::Manual::Tutorial::Authorization>
-
-=item 7
-
-L<Debugging|Catalyst::Manual::Tutorial::Debugging>
-
-=item 8
-
-L<Testing|Catalyst::Manual::Tutorial::Testing>
-
-=item 9
-
-L<Advanced CRUD|Catalyst::Manual::Tutorial::AdvancedCRUD>
-
-=item 10
-
-B<Appendices>
-
-=back
-
-
-=head1 DESCRIPTION
-
-This chapter of the tutorial provides supporting information relevant to
-the Catalyst tutorial.
-
-
-=head1 APPENDIX 1: CUT AND PASTE FOR POD-BASED EXAMPLES
-
-You may notice that Pod indents example code with four spaces. This
-section provides some quick advice to "un-indent" this text in common
-editors.
-
-=head2 "Un-indenting" with Vi/Vim
-
-When cutting and pasting multi-line text from Pod-based documents, the
-following vi/vim regexs can be helpful to "un-indent" the inserted text
-(do NOT type the quotes, they are only included to show spaces in the
-regex patterns). I<Note that all 3 of the regexs end in 4 spaces>:
-
-=over 4
-
-=item *
-
-":0,$s/^ "
-
-Removes four leading spaces from the entire file (from the first line,
-C<0>, to the last line, C<$>).
-
-=item *
-
-"%s/^ "
-
-A shortcut for the previous item (C<%> specifies the entire file; so
-this removes four leading spaces from every line).
-
-=item *
-
-":.,$s/^ "
-
-Removes the first four spaces from the line the cursor is on at the time
-the regex command is executed (".") to the last line of the file.
-
-=item *
-
-":.,44s/^ "
-
-Removes four leading space from the current line through line 44
-(obviously adjust the C<44> to the appropriate value in your example).
-
-=back
-
-=head2 "Un-indenting" with Emacs
-
-Although the author has not used Emacs for many years (apologies to
-the Emacs fans out there), here is a quick hint to get you started. To
-replace the leading spaces of every line in a file, use:
-
- M-x replace-regexp<RET>
- Replace regexp: ^ <RET>
- with: <RET>
-
-All of that will occur on the single line at the bottom of your screen.
-Note that "<RET>" represents the return key/enter. Also, there are
-four spaces after the "^" on the "Replace regexp:" line and no spaces
-entered on the last line.
-
-You can limit the replacement operation by selecting text first (depending
-on your version of Emacs, you can either use the mouse or experiment with
-commands such as C<C-SPC> to set the mark at the cursor location and
-C<C-E<lt>> and C<C-E<gt>> to set the mark at the beginning and end of the
-file respectively.
-
-Also, Stefan Kangas sent in the following tip about an alternate
-approach using the command C<indent-region> to redo the indentation
-for the currently selected region (adhering to indent rules in the
-current major mode). You can run the command by typing M-x indent-
-region or pressing the default keybinding C-M-\ in cperl-mode.
-Additional details can be found here:
-
-L<http://www.gnu.org/software/emacs/manual/html_node/emacs/Indentation-Comman>
-
-
-=head1 APPENDIX 2: USING POSTGRESQL AND MYSQL
-
-The main database used in this tutorial is the very simple yet powerful
-SQLite. This section provides information that can be used to "convert"
-the tutorial to use PostgreSQL and MySQL. However, note that part of
-the beauty of the MVC architecture is that very little database-specific
-code is spread throughout the system (at least when MVC is "done
-right"). Consequently, converting from one database to another is
-relatively painless with most Catalyst applications. In general, you
-just need to adapt the schema definition C<.sql> file you use to
-initialize your database and adjust a few configuration parameters.
-
-Also note that the purpose of the data definition statements for this
-section are not designed to take maximum advantage of the various
-features in each database for issues such as referential integrity and
-field types/constraints.
-
-
-=head2 PostgreSQL
-
-Use the following steps to adapt the tutorial to PostgreSQL. Thanks
-to Caelum (Rafael Kitover) for assistance with the most recent
-updates, and Louis Moore, Marcello Romani and Tom Lanyon for help with
-earlier versions.
-
-=over 4
-
-=item *
-
-Chapter 3: More Catalyst Basics
-
-=over 4
-
-=item *
-
-Install the PostgreSQL server and client and DBD::Pg:
-
-If you are following along in Debian 5, you can quickly install these
-items via this command:
-
- sudo aptitude install postgresql libdbd-pg-perl libdatetime-format-pg-perl
-
-To configure the permissions, you can open
-C</etc/postgresql/8.3/main/pg_hba.conf> and change this line (near the
-bottom):
-
- # "local" is for Unix domain socket connections only
- local all all ident sameuser
-
-to:
-
- # "local" is for Unix domain socket connections only
- local all all trust
-
-And then restart PostgreSQL:
-
- sudo /etc/init.d/postgresql-8.3 restart
-
-
-=item *
-
-Create the database and a user for the database (note that we are
-using "E<lt>catalystE<gt>" to represent the hidden password of
-"catalyst"):
-
- $ sudo -u postgres createuser -P catappuser
- Enter password for new role: <catalyst>
- Enter it again: <catalyst>
- Shall the new role be a superuser? (y/n) n
- Shall the new role be allowed to create databases? (y/n) n
- Shall the new role be allowed to create more new roles? (y/n) n
- CREATE ROLE
- $ sudo -u postgres createdb -O catappuser catappdb
- CREATE DATABASE
-
-=item *
-
-Create the C<.sql> file and load the data:
-
-=over 4
-
-=item *
-
-Open the C<myapp01_psql.sql> in your editor and enter:
-
- --
- -- Drops just in case you are reloading
- ---
- DROP TABLE IF EXISTS books CASCADE;
- DROP TABLE IF EXISTS authors CASCADE;
- DROP TABLE IF EXISTS book_authors CASCADE;
- DROP TABLE IF EXISTS users CASCADE;
- DROP TABLE IF EXISTS roles CASCADE;
- DROP TABLE IF EXISTS user_roles CASCADE;
-
- --
- -- Create a very simple database to hold book and author information
- --
- CREATE TABLE books (
- id SERIAL PRIMARY KEY,
- title TEXT ,
- rating INTEGER,
- -- Manually add these later
- -- created TIMESTAMP NOT NULL DEFAULT now(),
- -- updated TIMESTAMP
- );
-
- CREATE TABLE authors (
- id SERIAL PRIMARY KEY,
- first_name TEXT,
- last_name TEXT
- );
-
- -- 'book_authors' is a many-to-many join table between books & authors
- CREATE TABLE book_authors (
- book_id INTEGER REFERENCES books(id) ON DELETE CASCADE ON UPDATE CASCADE,
- author_id INTEGER REFERENCES authors(id) ON DELETE CASCADE ON UPDATE CASCADE,
- PRIMARY KEY (book_id, author_id)
- );
-
- ---
- --- Load some sample data
- ---
- INSERT INTO books (title, rating) VALUES ('CCSP SNRS Exam Certification Guide', 5);
- INSERT INTO books (title, rating) VALUES ('TCP/IP Illustrated, Volume 1', 5);
- INSERT INTO books (title, rating) VALUES ('Internetworking with TCP/IP Vol.1', 4);
- INSERT INTO books (title, rating) VALUES ('Perl Cookbook', 5);
- INSERT INTO books (title, rating) VALUES ('Designing with Web Standards', 5);
- INSERT INTO authors (first_name, last_name) VALUES ('Greg', 'Bastien');
- INSERT INTO authors (first_name, last_name) VALUES ('Sara', 'Nasseh');
- INSERT INTO authors (first_name, last_name) VALUES ('Christian', 'Degu');
- INSERT INTO authors (first_name, last_name) VALUES ('Richard', 'Stevens');
- INSERT INTO authors (first_name, last_name) VALUES ('Douglas', 'Comer');
- INSERT INTO authors (first_name, last_name) VALUES ('Tom', 'Christiansen');
- INSERT INTO authors (first_name, last_name) VALUES ('Nathan', 'Torkington');
- INSERT INTO authors (first_name, last_name) VALUES ('Jeffrey', 'Zeldman');
- INSERT INTO book_authors VALUES (1, 1);
- INSERT INTO book_authors VALUES (1, 2);
- INSERT INTO book_authors VALUES (1, 3);
- INSERT INTO book_authors VALUES (2, 4);
- INSERT INTO book_authors VALUES (3, 5);
- INSERT INTO book_authors VALUES (4, 6);
- INSERT INTO book_authors VALUES (4, 7);
- INSERT INTO book_authors VALUES (5, 8);
-
-=item *
-
-Load the data:
-
- $ psql -U catappuser -W catappdb -f myapp01_psql.sql
- Password for user catappuser:
- psql:myapp01_psql.sql:8: NOTICE: CREATE TABLE will create implicit sequence "books_id_seq" for serial column "books.id"
- psql:myapp01_psql.sql:8: NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "books_pkey" for table "books"
- CREATE TABLE
- psql:myapp01_psql.sql:15: NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "book_authors_pkey" for table "book_authors"
- CREATE TABLE
- psql:myapp01_psql.sql:21: NOTICE: CREATE TABLE will create implicit sequence "authors_id_seq" for serial column "authors.id"
- psql:myapp01_psql.sql:21: NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "authors_pkey" for table "authors"
- CREATE TABLE
- INSERT 0 1
- INSERT 0 1
- INSERT 0 1
- ...
-
-=item *
-
-Make sure the data loaded correctly:
-
- $ psql -U catappuser -W catappdb
- Password for user catappuser: <catalyst>
- Welcome to psql 8.3.7, the PostgreSQL interactive terminal.
-
- Type: \copyright for distribution terms
- \h for help with SQL commands
- \? for help with psql commands
- \g or terminate with semicolon to execute query
- \q to quit
-
- catappdb=> \dt
- List of relations
- Schema | Name | Type | Owner
- --------+--------------+-------+------------
- public | authors | table | catappuser
- public | book_authors | table | catappuser
- public | books | table | catappuser
- (3 rows)
-
- catappdb=> select * from books;
- id | title | rating
- ----+------------------------------------+--------
- 1 | CCSP SNRS Exam Certification Guide | 5
- 2 | TCP/IP Illustrated, Volume 1 | 5
- 3 | Internetworking with TCP/IP Vol.1 | 4
- 4 | Perl Cookbook | 5
- 5 | Designing with Web Standards | 5
- (5 rows)
-
- catappdb=>
-
-=back
-
-=item *
-
-After the steps where you:
-
- edit lib/MyApp.pm
-
- create lib/MyAppDB.pm
-
- create lib/MyAppDB/Book.pm
-
- create lib/MyAppDB/Author.pm
-
- create lib/MyAppDB/BookAuthor.pm
-
-
-=item *
-
-Generate the model using the Catalyst "_create.pl" script:
-
- $ rm lib/MyApp/Model/DB.pm # Delete just in case already there
- $ script/myapp_create.pl model DB DBIC::Schema MyApp::Schema \
- create=static components=TimeStamp,EncodedColumn \
- 'dbi:Pg:dbname=catappdb' 'catappuser' 'catalyst' '{ AutoCommit => 1 }'
-
-=back
-
-=item *
-
-Chapter 4: Basic CRUD
-
-Add Datetime Columns to Our Existing Books Table
-
- $ psql -U catappuser -W catappdb
- ...
- catappdb=> ALTER TABLE books ADD created TIMESTAMP NOT NULL DEFAULT now();
- ALTER TABLE
- catappdb=> ALTER TABLE books ADD updated TIMESTAMP;
- ALTER TABLE
- catappdb=> \q
-
-Re-generate the model using the Catalyst "_create.pl" script:
-
- $ script/myapp_create.pl model DB DBIC::Schema MyApp::Schema \
- create=static components=TimeStamp,EncodedColumn \
- 'dbi:Pg:dbname=catappdb' 'catappuser' 'catalyst' '{ AutoCommit => 1 }'
-
-
-=item *
-
-Chapter 5: Authentication
-
-=over 4
-
-=item *
-
-Create the C<.sql> file for the user/roles data:
-
-Open C<myapp02_psql.sql> in your editor and enter:
-
- --
- -- Add users and roles tables, along with a many-to-many join table
- --
-
- CREATE TABLE users (
- id SERIAL PRIMARY KEY,
- username TEXT,
- password TEXT,
- email_address TEXT,
- first_name TEXT,
- last_name TEXT,
- active INTEGER
- );
-
- CREATE TABLE roles (
- id SERIAL PRIMARY KEY,
- role TEXT
- );
-
- CREATE TABLE user_roles (
- user_id INTEGER REFERENCES users(id) ON DELETE CASCADE ON UPDATE CASCADE,
- role_id INTEGER REFERENCES roles(id) ON DELETE CASCADE ON UPDATE CASCADE,
- PRIMARY KEY (user_id, role_id)
- );
-
- --
- -- Load up some initial test data
- --
- INSERT INTO users (username, password, email_address, first_name, last_name, active)
- VALUES ('test01', 'mypass', 't01 at na.com', 'Joe', 'Blow', 1);
- INSERT INTO users (username, password, email_address, first_name, last_name, active)
- VALUES ('test02', 'mypass', 't02 at na.com', 'Jane', 'Doe', 1);
- INSERT INTO users (username, password, email_address, first_name, last_name, active)
- VALUES ('test03', 'mypass', 't03 at na.com', 'No', 'Go', 0);
- INSERT INTO roles (role) VALUES ('user');
- INSERT INTO roles (role) VALUES ('admin');
- INSERT INTO user_roles VALUES (1, 1);
- INSERT INTO user_roles VALUES (1, 2);
- INSERT INTO user_roles VALUES (2, 1);
- INSERT INTO user_roles VALUES (3, 1);
-
-=item *
-
-Load the data:
-
- $ psql -U catappuser -W catappdb -f myapp02_psql.sql
- Password for user catappuser: <catalyst>
- psql:myapp02_psql.sql:13: NOTICE: CREATE TABLE will create implicit sequence "users_id_seq" for serial column "users.id"
- psql:myapp02_psql.sql:13: NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "users_pkey" for table "users"
- CREATE TABLE
- psql:myapp02_psql.sql:18: NOTICE: CREATE TABLE will create implicit sequence "roles_id_seq" for serial column "roles.id"
- psql:myapp02_psql.sql:18: NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "roles_pkey" for table "roles"
- CREATE TABLE
- psql:myapp02_psql.sql:24: NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "user_roles_pkey" for table "user_roles"
- CREATE TABLE
- INSERT 0 1
- INSERT 0 1
- INSERT 0 1
- INSERT 0 1
- INSERT 0 1
- INSERT 0 1
- INSERT 0 1
- INSERT 0 1
- INSERT 0 1
-
-Confirm with:
-
- $ psql -U catappuser -W catappdb -c "select * from users"
- Password for user catappuser: <catalyst>
- id | username | password | email_address | first_name | last_name | active
- ----+----------+----------+---------------+------------+-----------+--------
- 1 | test01 | mypass | t01 at na.com | Joe | Blow | 1
- 2 | test02 | mypass | t02 at na.com | Jane | Doe | 1
- 3 | test03 | mypass | t03 at na.com | No | Go | 0
- (3 rows)
-
-
-=item *
-
-Modify C<set_hashed_passwords.pl> to match the following (the only difference
-is the C<connect> line):
-
- #!/usr/bin/perl
-
- use strict;
- use warnings;
-
- use MyApp::Schema;
-
- my $schema = MyApp::Schema->connect('dbi:Pg:dbname=catappdb', 'catappuser', 'catalyst');
-
- my @users = $schema->resultset('Users')->all;
-
- foreach my $user (@users) {
- $user->password('mypass');
- $user->update;
- }
-
-Run the C<set_hashed_passwords.pl> as per the "normal" flow of the
-tutorial:
-
- $ perl -Ilib set_hashed_passwords.pl
-
-You can verify that it worked with this command:
-
- $ psql -U catappuser -W catappdb -c "select * from users"
-
-
-=back
-
-=back
-
-
-=head2 MySQL
-
-B<NOTE:> This section is out of data with the rest of the tutorial.
-Consider using SQLite or PostgreSQL since they are current.
-
-Use the following steps to adapt the tutorial to MySQL. Thanks to Jim
-Howard for the help.
-
-=over 4
-
-=item *
-
-Chapter 3: Catalyst Basics
-
-=over 4
-
-=item *
-
-Install the required software:
-
-=over 4
-
-=item *
-
-The MySQL database server and client utility.
-
-=item *
-
-The Perl C<DBD::MySQL> module
-
-=back
-
-For CentOS users (see
-L<Catalyst::Manual::Installation::CentOS4|Catalyst::Manual::Installation::CentOS4>),
-you can use the following commands to install the software and start the MySQL
-daemon:
-
- yum -y install mysql mysql-server
- service mysqld start
-
-=item *
-
-Create the database and set the permissions:
-
- $ mysql
- Welcome to the MySQL monitor. Commands end with ; or \g.
- Your MySQL connection id is 2 to server version: 4.1.20
-
- Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
-
- mysql> create database myapp;
- Query OK, 1 row affected (0.01 sec)
-
- mysql> grant all on myapp.* to tutorial@'localhost';
- Query OK, 0 rows affected (0.00 sec)
-
- mysql> flush privileges;
- Query OK, 0 rows affected (0.00 sec)
-
- mysql> quit
- Bye
-
-=item *
-
-Create the C<.sql> file and load the data:
-
-=over 4
-
-=item *
-
-Open the C<myapp01_mysql.sql> in your editor and enter:
-
- --
- -- Create a very simple database to hold book and author information
- --
- DROP TABLE IF EXISTS books;
- DROP TABLE IF EXISTS book_authors;
- DROP TABLE IF EXISTS authors;
- CREATE TABLE books (
- id INT(11) PRIMARY KEY AUTO_INCREMENT,
- title TEXT ,
- rating INT(11)
- );
- -- 'book_authors' is a many-to-many join table between books & authors
- CREATE TABLE book_authors (
- book_id INT(11),
- author_id INT(11),
- PRIMARY KEY (book_id, author_id)
- );
- CREATE TABLE authors (
- id INT(11) PRIMARY KEY AUTO_INCREMENT,
- first_name TEXT,
- last_name TEXT
- );
- ---
- --- Load some sample data
- ---
- INSERT INTO books VALUES (1, 'CCSP SNRS Exam Certification Guide', 5);
- INSERT INTO books VALUES (2, 'TCP/IP Illustrated, Volume 1', 5);
- INSERT INTO books VALUES (3, 'Internetworking with TCP/IP Vol.1', 4);
- INSERT INTO books VALUES (4, 'Perl Cookbook', 5);
- INSERT INTO books VALUES (5, 'Designing with Web Standards', 5);
- INSERT INTO authors VALUES (1, 'Greg', 'Bastien');
- INSERT INTO authors VALUES (2, 'Sara', 'Nasseh');
- INSERT INTO authors VALUES (3, 'Christian', 'Degu');
- INSERT INTO authors VALUES (4, 'Richard', 'Stevens');
- INSERT INTO authors VALUES (5, 'Douglas', 'Comer');
- INSERT INTO authors VALUES (6, 'Tom', 'Christiansen');
- INSERT INTO authors VALUES (7, ' Nathan', 'Torkington');
- INSERT INTO authors VALUES (8, 'Jeffrey', 'Zeldman');
- INSERT INTO book_authors VALUES (1, 1);
- INSERT INTO book_authors VALUES (1, 2);
- INSERT INTO book_authors VALUES (1, 3);
- INSERT INTO book_authors VALUES (2, 4);
- INSERT INTO book_authors VALUES (3, 5);
- INSERT INTO book_authors VALUES (4, 6);
- INSERT INTO book_authors VALUES (4, 7);
- INSERT INTO book_authors VALUES (5, 8);
-
-=item *
-
-Load the data:
-
- mysql -ututorial myapp < myapp01_mysql.sql
-
-=item *
-
-Make sure the data loaded correctly:
-
- $ mysql -ututorial myapp
- Reading table information for completion of table and column names
- You can turn off this feature to get a quicker startup with -A
-
- Welcome to the MySQL monitor. Commands end with ; or \g.
- Your MySQL connection id is 4 to server version: 4.1.20
-
- Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
-
- mysql> show tables;
- +-----------------+
- | Tables_in_myapp |
- +-----------------+
- | authors |
- | book_authors |
- | books |
- +-----------------+
- 3 rows in set (0.00 sec)
-
- mysql> select * from books;
- +----+------------------------------------+--------+
- | id | title | rating |
- +----+------------------------------------+--------+
- | 1 | CCSP SNRS Exam Certification Guide | 5 |
- | 2 | TCP/IP Illustrated, Volume 1 | 5 |
- | 3 | Internetworking with TCP/IP Vol.1 | 4 |
- | 4 | Perl Cookbook | 5 |
- | 5 | Designing with Web Standards | 5 |
- +----+------------------------------------+--------+
- 5 rows in set (0.00 sec)
-
- mysql>
-
-=back
-
-=item *
-
-Update the model:
-
-=over 4
-
-=item *
-
-Delete the existing model:
-
- rm lib/MyApp/Model/MyAppDB.pm
-
-=item *
-
-Regenerate the model using the Catalyst "_create.pl" script:
-
- script/myapp_create.pl model DB DBIC::Schema MyApp::Schema \
- dbi:mysql:myapp '_username_here_' '_password_here_' '{ AutoCommit => 1 }'
-
-=back
-
-=back
-
-=item *
-
-Chapter 5: Authentication
-
-=over 4
-
-=item *
-
-Create the C<.sql> file for the user/roles data:
-
-Open C<myapp02_mysql.sql> in your editor and enter:
-
- --
- -- Add users and roles tables, along with a many-to-many join table
- --
- CREATE TABLE users (
- id INT(11) PRIMARY KEY,
- username TEXT,
- password TEXT,
- email_address TEXT,
- first_name TEXT,
- last_name TEXT,
- active INT(11)
- );
- CREATE TABLE roles (
- id INTEGER PRIMARY KEY,
- role TEXT
- );
- CREATE TABLE user_roles (
- user_id INT(11),
- role_id INT(11),
- PRIMARY KEY (user_id, role_id)
- );
- --
- -- Load up some initial test data
- --
- INSERT INTO users VALUES (1, 'test01', 'mypass', 't01 at na.com', 'Joe', 'Blow', 1);
- INSERT INTO users VALUES (2, 'test02', 'mypass', 't02 at na.com', 'Jane', 'Doe', 1);
- INSERT INTO users VALUES (3, 'test03', 'mypass', 't03 at na.com', 'No', 'Go', 0);
- INSERT INTO roles VALUES (1, 'user');
- INSERT INTO roles VALUES (2, 'admin');
- INSERT INTO user_roles VALUES (1, 1);
- INSERT INTO user_roles VALUES (1, 2);
- INSERT INTO user_roles VALUES (2, 1);
- INSERT INTO user_roles VALUES (3, 1);
-
-=item *
-
-Load the user/roles data:
-
- mysql -ututorial myapp < myapp02_mysql.sql
-
-=item *
-
-Create the C<.sql> file for the hashed password data:
-
-Open C<myapp03_mysql.sql> in your editor and enter:
-
- --
- -- Convert passwords to SHA-1 hashes
- --
- UPDATE users SET password = 'e727d1464ae12436e899a726da5b2f11d8381b26' WHERE id = 1;
- UPDATE users SET password = 'e727d1464ae12436e899a726da5b2f11d8381b26' WHERE id = 2;
- UPDATE users SET password = 'e727d1464ae12436e899a726da5b2f11d8381b26' WHERE id = 3;
-
-=item *
-
-Load the user/roles data:
-
- mysql -ututorial myapp < myapp03_mysql.sql
-
-=back
-
-=back
-
-
-=head1 AUTHOR
-
-Kennedy Clark, C<hkclark at gmail.com>
-
-Please report any errors, issues or suggestions to the author. The
-most recent version of the Catalyst Tutorial can be found at
-L<http://dev.catalyst.perl.org/repos/Catalyst/Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/>.
-
-Copyright 2006-2008, Kennedy Clark, under Creative Commons License
-(L<http://creativecommons.org/licenses/by-sa/3.0/us/>).
Deleted: Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/Authentication.pod
===================================================================
--- Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/Authentication.pod 2009-05-24 22:09:09 UTC (rev 10276)
+++ Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/Authentication.pod 2009-05-24 22:30:47 UTC (rev 10277)
@@ -1,916 +0,0 @@
-=head1 NAME
-
-Catalyst::Manual::Tutorial::Authentication - Catalyst Tutorial - Chapter 5: Authentication
-
-
-=head1 OVERVIEW
-
-This is B<Chapter 5 of 10> for the Catalyst tutorial.
-
-L<Tutorial Overview|Catalyst::Manual::Tutorial>
-
-=over 4
-
-=item 1
-
-L<Introduction|Catalyst::Manual::Tutorial::Intro>
-
-=item 2
-
-L<Catalyst Basics|Catalyst::Manual::Tutorial::CatalystBasics>
-
-=item 3
-
-L<More Catalyst Basics|Catalyst::Manual::Tutorial::MoreCatalystBasics>
-
-=item 4
-
-L<Basic CRUD|Catalyst::Manual::Tutorial::BasicCRUD>
-
-=item 5
-
-B<Authentication>
-
-=item 6
-
-L<Authorization|Catalyst::Manual::Tutorial::Authorization>
-
-=item 7
-
-L<Debugging|Catalyst::Manual::Tutorial::Debugging>
-
-=item 8
-
-L<Testing|Catalyst::Manual::Tutorial::Testing>
-
-=item 9
-
-L<Advanced CRUD|Catalyst::Manual::Tutorial::AdvancedCRUD>
-
-=item 10
-
-L<Appendices|Catalyst::Manual::Tutorial::Appendices>
-
-=back
-
-
-=head1 DESCRIPTION
-
-Now that we finally have a simple yet functional application, we can
-focus on providing authentication (with authorization coming next in
-Chapter 6).
-
-This chapter of the tutorial is divided into two main sections: 1) basic,
-cleartext authentication and 2) hash-based authentication.
-
-You can checkout the source code for this example from the catalyst
-subversion repository as per the instructions in
-L<Catalyst::Manual::Tutorial::Intro|Catalyst::Manual::Tutorial::Intro>.
-
-
-=head1 BASIC AUTHENTICATION
-
-This section explores how to add authentication logic to a Catalyst
-application.
-
-
-=head2 Add Users and Roles to the Database
-
-First, we add both user and role information to the database (we will
-add the role information here although it will not be used until the
-authorization section, Chapter 6). Create a new SQL script file by opening
-C<myapp02.sql> in your editor and insert:
-
- --
- -- Add user and role tables, along with a many-to-many join table
- --
- CREATE TABLE user (
- id INTEGER PRIMARY KEY,
- username TEXT,
- password TEXT,
- email_address TEXT,
- first_name TEXT,
- last_name TEXT,
- active INTEGER
- );
- CREATE TABLE role (
- id INTEGER PRIMARY KEY,
- role TEXT
- );
- CREATE TABLE user_role (
- user_id INTEGER,
- role_id INTEGER,
- PRIMARY KEY (user_id, role_id)
- );
- --
- -- Load up some initial test data
- --
- INSERT INTO user VALUES (1, 'test01', 'mypass', 't01 at na.com', 'Joe', 'Blow', 1);
- INSERT INTO user VALUES (2, 'test02', 'mypass', 't02 at na.com', 'Jane', 'Doe', 1);
- INSERT INTO user VALUES (3, 'test03', 'mypass', 't03 at na.com', 'No', 'Go', 0);
- INSERT INTO role VALUES (1, 'user');
- INSERT INTO role VALUES (2, 'admin');
- INSERT INTO user_role VALUES (1, 1);
- INSERT INTO user_role VALUES (1, 2);
- INSERT INTO user_role VALUES (2, 1);
- INSERT INTO user_role VALUES (3, 1);
-
-Then load this into the C<myapp.db> database with the following command:
-
- $ sqlite3 myapp.db < myapp02.sql
-
-=head2 Add User and Role Information to DBIC Schema
-
-Although we could manually edit the DBIC schema information to include
-the new tables added in the previous step, let's use the C<create=static>
-option on the DBIC model helper to do most of the work for us:
-
- $ script/myapp_create.pl model DB DBIC::Schema MyApp::Schema \
- create=static components=TimeStamp dbi:SQLite:myapp.db
- exists "/root/dev/MyApp/script/../lib/MyApp/Model"
- exists "/root/dev/MyApp/script/../t"
- Dumping manual schema for MyApp::Schema to directory /root/dev/MyApp/script/../lib ...
- Schema dump completed.
- exists "/root/dev/MyApp/script/../lib/MyApp/Model/DB.pm"
- $
- $ ls lib/MyApp/Schema/Result
- Author.pm BookAuthor.pm Book.pm Role.pm User.pm UserRole.pm
-
-Notice how the helper has added three new table-specific result source
-files to the C<lib/MyApp/Schema/Result> directory. And, more
-importantly, even if there were changes to the existing result source
-files, those changes would have only been written above the C<# DO NOT
-MODIFY THIS OR ANYTHING ABOVE!> comment and your hand-edited
-enhancements would have been preserved.
-
-Speaking of "hand-editted enhancements," we should now add
-relationship information to the three new result source files. Edit
-each of these files and add the following information between the C<#
-DO NOT MODIFY THIS OR ANYTHING ABOVE!> comment and the closing C<1;>:
-
-C<lib/MyApp/Schema/Result/User.pm>:
-
- #
- # Set relationships:
- #
-
- # has_many():
- # args:
- # 1) Name of relationship, DBIC will create accessor with this name
- # 2) Name of the model class referenced by this relationship
- # 3) Column name in *foreign* table (aka, foreign key in peer table)
- __PACKAGE__->has_many(map_user_role => 'MyApp::Schema::Result::UserRole', 'user_id');
-
- # many_to_many():
- # args:
- # 1) Name of relationship, DBIC will create accessor with this name
- # 2) Name of has_many() relationship this many_to_many() is shortcut for
- # 3) Name of belongs_to() relationship in model class of has_many() above
- # You must already have the has_many() defined to use a many_to_many().
- __PACKAGE__->many_to_many(roles => 'map_user_role', 'role');
-
-
-C<lib/MyApp/Schema/Result/Role.pm>:
-
- #
- # Set relationships:
- #
-
- # has_many():
- # args:
- # 1) Name of relationship, DBIC will create accessor with this name
- # 2) Name of the model class referenced by this relationship
- # 3) Column name in *foreign* table (aka, foreign key in peer table)
- __PACKAGE__->has_many(map_user_role => 'MyApp::Schema::Result::UserRole', 'role_id');
-
-
-C<lib/MyApp/Schema/Result/UserRole.pm>:
-
- #
- # Set relationships:
- #
-
- # belongs_to():
- # args:
- # 1) Name of relationship, DBIC will create accessor with this name
- # 2) Name of the model class referenced by this relationship
- # 3) Column name in *this* table
- __PACKAGE__->belongs_to(user => 'MyApp::Schema::Result::User', 'user_id');
-
- # belongs_to():
- # args:
- # 1) Name of relationship, DBIC will create accessor with this name
- # 2) Name of the model class referenced by this relationship
- # 3) Column name in *this* table
- __PACKAGE__->belongs_to(role => 'MyApp::Schema::Result::Role', 'role_id');
-
-The code for these three sets of updates is obviously very similar to
-the edits we made to the C<Book>, C<Author>, and C<BookAuthor>
-classes created in Chapter 3.
-
-Note that we do not need to make any change to the
-C<lib/MyApp/Schema.pm> schema file. It simply tells DBIC to load all
-of the Result Class and ResultSet Class files it finds in below the
-C<lib/MyApp/Schema> directory, so it will automatically pick up our
-new table information.
-
-
-=head2 Sanity-Check Reload of Development Server
-
-We aren't ready to try out the authentication just yet; we only want
-to do a quick check to be sure our model loads correctly. Press
-C<Ctrl-C> to kill the previous server instance (if it's still running)
-and restart it:
-
- $ script/myapp_server.pl
-
-Look for the three new model objects in the startup debug output:
-
- ...
- .-------------------------------------------------------------------+----------.
- | Class | Type |
- +-------------------------------------------------------------------+----------+
- | MyApp::Controller::Books | instance |
- | MyApp::Controller::Root | instance |
- | MyApp::Model::DB | instance |
- | MyApp::Model::DB::Author | class |
- | MyApp::Model::DB::Book | class |
- | MyApp::Model::DB::BookAuthor | class |
- | MyApp::Model::DB::Role | class |
- | MyApp::Model::DB::User | class |
- | MyApp::Model::DB::UserRole | class |
- | MyApp::View::TT | instance |
- '-------------------------------------------------------------------+----------'
- ...
-
-Again, notice that your "Result Class" classes have been "re-loaded"
-by Catalyst under C<MyApp::Model>.
-
-
-=head2 Include Authentication and Session Plugins
-
-Edit C<lib/MyApp.pm> and update it as follows (everything below
-C<StackTrace> is new):
-
- # Load plugins
- use Catalyst qw/-Debug
- ConfigLoader
- Static::Simple
-
- StackTrace
-
- Authentication
-
- Session
- Session::Store::FastMmap
- Session::State::Cookie
- /;
-
-B<Note:> As discussed in MoreCatalystBasics, different versions of
-C<Catalyst::Devel> have used a variety of methods to load the plugins.
-You can put the plugins in the C<use Catalyst> statement if you prefer.
-
-The C<Authentication> plugin supports Authentication while the
-C<Session> plugins are required to maintain state across multiple HTTP
-requests.
-
-Note that the only required Authentication class is the main one. This
-is a change that occurred in version 0.09999_01 of the
-C<Authentication> plugin. You B<do not need> to specify a particular
-Authentication::Store or Authentication::Credential plugin. Instead,
-indicate the Store and Credential you want to use in your application
-configuration (see below).
-
-Make sure you include the additional plugins as new dependencies in
-the Makefile.PL file something like this:
-
- requires (
- 'Catalyst::Plugin::Authentication' => '0',
- 'Catalyst::Plugin::Session' => '0',
- 'Catalyst::Plugin::Session::Store::FastMmap' => '0',
- 'Catalyst::Plugin::Session::State::Cookie' => '0',
- );
-
-Note that there are several options for
-L<Session::Store|Catalyst::Plugin::Session::Store>
-(L<Session::Store::FastMmap|Catalyst::Plugin::Session::Store::FastMmap>
-is generally a good choice if you are on Unix; try
-L<Session::Store::File|Catalyst::Plugin::Session::Store::File> if you
-are on Win32) -- consult
-L<Session::Store|Catalyst::Plugin::Session::Store> and its subclasses
-for additional information and options (for example to use a database-
-backed session store).
-
-
-=head2 Configure Authentication
-
-There are a variety of ways to provide configuration information to
-L<Catalyst::Plugin::Authentication|Catalyst::Plugin::Authentication>.
-Here we will use
-L<Catalyst::Authentication::Realm::SimpleDB|Catalyst::Authentication::Realm::SimpleDB>
-because it automatically sets a reasonable set of defaults for us. Open
-C<lib/MyApp.pm> and place the following text above the call to
-C<__PACKAGE__-E<gt>setup();>:
-
- # Configure SimpleDB Authentication
- __PACKAGE__->config->{'Plugin::Authentication'} = {
- default => {
- class => 'SimpleDB',
- user_model => 'DB::User',
- password_type => 'clear',
- },
- };
-
-We could have placed this configuration in C<myapp.conf>, but placing
-it in C<lib/MyApp.pm> is probably a better place since it's not likely
-something that users of your application will want to change during
-deployment (or you could use a mixture: leave C<class> and
-C<user_model> defined in C<lib/MyApp.pm> as we show above, but place
-C<password_type> in C<myapp.conf> to allow the type of password to be
-easily modified during deployment). We will stick with putting
-all of the authentication-related configuration in C<lib/MyApp.pm>
-for the tutorial, but if you wish to use C<myapp.conf>, just convert
-to the following code:
-
- <Plugin::Authentication>
- use_session 1
- <default>
- password_type self_check
- user_model DB::User
- class SimpleDB
- </default>
- </Plugin::Authentication>
-
-B<TIP:> Here is a short script that will dump the contents of
-C<MyApp->config> to L<Config::General|Config::General> format in
-C<myapp.conf>:
-
- $ perl -Ilib -e 'use MyApp; use Config::General;
- Config::General->new->save_file("myapp.conf", MyApp->config);'
-
-B<NOTE:> Because we are using SimpleDB along with a database layout
-that complies with its default assumptions, we don't need to specify
-the names of the columns where our username and password information
-is stored (hence, the "Simple" part of "SimpleDB"). That being said,
-SimpleDB lets you specify that type of information if you need to.
-Take a look at
-C<Catalyst::Authentication::Realm::SimpleDB|Catalyst::Authentication::Realm::SimpleDB>
-for details.
-
-
-=head2 Add Login and Logout Controllers
-
-Use the Catalyst create script to create two stub controller files:
-
- $ script/myapp_create.pl controller Login
- $ script/myapp_create.pl controller Logout
-
-You could easily use a single controller here. For example, you could
-have a C<User> controller with both C<login> and C<logout> actions.
-Remember, Catalyst is designed to be very flexible, and leaves such
-matters up to you, the designer and programmer.
-
-Then open C<lib/MyApp/Controller/Login.pm>, locate the
-C<sub index :Path :Args(0)> method (or C<sub index : Private> if you
-are using an older version of Catalyst) that was automatically
-inserted by the helpers when we created the Login controller above,
-and update the definition of C<sub index> to match:
-
- =head2 index
-
- Login logic
-
- =cut
-
- sub index :Path :Args(0) {
- my ($self, $c) = @_;
-
- # Get the username and password from form
- my $username = $c->request->params->{username} || "";
- my $password = $c->request->params->{password} || "";
-
- # If the username and password values were found in form
- if ($username && $password) {
- # Attempt to log the user in
- if ($c->authenticate({ username => $username,
- password => $password } )) {
- # If successful, then let them use the application
- $c->response->redirect($c->uri_for(
- $c->controller('Books')->action_for('list')));
- return;
- } else {
- # Set an error message
- $c->stash->{error_msg} = "Bad username or password.";
- }
- }
-
- # If either of above don't work out, send to the login page
- $c->stash->{template} = 'login.tt2';
- }
-
-Be sure to remove the C<$c-E<gt>response-E<gt>body('Matched MyApp::Controller::Login in Login.');>
-line of the C<sub index>.
-
-This controller fetches the C<username> and C<password> values from the
-login form and attempts to authenticate the user. If successful, it
-redirects the user to the book list page. If the login fails, the user
-will stay at the login page and receive an error message. If the
-C<username> and C<password> values are not present in the form, the
-user will be taken to the empty login form.
-
-Note that we could have used something like "C<sub default :Path>",
-however, it is generally recommended (partly for historical reasons,
-and partly for code clarity) only to use C<default> in
-C<MyApp::Controller::Root>, and then mainly to generate the 404 not
-found page for the application.
-
-Instead, we are using "C<sub somename :Path :Args(0) {...}>" here to
-specifically match the URL C</login>. C<Path> actions (aka, "literal
-actions") create URI matches relative to the namespace of the
-controller where they are defined. Although C<Path> supports
-arguments that allow relative and absolute paths to be defined, here
-we use an empty C<Path> definition to match on just the name of the
-controller itself. The method name, C<index>, is arbitrary. We make
-the match even more specific with the C<:Args(0)> action modifier --
-this forces the match on I<only> C</login>, not
-C</login/somethingelse>.
-
-Next, update the corresponding method in
-C<lib/MyApp/Controller/Logout.pm> to match:
-
- =head2 index
-
- Logout logic
-
- =cut
-
- sub index :Path :Args(0) {
- my ($self, $c) = @_;
-
- # Clear the user's state
- $c->logout;
-
- # Send the user to the starting point
- $c->response->redirect($c->uri_for('/'));
- }
-
-As with the login controller, be sure to delete the
-C<$c-E<gt>response-E<gt>body('Matched MyApp::Controller::Logout in Logout.');>
-line of the C<sub index>.
-
-
-=head2 Add a Login Form TT Template Page
-
-Create a login form by opening C<root/src/login.tt2> and inserting:
-
- [% META title = 'Login' %]
-
- <!-- Login form -->
- <form method="post" action="[% c.uri_for('/login') %]">
- <table>
- <tr>
- <td>Username:</td>
- <td><input type="text" name="username" size="40" /></td>
- </tr>
- <tr>
- <td>Password:</td>
- <td><input type="password" name="password" size="40" /></td>
- </tr>
- <tr>
- <td colspan="2"><input type="submit" name="submit" value="Submit" /></td>
- </tr>
- </table>
- </form>
-
-
-=head2 Add Valid User Check
-
-We need something that provides enforcement for the authentication
-mechanism -- a I<global> mechanism that prevents users who have not
-passed authentication from reaching any pages except the login page.
-This is generally done via an C<auto> action/method (prior to Catalyst
-v5.66, this sort of thing would go in C<MyApp.pm>, but starting in
-v5.66, the preferred location is C<lib/MyApp/Controller/Root.pm>).
-
-Edit the existing C<lib/MyApp/Controller/Root.pm> class file and insert
-the following method:
-
- =head2 auto
-
- Check if there is a user and, if not, forward to login page
-
- =cut
-
- # Note that 'auto' runs after 'begin' but before your actions and that
- # 'auto's "chain" (all from application path to most specific class are run)
- # See the 'Actions' section of 'Catalyst::Manual::Intro' for more info.
- sub auto : Private {
- my ($self, $c) = @_;
-
- # Allow unauthenticated users to reach the login page. This
- # allows unauthenticated users to reach any action in the Login
- # controller. To lock it down to a single action, we could use:
- # if ($c->action eq $c->controller('Login')->action_for('index'))
- # to only allow unauthenticated access to the 'index' action we
- # added above.
- if ($c->controller eq $c->controller('Login')) {
- return 1;
- }
-
- # If a user doesn't exist, force login
- if (!$c->user_exists) {
- # Dump a log message to the development server debug output
- $c->log->debug('***Root::auto User not found, forwarding to /login');
- # Redirect the user to the login page
- $c->response->redirect($c->uri_for('/login'));
- # Return 0 to cancel 'post-auto' processing and prevent use of application
- return 0;
- }
-
- # User found, so return 1 to continue with processing after this 'auto'
- return 1;
- }
-
-As discussed in
-L<Catalyst::Manual::Tutorial::MoreCatalystBasics/CREATE A CATALYST CONTROLLER>,
-every C<auto> method from the application/root controller down to the
-most specific controller will be called. By placing the
-authentication enforcement code inside the C<auto> method of
-C<lib/MyApp/Controller/Root.pm> (or C<lib/MyApp.pm>), it will be
-called for I<every> request that is received by the entire
-application.
-
-
-=head2 Displaying Content Only to Authenticated Users
-
-Let's say you want to provide some information on the login page that
-changes depending on whether the user has authenticated yet. To do
-this, open C<root/src/login.tt2> in your editor and add the following
-lines to the bottom of the file:
-
- ...
- <p>
- [%
- # This code illustrates how certain parts of the TT
- # template will only be shown to users who have logged in
- %]
- [% IF c.user_exists %]
- Please Note: You are already logged in as '[% c.user.username %]'.
- You can <a href="[% c.uri_for('/logout') %]">logout</a> here.
- [% ELSE %]
- You need to log in to use this application.
- [% END %]
- [%#
- Note that this whole block is a comment because the "#" appears
- immediate after the "[%" (with no spaces in between). Although it
- can be a handy way to temporarily "comment out" a whole block of
- TT code, it's probably a little too subtle for use in "normal"
- comments.
- %]
- </p>
-
-Although most of the code is comments, the middle few lines provide a
-"you are already logged in" reminder if the user returns to the login
-page after they have already authenticated. For users who have not yet
-authenticated, a "You need to log in..." message is displayed (note the
-use of an IF-THEN-ELSE construct in TT).
-
-
-=head2 Try Out Authentication
-
-Press C<Ctrl-C> to kill the previous server instance (if it's still
-running) and restart it:
-
- $ script/myapp_server.pl
-
-B<IMPORTANT NOTE:> If you are having issues with authentication on
-Internet Explorer, be sure to check the system clocks on both your
-server and client machines. Internet Explorer is very picky about
-timestamps for cookies. You can quickly sync a Debian system by
-installing the "ntpdate" package:
-
- sudo aptitude -y install ntpdate
-
-And then run the following command:
-
- sudo ntpdate-debian
-
-Or, depending on your firewall configuration:
-
- sudo ntpdate-debian -u
-
-Note: NTP can be a little more finicky about firewalls because it uses
-UDP vs. the more common TCP that you see with most Internet protocols.
-Worse case, you might have to manually set the time on your development
-box instead of using NTP.
-
-Now trying going to L<http://localhost:3000/books/list> and you should
-be redirected to the login page, hitting Shift+Reload or Ctrl+Reload
-if necessary (the "You are already logged in" message should I<not>
-appear -- if it does, click the C<logout> button and try again). Note
-the C<***Root::auto User not found...> debug message in the
-development server output. Enter username C<test01> and password
-C<mypass>, and you should be taken to the Book List page.
-
-Open C<root/src/books/list.tt2> and add the following lines to the
-bottom (below the closing </table> tag):
-
- <p>
- <a href="[% c.uri_for('/login') %]">Login</a>
- <a href="[% c.uri_for(c.controller.action_for('form_create')) %]">Create</a>
- </p>
-
-Reload your browser and you should now see a "Login" and "Create" links
-at the bottom of the page (as mentioned earlier, you can update template
-files without reloading the development server). Click the first link
-to return to the login page. This time you I<should> see the "You are
-already logged in" message.
-
-Finally, click the C<You can logout here> link on the C</login> page.
-You should stay at the login page, but the message should change to "You
-need to log in to use this application."
-
-
-=head1 USING PASSWORD HASHES
-
-In this section we increase the security of our system by converting
-from cleartext passwords to SHA-1 password hashes that include a
-random "salt" value to make them extremely difficult to crack with
-dictionary and "rainbow table" attacks.
-
-B<Note:> This section is optional. You can skip it and the rest of the
-tutorial will function normally.
-
-Be aware that even with the techniques shown in this section, the browser
-still transmits the passwords in cleartext to your application. We are
-just avoiding the I<storage> of cleartext passwords in the database by
-using a salted SHA-1 hash. If you are concerned about cleartext passwords
-between the browser and your application, consider using SSL/TLS, made
-easy with the Catalyst plugin Catalyst::Plugin:RequireSSL.
-
-
-=head2 Install DBIx::Class::EncodedColumn
-
-L<DBIx::Class::EncodedColumn|DBIx::Class::EncodedColumn> provides features
-that can greatly simplify the maintenance of passwords. It's currently
-not available as a .deb package in the normal Debian repositories, so let's
-install it directly from CPAN:
-
- $ sudo cpan DBIx::Class::EncodedColumn
-
-
-=head2 Re-Run the DBIC::Schema Model Helper to Include DBIx::Class::EncodedColumn
-
-Next, we can re-run the model helper to have it include
-L<DBIx::Class::EncodedColumn|DBIx::Class::EncodedColumn> in all of the
-Result Classes it generates for us. Simply use the same command we
-saw in Chapters 3 and 4, but add C<,EncodedColumn> to the C<components>
-argument:
-
- $ script/myapp_create.pl model DB DBIC::Schema MyApp::Schema \
- create=static components=TimeStamp,EncodedColumn dbi:SQLite:myapp.db
-
-If you then open one of the Result Classes, you will see that it
-includes EncodedColumn in the C<load_components> line. Take a look at
-C<lib/MyApp/Schema/Result/User.pm> since that's the main class where we
-want to use hashed and salted passwords:
-
- __PACKAGE__->load_components("InflateColumn::DateTime", "TimeStamp", "EncodedColumn", "Core");
-
-
-=head2 Modify the "password" Column to Use EncodedColumn
-
-Open the file C<lib/MyApp/Schema/Result/User.pm> and enter the following
-text below the "# DO NOT MODIFY THIS OR ANYTHING ABOVE!" line but above
-the closing "1;":
-
- # Have the 'password' column use a SHA-1 hash and 10-character salt
- # with hex encoding; Generate the 'check_password" method
- __PACKAGE__->add_columns(
- 'password' => {
- data_type => "TEXT",
- size => undef,
- encode_column => 1,
- encode_class => 'Digest',
- encode_args => {salt_length => 10},
- encode_check_method => 'check_password',
- },
- );
-
-This redefines the automatically generated definition for the password
-fields at the top of the Result Class file to now use EncodedColumn
-logic (C<encoded_column> is set to 1). C<encode_class> can be set to
-either C<Digest> to use
-L<DBIx::Class::EncodedColumn::Digest|DBIx::Class::EncodedColumn::Digest>,
-or C<Crypt::Eksblowfish::Bcrypt> for
-L<DBIx::Class::EncodedColumn::Crypt::Eksblowfish::Bcrypt|DBIx::Class::EncodedColumn::Crypt::Eksblowfish::Bcrypt>.
-C<encode_args> is then used to customize the type of Digest you
-selected. Here we only specified the size of the salt to use, but
-we could have also modified the hashing algorithm ('SHA-256' is
-the default) and the format to use ('base64' is the default, but
-'hex' and 'binary' are other options). To use these, you could
-change the C<encode_args> to something like:
-
- encode_args => {algorithm => 'SHA-1',
- format => 'hex',
- salt_length => 10},
-
-
-=head2 Load Hashed Passwords in the Database
-
-Next, let's create a quick script to load some hashed and salted passwords
-into the C<password> column of our C<users> table. Open the file
-C<set_hashed_passwords.pl> in your editor and enter the following text:
-
- #!/usr/bin/perl
-
- use strict;
- use warnings;
-
- use MyApp::Schema;
-
- my $schema = MyApp::Schema->connect('dbi:SQLite:myapp.db');
-
- my @users = $schema->resultset('User')->all;
-
- foreach my $user (@users) {
- $user->password('mypass');
- $user->update;
- }
-
-EncodedColumn lets us simple call C<$user->check_password($password)>
-to see if the user has supplied the correct password, or, as we show
-above, call C<$user->update($new_password)> to update the hashed
-password stored for this user.
-
-Then run the following command:
-
- $ perl -Ilib set_hashed_passwords.pl
-
-We had to use the C<-Ilib> arguement to tell perl to look under the
-C<lib> directory for our C<MyApp::Schema> model.
-
-Then dump the users table to verify that it worked:
-
- $ sqlite3 myapp.db "select * from user"
- 1|test01|38d3974fa9e9263099f7bc2574284b2f55473a9bM=fwpX2NR8|t01 at na.com|Joe|Blow|1
- 2|test02|6ed8586587e53e0d7509b1cfed5df08feadc68cbMJlnPyPt0I|t02 at na.com|Jane|Doe|1
- 3|test03|af929a151340c6aed4d54d7e2651795d1ad2e2f7UW8dHoGv9z|t03 at na.com|No|Go|0
-
-As you can see, the passwords are much harder to steal from the
-database. Also note that this demonstrates how to use a DBIx::Class
-model outside of your web application -- a very useful feature in many
-situations.
-
-
-=head2 Enable Hashed and Salted Passwords
-
-Edit C<lib/MyApp.pm> and update it to match the following text (the only change
-is to the C<password_type> field):
-
- # Configure SimpleDB Authentication
- __PACKAGE__->config->{'Plugin::Authentication'} = {
- default => {
- class => 'SimpleDB',
- user_model => 'DB::User',
- password_type => 'self_check',
- },
- };
-
-The use of C<self_check> will cause
-Catalyst::Plugin::Authentication::Store::DBIC to call the
-C<check_password> method we enabled on our C<password> columns.
-
-
-=head2 Try Out the Hashed Passwords
-
-Press C<Ctrl-C> to kill the previous server instance (if it's still
-running) and restart it:
-
- $ script/myapp_server.pl
-
-You should now be able to go to L<http://localhost:3000/books/list> and
-login as before. When done, click the "logout" link on the login page
-(or point your browser at L<http://localhost:3000/logout>).
-
-
-=head1 USING THE SESSION FOR FLASH
-
-As discussed in the previous chapter of the tutorial, C<flash> allows
-you to set variables in a way that is very similar to C<stash>, but it
-will remain set across multiple requests. Once the value is read, it
-is cleared (unless reset). Although C<flash> has nothing to do with
-authentication, it does leverage the same session plugins. Now that
-those plugins are enabled, let's go back and update the "delete and
-redirect with query parameters" code seen at the end of the L<Basic
-CRUD|Catalyst::Manual::Tutorial::BasicCRUD> chapter of the tutorial to
-take advantage of C<flash>.
-
-First, open C<lib/MyApp/Controller/Books.pm> and modify C<sub delete>
-to match the following (everything after the model search line of code
-has changed):
-
- =head2 delete
-
- Delete a book
-
- =cut
-
- sub delete :Chained('object') :PathPart('delete') :Args(0) {
- my ($self, $c) = @_;
-
- # Use the book object saved by 'object' and delete it along
- # with related 'book_authors' entries
- $c->stash->{object}->delete;
-
- # Use 'flash' to save information across requests until it's read
- $c->flash->{status_msg} = "Book deleted";
-
- # Redirect the user back to the list page
- $c->response->redirect($c->uri_for($self->action_for('list')));
- }
-
-Next, open C<root/src/wrapper.tt2> and update the TT code to pull from
-flash vs. the C<status_msg> query parameter:
-
- ...
- <div id="content">
- [%# Status and error messages %]
- <span class="message">[% status_msg || c.flash.status_msg %]</span>
- <span class="error">[% error_msg %]</span>
- [%# This is where TT will stick all of your template's contents. -%]
- [% content %]
- </div><!-- end content -->
- ...
-
-Although the sample above only shows the C<content> div, leave the
-rest of the file intact -- the only change we made to the C<wrapper.tt2>
-was to add "C<|| c.request.params.status_msg>" to the
-C<E<lt>span class="message"E<gt>> line.
-
-
-=head2 Try Out Flash
-
-Restart the development server, log in, and then point your browser to
-L<http://localhost:3000/books/url_create/Test/1/4> to create an extra
-several books. Click the "Return to list" link and delete one of the
-"Test" books you just added. The C<flash> mechanism should retain our
-"Book deleted" status message across the redirect.
-
-B<NOTE:> While C<flash> will save information across multiple requests,
-I<it does get cleared the first time it is read>. In general, this is
-exactly what you want -- the C<flash> message will get displayed on
-the next screen where it's appropriate, but it won't "keep showing up"
-after that first time (unless you reset it). Please refer to
-L<Catalyst::Plugin::Session|Catalyst::Plugin::Session> for additional
-information.
-
-
-=head2 Switch To Flash-To-Stash
-
-Although the a use of flash above works well, the
-C<status_msg || c.flash.status_msg> statement is a little ugly. A nice
-alternative is to use the C<flash_to_stash> feature that automatically
-copies the content of flash to stash. This makes your controller
-and template code work regardless of where it was directly access, a
-forward, or a redirect. To enable C<flash_to_stash>, you can either
-set the value in C<lib/MyApp.pm> by changing the default
-C<__PACKAGE__-E<gt>config> setting to something like:
-
- __PACKAGE__->config(
- name => 'MyApp',
- session => {flash_to_stash => 1}
- );
-
-B<or> add the following to C<myapp.conf>:
-
- <session>
- flash_to_stash 1
- </session>
-
-The C<__PACKAGE__-E<gt>config> option is probably preferable here
-since it's not something you will want to change at runtime without it
-possibly breaking some of your code.
-
-Then edit C<root/src/wrapper.tt2> and change the C<status_msg> line
-to match the following:
-
- <span class="message">[% status_msg %]</span>
-
-Restart the development server and go to
-L<http://localhost:3000/books/list> in your browser. Delete another
-of the "Test" books you added in the previous step. Flash should still
-maintain the status message across the redirect even though you are no
-longer explicitly accessing C<c.flash>.
-
-
-=head1 AUTHOR
-
-Kennedy Clark, C<hkclark at gmail.com>
-
-Please report any errors, issues or suggestions to the author. The
-most recent version of the Catalyst Tutorial can be found at
-L<http://dev.catalyst.perl.org/repos/Catalyst/Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/>.
-
-Copyright 2006-2008, Kennedy Clark, under Creative Commons License
-(L<http://creativecommons.org/licenses/by-sa/3.0/us/>).
Deleted: Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/Authorization.pod
===================================================================
--- Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/Authorization.pod 2009-05-24 22:09:09 UTC (rev 10276)
+++ Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/Authorization.pod 2009-05-24 22:30:47 UTC (rev 10277)
@@ -1,362 +0,0 @@
-=head1 NAME
-
-Catalyst::Manual::Tutorial::Authorization - Catalyst Tutorial - Chapter 6: Authorization
-
-
-=head1 OVERVIEW
-
-This is B<Chapter 6 of 10> for the Catalyst tutorial.
-
-L<Tutorial Overview|Catalyst::Manual::Tutorial>
-
-=over 4
-
-=item 1
-
-L<Introduction|Catalyst::Manual::Tutorial::Intro>
-
-=item 2
-
-L<Catalyst Basics|Catalyst::Manual::Tutorial::CatalystBasics>
-
-=item 3
-
-L<More Catalyst Basics|Catalyst::Manual::Tutorial::MoreCatalystBasics>
-
-=item 4
-
-L<Basic CRUD|Catalyst::Manual::Tutorial::BasicCRUD>
-
-=item 5
-
-L<Authentication|Catalyst::Manual::Tutorial::Authentication>
-
-=item 6
-
-B<Authorization>
-
-=item 7
-
-L<Debugging|Catalyst::Manual::Tutorial::Debugging>
-
-=item 8
-
-L<Testing|Catalyst::Manual::Tutorial::Testing>
-
-=item 9
-
-L<Advanced CRUD|Catalyst::Manual::Tutorial::AdvancedCRUD>
-
-=item 10
-
-L<Appendices|Catalyst::Manual::Tutorial::Appendices>
-
-=back
-
-
-=head1 DESCRIPTION
-
-This chapter of the tutorial adds role-based authorization to the
-existing authentication implemented in Chapter 5. It provides simple
-examples of how to use roles in both TT templates and controller
-actions. The first half looks at basic authorization concepts. The
-second half looks at how moving your authorization code to your model
-can simplify your code and make things easier to maintain.
-
-You can checkout the source code for this example from the catalyst
-subversion repository as per the instructions in
-L<Catalyst::Manual::Tutorial::Intro|Catalyst::Manual::Tutorial::Intro>.
-
-
-=head1 BASIC AUTHORIZATION
-
-In this section you learn the basics of how authorization works under
-Catalyst.
-
-
-=head2 Update Plugins to Include Support for Authorization
-
-Edit C<lib/MyApp.pm> and add C<Authorization::Roles> to the list:
-
- # Load plugins
- use Catalyst qw/-Debug
- ConfigLoader
- Static::Simple
-
- StackTrace
-
- Authentication
- Authorization::Roles
-
- Session
- Session::Store::FastMmap
- Session::State::Cookie
- /;
-
-B<Note:> As discussed in MoreCatalystBasics, different versions of
-C<Catalyst::Devel> have used a variety of methods to load the plugins.
-You can put the plugins in the C<use Catalyst> statement if you
-prefer.
-
-Once again (remain sharp, by now you should be getting the hang of things)
-include this additional plugin as a new dependency in the Makefile.PL file
-like this:
-
- requires (
- ...
- 'Catalyst::Plugin::Authorization::Roles' => '0',
- );
-
-=head2 Add Role-Specific Logic to the "Book List" Template
-
-Open C<root/src/books/list.tt2> in your editor and add the following
-lines to the bottom of the file:
-
- ...
- <p>Hello [% c.user.username %], you have the following roles:</p>
-
- <ul>
- [% # Dump list of roles -%]
- [% FOR role = c.user.role %]<li>[% role %]</li>[% END %]
- </ul>
-
- <p>
- [% # Add some simple role-specific logic to template %]
- [% # Use $c->check_user_roles() to check authz -%]
- [% IF c.check_user_roles('user') %]
- [% # Give normal users a link for 'logout' %]
- <a href="[% c.uri_for('/logout') %]">User Logout</a>
- [% END %]
-
- [% # Can also use $c->user->check_roles() to check authz -%]
- [% IF c.check_user_roles('admin') %]
- [% # Give admin users a link for 'create' %]
- <a href="[% c.uri_for(c.controller.action_for('form_create')) %]">Admin Create</a>
- [% END %]
- </p>
-
-This code displays a different combination of links depending on the
-roles assigned to the user.
-
-
-=head2 Limit Books::add to 'admin' Users
-
-C<IF> statements in TT templates simply control the output that is sent
-to the user's browser; it provides no real enforcement (if users know or
-guess the appropriate URLs, they are still perfectly free to hit any
-action within your application). We need to enhance the controller
-logic to wrap restricted actions with role-validation logic.
-
-For example, we might want to restrict the "formless create" action to
-admin-level users by editing C<lib/MyApp/Controller/Books.pm> and
-updating C<url_create> to match the following code:
-
- =head2 url_create
-
- Create a book with the supplied title and rating,
- with manual authorization
-
- =cut
-
- sub url_create :Chained('base') :PathPart('url_create') :Args(3) {
- # In addition to self & context, get the title, rating & author_id args
- # from the URL. Note that Catalyst automatically puts extra information
- # after the "/<controller_name>/<action_name/" into @_
- my ($self, $c, $title, $rating, $author_id) = @_;
-
- # Check the user's roles
- if ($c->check_user_roles('admin')) {
- # Call create() on the book model object. Pass the table
- # columns/field values we want to set as hash values
- my $book = $c->model('DB::Book')->create({
- title => $title,
- rating => $rating
- });
-
- # Add a record to the join table for this book, mapping to
- # appropriate author
- $book->add_to_book_author({author_id => $author_id});
- # Note: Above is a shortcut for this:
- # $book->create_related('book_author', {author_id => $author_id});
-
- # Assign the Book object to the stash for display in the view
- $c->stash->{book} = $book;
-
- # Set the TT template to use
- $c->stash->{template} = 'books/create_done.tt2';
- } else {
- # Provide very simple feedback to the user.
- $c->response->body('Unauthorized!');
- }
- }
-
-
-To add authorization, we simply wrap the main code of this method in an
-C<if> statement that calls C<check_user_roles>. If the user does not
-have the appropriate permissions, they receive an "Unauthorized!"
-message. Note that we intentionally chose to display the message this
-way to demonstrate that TT templates will not be used if the response
-body has already been set. In reality you would probably want to use a
-technique that maintains the visual continuity of your template layout
-(for example, using the "status" or "error" message feature added in
-Chapter 3 or C<detach> to an action that shows an "unauthorized" page).
-
-B<TIP>: If you want to keep your existing C<url_create> method, you can
-create a new copy and comment out the original by making it look like a
-Pod comment. For example, put something like C<=begin> before
-C<sub add : Local {> and C<=end> after the closing C<}>.
-
-
-=head2 Try Out Authentication And Authorization
-
-Press C<Ctrl-C> to kill the previous server instance (if it's still
-running) and restart it:
-
- $ script/myapp_server.pl
-
-Now trying going to L<http://localhost:3000/books/list> and you should
-be taken to the login page (you might have to C<Shift+Reload> or
-C<Ctrl+Reload> your browser and/or click the "User Logout" link on the book
-list page). Try logging in with both C<test01> and C<test02> (both
-use a password of C<mypass>) and notice how the roles information
-updates at the bottom of the "Book List" page. Also try the "User Logout"
-link on the book list page.
-
-Now the "url_create" URL will work if you are already logged in as user
-C<test01>, but receive an authorization failure if you are logged in as
-C<test02>. Try:
-
- http://localhost:3000/books/url_create/test/1/6
-
-while logged in as each user. Use one of the "logout" links (or go to
-L<http://localhost:3000/logout> in your browser directly) when you are
-done.
-
-
-=head1 ENABLE MODEL-BASED AUTHORIZATION
-
-Hopefully it's fairly obvious that adding detailed permission checking
-logic to our controllers and view templates isn't a very clean or
-scalable way to build role-based permissions into out application. As
-with many other aspects of MVC web development, the goal is to have
-your controllers and views be an "thin" as possible, with all of the
-"fancy business logic" built into your model.
-
-For example, let's add a method to our C<Books.pm> Result Class to
-check if a user is allowed to delete a book. Open
-C<lib/MyApp/Schema/Result/Book.pm> and add the following method
-(be sure to add it below the "C<DO NOT MODIFY ...>" line):
-
- =head2 delete_allowed_by
-
- Can the specified user delete the current book?
-
- =cut
-
- sub delete_allowed_by {
- my ($self, $user) = @_;
-
- # Only allow delete if user has 'admin' role
- return $user->has_role('admin');
- }
-
-Here we call a C<has_role> method on our user object, so we should add
-this method to our Result Class. Open
-C<lib/MyApp/Schema/Result/User.pm> and add the following method below
-the "C<DO NOT MODIFY ...>" line:
-
- =head 2 has_role
-
- Check if a user has the specified role
-
- =cut
-
- use Perl6::Junction qw/any/;
- sub has_role {
- my ($self, $role) = @_;
-
- # Does this user posses the required role?
- return any(map { $_->role } $self->roles) eq $role;
- }
-
-Now we need to add some enforcement inside our controller. Open
-C<lib/MyApp/Controller/Books.pm> and update the C<delete> method to
-match the following code:
-
- =head2 delete
-
- Delete a book
-
- =cut
-
- sub delete :Chained('object') :PathPart('delete') :Args(0) {
- my ($self, $c) = @_;
-
- # Check permissions
- $c->detach('/error_noperms')
- unless $c->stash->{object}->delete_allowed_by($c->user->get_object);
-
- # Use the book object saved by 'object' and delete it along
- # with related 'book_authors' entries
- $c->stash->{object}->delete;
-
- # Use 'flash' to save information across requests until it's read
- $c->flash->{status_msg} = "Book deleted";
-
- # Redirect the user back to the list page
- $c->response->redirect($c->uri_for($self->action_for('list')));
- }
-
-Here, we C<detach> to an error page if the user is lacking the
-appropriate permissions. For this to work, we need to make
-arrangements for the '/error_noperms' action to work. Open
-C<lib/MyApp/Controller/Root.pm> and add this method:
-
- =head2 error_noperms
-
- Permissions error screen
-
- =cut
-
- sub error_noperms :Chained('/') :PathPath('error_noperms') :Args(0) {
- my ($self, $c) = @_;
-
- $c->stash->{template} = 'error_noperms.tt2';
- }
-
-And also add the template file by putting the following text into
-C<root/src/error_noperms.tt2>:
-
- <span class="error">Permission Denied</span>
-
-Then run the Catalyst development server script:
-
- $ script/myapp_server.pl
-
-Log in as C<test01> and create several new books using the C<url_create>
-feature:
-
- http://localhost:3000/books/url_create/Test/1/4
-
-Then, while still logged in as C<test01>, click the "Delete" link next
-to one of these books. The book should be removed and you should see
-the usual green "Book deleted" message. Next, click the "User Logout"
-link and log back in as C<test02>. Now try deleting one of the books.
-You should be taken to the red "Permission Denied" message on our
-error page.
-
-Use one of the 'Logout' links (or go to the
-L<http://localhost:3000/logout> URL directly) when you are done.
-
-
-=head1 AUTHOR
-
-Kennedy Clark, C<hkclark at gmail.com>
-
-Please report any errors, issues or suggestions to the author. The
-most recent version of the Catalyst Tutorial can be found at
-L<http://dev.catalyst.perl.org/repos/Catalyst/Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/>.
-
-Copyright 2006-2008, Kennedy Clark, under Creative Commons License
-(L<http://creativecommons.org/licenses/by-sa/3.0/us/>).
-
Deleted: Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/BasicCRUD.pod
===================================================================
--- Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/BasicCRUD.pod 2009-05-24 22:09:09 UTC (rev 10276)
+++ Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/BasicCRUD.pod 2009-05-24 22:30:47 UTC (rev 10277)
@@ -1,1302 +0,0 @@
-=head1 NAME
-
-Catalyst::Manual::Tutorial::BasicCRUD - Catalyst Tutorial - Chapter 4: Basic CRUD
-
-
-=head1 OVERVIEW
-
-This is B<Chapter 4 of 10> for the Catalyst tutorial.
-
-L<Tutorial Overview|Catalyst::Manual::Tutorial>
-
-=over 4
-
-=item 1
-
-L<Introduction|Catalyst::Manual::Tutorial::Intro>
-
-=item 2
-
-L<Catalyst Basics|Catalyst::Manual::Tutorial::CatalystBasics>
-
-=item 3
-
-L<More Catalyst Basics|Catalyst::Manual::Tutorial::MoreCatalystBasics>
-
-=item 4
-
-B<Basic CRUD>
-
-=item 5
-
-L<Authentication|Catalyst::Manual::Tutorial::Authentication>
-
-=item 6
-
-L<Authorization|Catalyst::Manual::Tutorial::Authorization>
-
-=item 7
-
-L<Debugging|Catalyst::Manual::Tutorial::Debugging>
-
-=item 8
-
-L<Testing|Catalyst::Manual::Tutorial::Testing>
-
-=item 9
-
-L<Advanced CRUD|Catalyst::Manual::Tutorial::AdvancedCRUD>
-
-=item 10
-
-L<Appendices|Catalyst::Manual::Tutorial::Appendices>
-
-=back
-
-
-=head1 DESCRIPTION
-
-This chapter of the tutorial builds on the fairly primitive
-application created in Chapter 3 to add basic support for Create,
-Read, Update, and Delete (CRUD) of C<Book> objects. Note that the
-'list' function in Chapter 2 already implements the Read portion of
-CRUD (although Read normally refers to reading a single object; you
-could implement full Read functionality using the techniques
-introduced below). This section will focus on the Create and Delete
-aspects of CRUD. More advanced capabilities, including full Update
-functionality, will be addressed in Chapter 9.
-
-Although this chapter of the tutorial will show you how to build CRUD
-functionality yourself, another option is to use a "CRUD builder" type
-of tool to automate the process. You get less control, but it's quick
-and easy. For example, see
-L<CatalystX::ListFramework::Builder|CatalystX::ListFramework::Builder>,
-L<CatalystX::CRUD|CatalystX::CRUD>, and
-L<CatalystX::CRUD::YUI|CatalystX::CRUD::YUI>.
-
-You can check out the source code for this example from the Catalyst
-Subversion repository as per the instructions in
-L<Catalyst::Manual::Tutorial::Intro|Catalyst::Manual::Tutorial::Intro>.
-
-
-=head1 FORMLESS SUBMISSION
-
-Our initial attempt at object creation will utilize the "URL
-arguments" feature of Catalyst (we will employ the more common form-
-based submission in the sections that follow).
-
-
-=head2 Include a Create Action in the Books Controller
-
-Edit C<lib/MyApp/Controller/Books.pm> and enter the following method:
-
- =head2 url_create
-
- Create a book with the supplied title, rating, and author
-
- =cut
-
- sub url_create : Local {
- # In addition to self & context, get the title, rating, &
- # author_id args from the URL. Note that Catalyst automatically
- # puts extra information after the "/<controller_name>/<action_name/"
- # into @_
- my ($self, $c, $title, $rating, $author_id) = @_;
-
- # Call create() on the book model object. Pass the table
- # columns/field values we want to set as hash values
- my $book = $c->model('DB::Book')->create({
- title => $title,
- rating => $rating
- });
-
- # Add a record to the join table for this book, mapping to
- # appropriate author
- $book->add_to_book_author({author_id => $author_id});
- # Note: Above is a shortcut for this:
- # $book->create_related('book_author', {author_id => $author_id});
-
- # Assign the Book object to the stash for display in the view
- $c->stash->{book} = $book;
-
- # Set the TT template to use
- $c->stash->{template} = 'books/create_done.tt2';
- }
-
-Notice that Catalyst takes "extra slash-separated information" from the
-URL and passes it as arguments in C<@_>. The C<url_create> action then
-uses a simple call to the DBIC C<create> method to add the requested
-information to the database (with a separate call to
-C<add_to_book_author> to update the join table). As do virtually all
-controller methods (at least the ones that directly handle user input),
-it then sets the template that should handle this request.
-
-
-=head2 Include a Template for the 'url_create' Action:
-
-Edit C<root/src/books/create_done.tt2> and then enter:
-
- [% # Use the TT Dumper plugin to Data::Dumper variables to the browser -%]
- [% # Not a good idea for production use, though. :-) 'Indent=1' is -%]
- [% # optional, but prevents "massive indenting" of deeply nested objects -%]
- [% USE Dumper(Indent=1) -%]
-
- [% # Set the page title. META can 'go back' and set values in templates -%]
- [% # that have been processed 'before' this template (here it's for -%]
- [% # root/lib/site/html and root/lib/site/header). Note that META only -%]
- [% # works on simple/static strings (i.e. there is no variable -%]
- [% # interpolation). -%]
- [% META title = 'Book Created' %]
-
- [% # Output information about the record that was added. First title. -%]
- <p>Added book '[% book.title %]'
-
- [% # Output the last name of the first author. This is complicated by an -%]
- [% # issue in TT 2.15 where blessed hash objects are not handled right. -%]
- [% # First, fetch 'book.author' from the DB once. -%]
- [% authors = book.author %]
- [% # Now use IF statements to test if 'authors.first' is "working". If so, -%]
- [% # we use it. Otherwise we use a hack that seems to keep TT 2.15 happy. -%]
- by '[% authors.first.last_name IF authors.first;
- authors.list.first.value.last_name IF ! authors.first %]'
-
- [% # Output the rating for the book that was added -%]
- with a rating of [% book.rating %].</p>
-
- [% # Provide a link back to the list page -%]
- [% # 'uri_for()' builds a full URI; e.g., 'http://localhost:3000/books/list' -%]
- <p><a href="[% c.uri_for('/books/list') %]">Return to list</a></p>
-
- [% # Try out the TT Dumper (for development only!) -%]
- <pre>
- Dump of the 'book' variable:
- [% Dumper.dump(book) %]
- </pre>
-
-The TT C<USE> directive allows access to a variety of plugin modules
-(TT plugins, that is, not Catalyst plugins) to add extra functionality
-to the base TT capabilities. Here, the plugin allows
-L<Data::Dumper|Data::Dumper> "pretty printing" of objects and
-variables. Other than that, the rest of the code should be familiar
-from the examples in Chapter 3.
-
-
-=head2 Try the 'url_create' Feature
-
-If the application is still running from before, use C<Ctrl-C> to kill
-it. Then restart the server:
-
- $ DBIC_TRACE=1 script/myapp_server.pl
-
-Note that new path for C</books/url_create> appears in the startup debug
-output.
-
-B<TIP>: You can use C<script/myapp_server.pl -r> to have the development
-server auto-detect changed files and reload itself (if your browser acts
-odd, you should also try throwing in a C<-k>). If you make changes to
-the TT templates only, you do not need to reload the development server
-(only changes to "compiled code" such as Controller and Model C<.pm>
-files require a reload).
-
-Next, use your browser to enter the following URL:
-
- http://localhost:3000/books/url_create/TCPIP_Illustrated_Vol-2/5/4
-
-Your browser should display "Added book 'TCPIP_Illustrated_Vol-2' by
-'Stevens' with a rating of 5." along with a dump of the new book model
-object as it was returned by DBIC. You should also see the following
-DBIC debug messages displayed in the development server log messages
-if you have DBIC_TRACE set:
-
- INSERT INTO book (rating, title) VALUES (?, ?): `5', `TCPIP_Illustrated_Vol-2'
- INSERT INTO book_author (author_id, book_id) VALUES (?, ?): `4', `6'
-
-The C<INSERT> statements are obviously adding the book and linking it to
-the existing record for Richard Stevens. The C<SELECT> statement results
-from DBIC automatically fetching the book for the C<Dumper.dump(book)>.
-
-If you then click the "Return to list" link, you should find that
-there are now six books shown (if necessary, Shift+Reload or
-Ctrl+Reload your browser at the C</books/list> page). You should now see
-the following six DBIC debug messages displayed for N=1-6:
-
- SELECT author.id, author.first_name, author.last_name \
- FROM book_author me JOIN author author \
- ON ( author.id = me.author_id ) WHERE ( me.book_id = ? ): 'N'
-
-
-=head1 CONVERT TO A CHAINED ACTION
-
-Although the example above uses the same C<Local> action type for the
-method that we saw in the previous chapter of the tutorial, there is an
-alternate approach that allows us to be more specific while also
-paving the way for more advanced capabilities. Change the method
-declaration for C<url_create> in C<lib/MyApp/Controller/Books.pm> you
-entered above to match the following:
-
- sub url_create :Chained('/') :PathPart('books/url_create') :Args(3) {
-
-This converts the method to take advantage of the Chained
-action/dispatch type. Chaining lets you have a single URL
-automatically dispatch to several controller methods, each of which
-can have precise control over the number of arguments that it will
-receive. A chain can essentially be thought of having three parts --
-a beginning, a middle, and an end. The bullets below summarize the key
-points behind each of these parts of a chain:
-
-
-=over 4
-
-
-=item *
-
-Beginning
-
-=over 4
-
-=item *
-
-B<Use "C<:Chained('/')>" to start a chain>
-
-=item *
-
-Get arguments through C<CaptureArgs()>
-
-=item *
-
-Specify the path to match with C<PathPart()>
-
-=back
-
-
-=item *
-
-Middle
-
-=over 4
-
-=item *
-
-Link to previous part of the chain with C<:Chained('_name_')>
-
-=item *
-
-Get arguments through C<CaptureArgs()>
-
-=item *
-
-Specify the path to match with C<PathPart()>
-
-=back
-
-
-=item *
-
-End
-
-=over 4
-
-=item *
-
-Link to previous part of the chain with C<:Chained('_name_')>
-
-=item *
-
-B<Do NOT get arguments through "C<CaptureArgs()>," use "C<Args()>" instead to end a chain>
-
-=item *
-
-Specify the path to match with C<PathPart()>
-
-=back
-
-
-=back
-
-In our C<url_create> method above, we have combined all three parts into
-a single method: C<:Chained('/')> to start the chain,
-C<:PathPart('books/url_create')> to specify the base URL to match, and
-C<:Args(3)> to capture exactly three arguments and to end the chain.
-
-As we will see shortly, a chain can consist of as many "links" as you
-wish, with each part capturing some arguments and doing some work
-along the way. We will continue to use the Chained action type in this
-chapter of the tutorial and explore slightly more advanced capabilities
-with the base method and delete feature below. But Chained dispatch
-is capable of far more. For additional information, see
-L<Catalyst::Manual::Intro/Action types>,
-L<Catalyst::DispatchType::Chained|Catalyst::DispatchType::Chained>,
-and the 2006 Advent calendar entry on the subject:
-L<http://www.catalystframework.org/calendar/2006/10>.
-
-
-=head2 Try the Chained Action
-
-If you look back at the development server startup logs from your
-initial version of the C<url_create> method (the one using the
-C<:Local> attribute), you will notice that it produced output similar
-to the following:
-
- [debug] Loaded Path actions:
- .-------------------------------------+--------------------------------------.
- | Path | Private |
- +-------------------------------------+--------------------------------------+
- | / | /default |
- | / | /index |
- | /books | /books/index |
- | /books/list | /books/list |
- | /books/url_create | /books/url_create |
- '-------------------------------------+--------------------------------------'
-
-Now start the development server with our basic chained method in
-place and the startup debug output should change to something along
-the lines of the following:
-
- [debug] Loaded Path actions:
- .-------------------------------------+--------------------------------------.
- | Path | Private |
- +-------------------------------------+--------------------------------------+
- | / | /default |
- | / | /index |
- | /books | /books/index |
- | /books/list | /books/list |
- '-------------------------------------+--------------------------------------'
-
- [debug] Loaded Chained actions:
- .-------------------------------------+--------------------------------------.
- | Path Spec | Private |
- +-------------------------------------+--------------------------------------+
- | /books/url_create/*/*/* | /books/url_create |
- '-------------------------------------+--------------------------------------'
-
-C<url_create> has disappeared form the "Loaded Path actions" section
-but it now shows up under the newly created "Loaded Chained actions"
-section. And the "/*/*/*" portion clearly shows our requirement for
-three arguments.
-
-As with our non-chained version of C<url_create>, use your browser to
-enter the following URL:
-
- http://localhost:3000/books/url_create/TCPIP_Illustrated_Vol-2/5/4
-
-You should see the same "Added book 'TCPIP_Illustrated_Vol-2' by
-'Stevens' with a rating of 5." along with a dump of the new book model
-object. Click the "Return to list" link, and you should find that there
-are now seven books shown (two copies of I<TCPIP_Illustrated_Vol-2>).
-
-
-=head2 Refactor to Use a 'base' Method to Start the Chains
-
-Let's make a quick update to our initial Chained action to show a
-little more of the power of chaining. First, open
-C<lib/MyApp/Controller/Books.pm> in your editor and add the following
-method:
-
- =head2 base
-
- Can place common logic to start chained dispatch here
-
- =cut
-
- sub base :Chained('/') :PathPart('books') :CaptureArgs(0) {
- my ($self, $c) = @_;
-
- # Store the ResultSet in stash so it's available for other methods
- $c->stash->{resultset} = $c->model('DB::Book');
-
- # Print a message to the debug log
- $c->log->debug('*** INSIDE BASE METHOD ***');
- }
-
-Here we print a log message and store the DBIC ResultSet in
-C<$c-E<gt>stash-E<gt>{resultset}> so that it's automatically available
-for other actions that chain off C<base>. If your controller always
-needs a book ID as its first argument, you could have the base method
-capture that argument (with C<:CaptureArgs(1)>) and use it to pull the
-book object with C<-E<gt>find($id)> and leave it in the stash for
-later parts of your chains to then act upon. Because we have several
-actions that don't need to retrieve a book (such as the C<url_create>
-we are working with now), we will instead add that functionality
-to a common C<object> action shortly.
-
-As for C<url_create>, let's modify it to first dispatch to C<base>.
-Open up C<lib/MyApp/Controller/Books.pm> and edit the declaration for
-C<url_create> to match the following:
-
- sub url_create :Chained('base') :PathPart('url_create') :Args(3) {
-
-Next, try out the refactored chain by restarting the development
-server. Notice that our "Loaded Chained actions" section has changed
-slightly:
-
- [debug] Loaded Chained actions:
- .-------------------------------------+--------------------------------------.
- | Path Spec | Private |
- +-------------------------------------+--------------------------------------+
- | /books/url_create/*/*/* | /books/base (0) |
- | | => /books/url_create |
- '-------------------------------------+--------------------------------------'
-
-The "Path Spec" is the same, but now it maps to two Private actions as
-we would expect.
-
-Once again, enter the following URL into your browser:
-
- http://localhost:3000/books/url_create/TCPIP_Illustrated_Vol-2/5/4
-
-The same "Added book 'TCPIP_Illustrated_Vol-2' by 'Stevens' with a
-rating of 5." message and a dump of the new book object should appear.
-Also notice the extra debug message in the development server output
-from the C<base> method. Click the "Return to list" link, and you
-should find that there are now eight books shown.
-
-
-=head1 MANUALLY BUILDING A CREATE FORM
-
-Although the C<url_create> action in the previous step does begin to
-reveal the power and flexibility of both Catalyst and DBIC, it's
-obviously not a very realistic example of how users should be expected
-to enter data. This section begins to address that concern.
-
-
-=head2 Add Method to Display The Form
-
-Edit C<lib/MyApp/Controller/Books.pm> and add the following method:
-
- =head2 form_create
-
- Display form to collect information for book to create
-
- =cut
-
- sub form_create :Chained('base') :PathPart('form_create') :Args(0) {
- my ($self, $c) = @_;
-
- # Set the TT template to use
- $c->stash->{template} = 'books/form_create.tt2';
- }
-
-This action simply invokes a view containing a form to create a book.
-
-
-=head2 Add a Template for the Form
-
-Open C<root/src/books/form_create.tt2> in your editor and enter:
-
- [% META title = 'Manual Form Book Create' -%]
-
- <form method="post" action="[% c.uri_for('form_create_do') %]">
- <table>
- <tr><td>Title:</td><td><input type="text" name="title"></td></tr>
- <tr><td>Rating:</td><td><input type="text" name="rating"></td></tr>
- <tr><td>Author ID:</td><td><input type="text" name="author_id"></td></tr>
- </table>
- <input type="submit" name="Submit" value="Submit">
- </form>
-
-Note that we have specified the target of the form data as
-C<form_create_do>, the method created in the section that follows.
-
-
-=head2 Add a Method to Process Form Values and Update Database
-
-Edit C<lib/MyApp/Controller/Books.pm> and add the following method to
-save the form information to the database:
-
- =head2 form_create_do
-
- Take information from form and add to database
-
- =cut
-
- sub form_create_do :Chained('base') :PathPart('form_create_do') :Args(0) {
- my ($self, $c) = @_;
-
- # Retrieve the values from the form
- my $title = $c->request->params->{title} || 'N/A';
- my $rating = $c->request->params->{rating} || 'N/A';
- my $author_id = $c->request->params->{author_id} || '1';
-
- # Create the book
- my $book = $c->model('DB::Book')->create({
- title => $title,
- rating => $rating,
- });
- # Handle relationship with author
- $book->add_to_book_author({author_id => $author_id});
-
- # Store new model object in stash
- $c->stash->{book} = $book;
-
- # Avoid Data::Dumper issue mentioned earlier
- # You can probably omit this
- $Data::Dumper::Useperl = 1;
-
- # Set the TT template to use
- $c->stash->{template} = 'books/create_done.tt2';
- }
-
-
-=head2 Test Out The Form
-
-If the application is still running from before, use C<Ctrl-C> to kill
-it. Then restart the server:
-
- $ script/myapp_server.pl
-
-Notice that the server startup log reflects the two new chained
-methods that we added:
-
- [debug] Loaded Chained actions:
- .-------------------------------------+--------------------------------------.
- | Path Spec | Private |
- +-------------------------------------+--------------------------------------+
- | /books/form_create | /books/base (0) |
- | | => /books/form_create |
- | /books/form_create_do | /books/base (0) |
- | | => /books/form_create_do |
- | /books/url_create/*/*/* | /books/base (0) |
- | | => /books/url_create |
- '-------------------------------------+--------------------------------------'
-
-Point your browser to L<http://localhost:3000/books/form_create> and
-enter "TCP/IP Illustrated, Vol 3" for the title, a rating of 5, and an
-author ID of 4. You should then see the output of the same
-C<create_done.tt2> template seen in earlier examples. Finally, click
-"Return to list" to view the full list of books.
-
-B<Note:> Having the user enter the primary key ID for the author is
-obviously crude; we will address this concern with a drop-down list in
-Chapter 9.
-
-
-=head1 A SIMPLE DELETE FEATURE
-
-Turning our attention to the Delete portion of CRUD, this section
-illustrates some basic techniques that can be used to remove information
-from the database.
-
-
-=head2 Include a Delete Link in the List
-
-Edit C<root/src/books/list.tt2> and update it to match the following (two
-sections have changed: 1) the additional '<th>Links</th>' table header,
-and 2) the four lines for the Delete link near the bottom):
-
- [% # This is a TT comment. The '-' at the end "chomps" the newline. You won't -%]
- [% # see this "chomping" in your browser because HTML ignores blank lines, but -%]
- [% # it WILL eliminate a blank line if you view the HTML source. It's purely -%]
- [%- # optional, but both the beginning and the ending TT tags support chomping. -%]
-
- [% # Provide a title -%]
- [% META title = 'Book List' -%]
-
- <table>
- <tr><th>Title</th><th>Rating</th><th>Author(s)</th><th>Links</th></tr>
- [% # Display each book in a table row %]
- [% FOREACH book IN books -%]
- <tr>
- <td>[% book.title %]</td>
- <td>[% book.rating %]</td>
- <td>
- [% # First initialize a TT variable to hold a list. Then use a TT FOREACH -%]
- [% # loop in 'side effect notation' to load just the last names of the -%]
- [% # authors into the list. Note that the 'push' TT vmethod doesn't return -%]
- [% # a value, so nothing will be printed here. But, if you have something -%]
- [% # in TT that does return a value and you don't want it printed, you can -%]
- [% # 1) assign it to a bogus value, or # 2) use the CALL keyword to -%]
- [% # call it and discard the return value. -%]
- [% tt_authors = [ ];
- tt_authors.push(author.last_name) FOREACH author = book.author %]
- [% # Now use a TT 'virtual method' to display the author count in parens -%]
- [% # Note the use of the TT filter "| html" to escape dangerous characters -%]
- ([% tt_authors.size | html %])
- [% # Use another TT vmethod to join & print the names & comma separators -%]
- [% tt_authors.join(', ') | html %]
- </td>
- <td>
- [% # Add a link to delete a book %]
- <a href="[% c.uri_for(c.controller.action_for('delete'), [book.id]) %]">Delete</a>
- </td>
- </tr>
- [% END -%]
- </table>
-
-The additional code is obviously designed to add a new column to the
-right side of the table with a C<Delete> "button" (for simplicity, links
-will be used instead of full HTML buttons; in practice, anything that
-modifies data should be handled with a form sending a PUT request).
-
-Also notice that we are using a more advanced form of C<uri_for> than
-we have seen before. Here we use
-C<$c-E<gt>controller-E<gt>action_for> to automatically generate a URI
-appropriate for that action based on the method we want to link to
-while inserting the C<book.id> value into the appropriate place. Now,
-if you ever change C<:PathPart('delete')> in your controller method to
-C<:PathPart('kill')>, then your links will automatically update
-without any changes to your .tt2 template file. As long as the name
-of your method does not change (here, "delete"), then your links will
-still be correct. There are a few shortcuts and options when using
-C<action_for()>:
-
-=over 4
-
-=item *
-
-If you are referring to a method in the current controller, you can
-use C<$self-E<gt>action_for('_method_name_')>.
-
-=item *
-
-If you are referring to a method in a different controller, you need
-to include that controller's name as an argument to C<controller()>, as in
-C<$c-E<gt>controller('_controller_name_')-E<gt>action_for('_method_name_')>.
-
-=back
-
-B<Note:> In practice you should B<never> use a GET request to delete a
-record -- always use POST for actions that will modify data. We are
-doing it here for illustrative and simplicity purposes only.
-
-
-=head2 Add a Common Method to Retrieve a Book for the Chain
-
-As mentioned earlier, since we have a mixture of actions that operate
-on a single book ID and others that do not, we should not have C<base>
-capture the book ID, find the corresponding book in the database and
-save it in the stash for later links in the chain. However, just
-because that logic does not belong in C<base> doesn't mean that we
-can't create another location to centralize the book lookup code. In
-our case, we will create a method called C<object> that will store the
-specific book in the stash. Chains that always operate on a single
-existing book can chain off this method, but methods such as
-C<url_create> that don't operate on an existing book can chain
-directly off base.
-
-To add the C<object> method, edit C<lib/MyApp/Controller/Books.pm>
-and add the following code:
-
- =head2 object
-
- Fetch the specified book object based on the book ID and store
- it in the stash
-
- =cut
-
- sub object :Chained('base') :PathPart('id') :CaptureArgs(1) {
- # $id = primary key of book to delete
- my ($self, $c, $id) = @_;
-
- # Find the book object and store it in the stash
- $c->stash(object => $c->stash->{resultset}->find($id));
-
- # Make sure the lookup was successful. You would probably
- # want to do something like this in a real app:
- # $c->detach('/error_404') if !$c->stash->{object};
- die "Book $id not found!" if !$c->stash->{object};
- }
-
-Now, any other method that chains off C<object> will automatically
-have the appropriate book waiting for it in
-C<$c-E<gt>stash-E<gt>{object}>.
-
-Also note that we are using a different technique for setting
-C<$c-E<gt>stash>. The advantage of this style is that it lets you set
-multiple stash variables at a time. For example:
-
- $c->stash(object => $c->stash->{resultset}->find($id),
- another_thing => 1);
-
-or as a hashref:
-
- $c->stash({object => $c->stash->{resultset}->find($id),
- another_thing => 1});
-
-Either format works, but the C<$c-E<gt>stash(name =E<gt> value);>
-style is growing in popularity -- you may wish to use it all
-the time (even when you are only setting a single value).
-
-
-=head2 Add a Delete Action to the Controller
-
-Open C<lib/MyApp/Controller/Books.pm> in your editor and add the
-following method:
-
- =head2 delete
-
- Delete a book
-
- =cut
-
- sub delete :Chained('object') :PathPart('delete') :Args(0) {
- my ($self, $c) = @_;
-
- # Use the book object saved by 'object' and delete it along
- # with related 'book_author' entries
- $c->stash->{object}->delete;
-
- # Set a status message to be displayed at the top of the view
- $c->stash->{status_msg} = "Book deleted.";
-
- # Forward to the list action/method in this controller
- $c->forward('list');
- }
-
-This method first deletes the book object saved by the C<object> method.
-However, it also removes the corresponding entry from the
-C<book_author> table with a cascading delete.
-
-Then, rather than forwarding to a "delete done" page as we did with the
-earlier create example, it simply sets the C<status_msg> to display a
-notification to the user as the normal list view is rendered.
-
-The C<delete> action uses the context C<forward> method to return the
-user to the book list. The C<detach> method could have also been used.
-Whereas C<forward> I<returns> to the original action once it is
-completed, C<detach> does I<not> return. Other than that, the two are
-equivalent.
-
-
-=head2 Try the Delete Feature
-
-If the application is still running from before, use C<Ctrl-C> to kill
-it. Then restart the server:
-
- $ DBIC_TRACE=1 script/myapp_server.pl
-
-The C<delete> method now appears in the "Loaded Chained actions" section
-of the startup debug output:
-
- [debug] Loaded Chained actions:
- .-------------------------------------+--------------------------------------.
- | Path Spec | Private |
- +-------------------------------------+--------------------------------------+
- | /books/id/*/delete | /books/base (0) |
- | | -> /books/object (1) |
- | | => /books/delete |
- | /books/form_create | /books/base (0) |
- | | => /books/form_create |
- | /books/form_create_do | /books/base (0) |
- | | => /books/form_create_do |
- | /books/url_create/*/*/* | /books/base (0) |
- | | => /books/url_create |
- '-------------------------------------+--------------------------------------'
-
-Then point your browser to L<http://localhost:3000/books/list> and click
-the "Delete" link next to the first "TCPIP_Illustrated_Vol-2". A green
-"Book deleted" status message should display at the top of the page,
-along with a list of the eight remaining books. You will also see the
-cascading delete operation via the DBIC_TRACE output:
-
- SELECT me.id, me.title, me.rating FROM book me WHERE ( ( me.id = ? ) ): '6'
- DELETE FROM book WHERE ( id = ? ): '6'
- SELECT me.book_id, me.author_id FROM book_author me WHERE ( me.book_id = ? ): '6'
- DELETE FROM book_author WHERE ( author_id = ? AND book_id = ? ): '4', '6'
-
-
-=head2 Fixing a Dangerous URL
-
-Note the URL in your browser once you have performed the deletion in the
-prior step -- it is still referencing the delete action:
-
- http://localhost:3000/books/id/6/delete
-
-What if the user were to press reload with this URL still active? In
-this case the redundant delete is harmless (although it does generate
-an exception screen, it doesn't perform any undesirable actions on the
-application or database), but in other cases this could clearly be
-extremely dangerous.
-
-We can improve the logic by converting to a redirect. Unlike
-C<$c-E<gt>forward('list'))> or C<$c-E<gt>detach('list'))> that perform
-a server-side alteration in the flow of processing, a redirect is a
-client-side mechanism that causes the browser to issue an entirely
-new request. As a result, the URL in the browser is updated to match
-the destination of the redirection URL.
-
-To convert the forward used in the previous section to a redirect,
-open C<lib/MyApp/Controller/Books.pm> and edit the existing
-C<sub delete> method to match:
-
- =head2 delete
-
- Delete a book
-
- =cut
-
- sub delete :Chained('object') :PathPart('delete') :Args(0) {
- my ($self, $c) = @_;
-
- # Use the book object saved by 'object' and delete it along
- # with related 'book_author' entries
- $c->stash->{object}->delete;
-
- # Set a status message to be displayed at the top of the view
- $c->stash->{status_msg} = "Book deleted.";
-
- # Redirect the user back to the list page. Note the use
- # of $self->action_for as earlier in this section (BasicCRUD)
- $c->response->redirect($c->uri_for($self->action_for('list')));
- }
-
-
-=head2 Try the Delete and Redirect Logic
-
-Restart the development server and point your browser to
-L<http://localhost:3000/books/list> (don't just hit "Refresh" in your
-browser since we left the URL in an invalid state in the previous
-section!) and delete the first copy of the remaining two
-"TCPIP_Illustrated_Vol-2" books. The URL in your browser should return
-to the L<http://localhost:3000/books/list> URL, so that is an
-improvement, but notice that I<no green "Book deleted" status message is
-displayed>. Because the stash is reset on every request (and a redirect
-involves a second request), the C<status_msg> is cleared before it can
-be displayed.
-
-
-=head2 Using 'uri_for' to Pass Query Parameters
-
-There are several ways to pass information across a redirect. One
-option is to use the C<flash> technique that we will see in Chapter 5
-of this tutorial; however, here we will pass the information via query
-parameters on the redirect itself. Open
-C<lib/MyApp/Controller/Books.pm> and update the existing C<sub delete>
-method to match the following:
-
- =head2 delete
-
- Delete a book
-
- =cut
-
- sub delete :Chained('object') :PathPart('delete') :Args(0) {
- my ($self, $c) = @_;
-
- # Use the book object saved by 'object' and delete it along
- # with related 'book_author' entries
- $c->stash->{object}->delete;
-
- # Redirect the user back to the list page with status msg as an arg
- $c->response->redirect($c->uri_for($self->action_for('list'),
- {status_msg => "Book deleted."}));
- }
-
-This modification simply leverages the ability of C<uri_for> to include
-an arbitrary number of name/value pairs in a hash reference. Next, we
-need to update C<root/src/wrapper.tt2> to handle C<status_msg> as a
-query parameter:
-
- ...
- <div id="content">
- [%# Status and error messages %]
- <span class="message">[% status_msg || c.request.params.status_msg %]</span>
- <span class="error">[% error_msg %]</span>
- [%# This is where TT will stick all of your template's contents. -%]
- [% content %]
- </div><!-- end content -->
- ...
-
-Although the sample above only shows the C<content> div, leave the
-rest of the file intact -- the only change we made to the C<wrapper.tt2>
-was to add "C<|| c.request.params.status_msg>" to the
-C<E<lt>span class="message"E<gt>> line.
-
-
-=head2 Try the Delete and Redirect With Query Param Logic
-
-Restart the development server and point your browser to
-L<http://localhost:3000/books/list> (you should now be able to safely
-hit "refresh" in your browser). Then delete the remaining copy of
-"TCPIP_Illustrated_Vol-2". The green "Book deleted" status message
-should return.
-
-B<NOTE:> Another popular method for maintaining server-side
-information across a redirect is to use the C<flash> technique we
-discuss in the next chapter of the tutorial,
-L<Authentication|Catalyst::Manual::Tutorial::Authentication>. While
-C<flash> is a "slicker" mechanism in that it's all handled by the
-server and doesn't "pollute" your URLs, B<it is important to note that
-C<flash> can lead to situations where the wrong information shows up
-in the wrong browser window if the user has multiple windows or
-browser tabs open>. For example, Window A causes something to be
-placed in the stash, but before that window performs a redirect,
-Window B makes a request to the server and gets the status information
-that should really go to Window A. For this reason, you may wish
-to use the "query param" technique shown here in your applications.
-
-
-=head1 EXPLORING THE POWER OF DBIC
-
-In this section we will explore some additional capabilities offered
-by DBIx::Class. Although these features have relatively little to do
-with Catalyst per se, you will almost certainly want to take advantage
-of them in your applications.
-
-
-=head2 Add Datetime Columns to Our Existing Books Table
-
-Let's add two columns to our existing C<books> table to track when
-each book was added and when each book is updated:
-
- $ sqlite3 myapp.db
- sqlite> ALTER TABLE book ADD created INTEGER;
- sqlite> ALTER TABLE book ADD updated INTEGER;
- sqlite> UPDATE book SET created = DATETIME('NOW'), updated = DATETIME('NOW');
- sqlite> SELECT * FROM book;
- 1|CCSP SNRS Exam Certification Guide|5|2009-03-08 16:26:35|2009-03-08 16:26:35
- 2|TCP/IP Illustrated, Volume 1|5|2009-03-08 16:26:35|2009-03-08 16:26:35
- 3|Internetworking with TCP/IP Vol.1|4|2009-03-08 16:26:35|2009-03-08 16:26:35
- 4|Perl Cookbook|5|2009-03-08 16:26:35|2009-03-08 16:26:35
- 5|Designing with Web Standards|5|2009-03-08 16:26:35|2009-03-08 16:26:35
- 9|TCP/IP Illustrated, Vol 3|5|2009-03-08 16:26:35|2009-03-08 16:26:35
- sqlite> .quit
- $
-
-This will modify the C<books> table to include the two new fields
-and populate those fields with the current time.
-
-
-=head2 Update DBIx::Class to Automatically Handle the Datetime Columns
-
-Next, we should re-run the DBIC helper to update the Result Classes
-with the new fields:
-
- $ script/myapp_create.pl model DB DBIC::Schema MyApp::Schema \
- create=static components=TimeStamp dbi:SQLite:myapp.db
- exists "/root/dev/MyApp/script/../lib/MyApp/Model"
- exists "/root/dev/MyApp/script/../t"
- Dumping manual schema for MyApp::Schema to directory /root/dev/MyApp/script/../lib ...
- Schema dump completed.
- exists "/root/dev/MyApp/script/../lib/MyApp/Model/DB.pm"
-
-Notice that we modified our use of the helper slightly: we told
-it to include the L<DBIx::Class::TimeStamp|DBIx::Class::TimeStamp>
-in the C<load_components> line of the Result Classes.
-
-If you open C<lib/MyApp/Schema/Result/Book.pm> in your editor you
-should see that the C<created> and C<updated> fields are now included
-in the call to C<add_columns()>, but our relationship information below
-the "C<# DO NOT MODIFY...>" line was automatically preserved.
-
-While we have this file open, let's update it with some additional
-information to have DBIC automatically handle the updating of these
-two fields for us. Insert the following code at the bottom of the
-file (it B<must> be B<below> the "C<# DO NOT MODIFY...>" line and
-B<above> the C<1;> on the last line):
-
- #
- # Enable automatic date handling
- #
- __PACKAGE__->add_columns(
- "created",
- { data_type => 'datetime', set_on_create => 1 },
- "updated",
- { data_type => 'datetime', set_on_create => 1, set_on_update => 1 },
- );
-
-This will override the definition for these fields that Schema::Loader
-placed at the top of the file. The C<set_on_create> and
-C<set_on_update> options will cause DBIx::Class to automatically
-update the timestamps in these columns whenever a row is created or
-modified.
-
-To test this out, restart the development server using the
-C<DBIC_TRACE=1> option:
-
- DBIC_TRACE=1 script/myapp_server.pl
-
-Then enter the following URL into your web browser:
-
- http://localhost:3000/books/url_create/TCPIP_Illustrated_Vol-2/5/4
-
-You should get the same "Book Created" screen we saw above. However,
-if you now use the sqlite3 command-line tool to dump the C<books> table,
-you will see that the new book we added has an appropriate date and
-time entered for it (see the last line in the listing below):
-
- sqlite3 myapp.db "select * from book"
- 1|CCSP SNRS Exam Certification Guide|5|2009-03-08 16:26:35|2009-03-08 16:26:35
- 2|TCP/IP Illustrated, Volume 1|5|2009-03-08 16:26:35|2009-03-08 16:26:35
- 3|Internetworking with TCP/IP Vol.1|4|2009-03-08 16:26:35|2009-03-08 16:26:35
- 4|Perl Cookbook|5|2009-03-08 16:26:35|2009-03-08 16:26:35
- 5|Designing with Web Standards|5|2009-03-08 16:26:35|2009-03-08 16:26:35
- 9|TCP/IP Illustrated, Vol 3|5|2009-03-08 16:26:35|2009-03-08 16:26:35
- 10|TCPIP_Illustrated_Vol-2|5|2009-03-08 16:29:08|2009-03-08 16:29:08
-
-Notice in the debug log that the SQL DBIC generated has changed to
-incorporate the datetime logic:
-
- INSERT INTO book (created, rating, title, updated) VALUES (?, ?, ?, ?):
- '2009-03-08 16:29:08', '5', 'TCPIP_Illustrated_Vol-2', '2009-03-08 16:29:08'
- INSERT INTO book_author (author_id, book_id) VALUES (?, ?): '4', '10'
-
-
-=head2 Create a ResultSet Class
-
-An often overlooked but extremely powerful features of DBIC is that it
-allows you to supply your own subclasses of C<DBIx::Class::ResultSet>.
-It allows you to pull complex and unsightly "query code" out of your
-controllers and encapsulate it in a method of your ResultSet Class.
-These "canned queries" in your ResultSet Class can then be invoked
-via a single call, resulting in much cleaner and easier to read
-controller code.
-
-To illustrate the concept with a fairly simple example, let's create a
-method that returns books added in the last 10 minutes. Start by
-making a directory where DBIx::Class will look for our ResultSet Class:
-
- mkdir lib/MyApp/Schema/ResultSet
-
-Then open C<lib/MyApp/Schema/ResultSet/Book.pm> and enter the following:
-
- package MyApp::Schema::ResultSet::Book;
-
- use strict;
- use warnings;
- use base 'DBIx::Class::ResultSet';
-
- =head2 created_after
-
- A predefined search for recently added books
-
- =cut
-
- sub created_after {
- my ($self, $datetime) = @_;
-
- my $date_str = $self->_source_handle->schema->storage
- ->datetime_parser->format_datetime($datetime);
-
- return $self->search({
- created => { '>' => $date_str }
- });
- }
-
- 1;
-
-Then we need to tell the Result Class to to treat this as a ResultSet
-Class. Open C<lib/MyApp/Schema/Result/Book.pm> and add the following
-above the "C<1;>" at the bottom of the file:
-
- #
- # Set ResultSet Class
- #
- __PACKAGE__->resultset_class('MyApp::Schema::ResultSet::Book');
-
-Then add the following method to the C<lib/MyApp/Controller/Books.pm>:
-
- =head2 list_recent
-
- List recently created books
-
- =cut
-
- sub list_recent :Chained('base') :PathPart('list_recent') :Args(1) {
- my ($self, $c, $mins) = @_;
-
- # Retrieve all of the book records as book model objects and store in the
- # stash where they can be accessed by the TT template, but only
- # retrieve books created within the last $min number of minutes
- $c->stash->{books} = [$c->model('DB::Book')
- ->created_after(DateTime->now->subtract(minutes => $mins))];
-
- # Set the TT template to use. You will almost always want to do this
- # in your action methods (action methods respond to user input in
- # your controllers).
- $c->stash->{template} = 'books/list.tt2';
- }
-
-Now start the development server with C<DBIC_TRACE=1> and try
-different values for the minutes argument (the final number value) for
-the URL C<http://localhost:3000/books/list_recent/10>. For example,
-this would list all books added in the last fifteen minutes:
-
- http://localhost:3000/books/list_recent/15
-
-Depending on how recently you added books, you might want to
-try a higher or lower value.
-
-
-=head2 Chaining ResultSets
-
-One of the most helpful and powerful features in DBIx::Class is that
-it allows you to "chain together" a series of queries (note that this
-has nothing to do with the "Chained Dispatch" for Catalyst that we
-were discussing above). Because each ResultSet returns another
-ResultSet, you can take an initial query and immediately feed that
-into a second query (and so on for as many queries you need). Note
-that no matter how many ResultSets you chain together, the database
-itself will not be hit until you use a method that attempts to access
-the data. And, because this technique carries over to the ResultSet
-Class feature we implemented in the previous section for our "canned
-search", we can combine the two capabilities. For example, let's add
-an action to our C<Books> controller that lists books that are both
-recent I<and> have "TCP" in the title. Open up
-C<lib/MyApp/Controller/Books.pm> and add the following method:
-
- =head2 list_recent_tcp
-
- List recently created books
-
- =cut
-
- sub list_recent_tcp :Chained('base') :PathPart('list_recent_tcp') :Args(1) {
- my ($self, $c, $mins) = @_;
-
- # Retrieve all of the book records as book model objects and store in the
- # stash where they can be accessed by the TT template, but only
- # retrieve books created within the last $min number of minutes
- # AND that have 'TCP' in the title
- $c->stash->{books} = [$c->model('DB::Book')
- ->created_after(DateTime->now->subtract(minutes => $mins))
- ->search({title => {'like', '%TCP%'}})
- ];
-
- # Set the TT template to use. You will almost always want to do this
- # in your action methods (action methods respond to user input in
- # your controllers).
- $c->stash->{template} = 'books/list.tt2';
- }
-
-To try this out, restart the development server with:
-
- DBIC_TRACE=1 script/myapp_server.pl
-
-And enter the following URL into your browser:
-
- http://localhost:3000/books/list_recent_tcp/100
-
-And you should get a list of books added in the last 100 minutes that
-contain the string "TCP" in the title. However, if you look at all
-books within the last 100 minutes, you should get a longer list
-(again, you might have to adjust the number of minutes depending on
-how recently you added books to your database):
-
- http://localhost:3000/books/list_recent/100
-
-Take a look at the DBIC_TRACE output in the development server log for
-the first URL and you should see something similar to the following:
-
- SELECT me.id, me.title, me.rating, me.created, me.updated FROM book me
- WHERE ( ( ( title LIKE ? ) AND ( created > ? ) ) ): '%TCP%', '2009-03-08 14:52:54'
-
-However, let's not pollute our controller code with this raw "TCP"
-query -- it would be cleaner to encapsulate that code in a method on
-our ResultSet Class. To do this, open
-C<lib/MyApp/Schema/ResultSet/Book.pm> and add the following method:
-
- =head2 title_like
-
- A predefined search for books with a 'LIKE' search in the string
-
- =cut
-
- sub title_like {
- my ($self, $title_str) = @_;
-
- return $self->search({
- title => { 'like' => "%$title_str%" }
- });
- }
-
-We defined the search string as C<$title_str> to make the method more
-flexible. Now update the C<list_recent_tcp> method in
-C<lib/MyApp/Controller/Books.pm> to match the following (we have
-replaced the C<-E<gt>search> line with the C<-E<gt>title_like> line
-shown here -- the rest of the method should be the same):
-
- =head2 list_recent_tcp
-
- List recently created books
-
- =cut
-
- sub list_recent_tcp :Chained('base') :PathPart('list_recent_tcp') :Args(1) {
- my ($self, $c, $mins) = @_;
-
- # Retrieve all of the book records as book model objects and store in the
- # stash where they can be accessed by the TT template, but only
- # retrieve books created within the last $min number of minutes
- # AND that have 'TCP' in the title
- $c->stash->{books} = [$c->model('DB::Book')
- ->created_after(DateTime->now->subtract(minutes => $mins))
- ->title_like('TCP')
- ];
-
- # Set the TT template to use. You will almost always want to do this
- # in your action methods (action methods respond to user input in
- # your controllers).
- $c->stash->{template} = 'books/list.tt2';
- }
-
-Then restart the development server and try out the C<list_recent_tcp>
-and C<list_recent> URL as we did above. It should work just the same,
-but our code is obviously cleaner and more modular, while also being
-more flexible at the same time.
-
-
-=head2 Adding Methods to Result Classes
-
-In the previous two sections we saw a good example of how we could use
-DBIx::Class ResultSet Classes to clean up our code for an entire query
-(for example, our "canned searches" that filtered the entire query).
-We can do a similar improvement when working with individual rows as
-well. Whereas the ResultSet construct is used in DBIC to correspond
-to an entire query, the Result Class construct is used to represent a
-row. Therefore, we can add row-specific "helper methods" to our Result
-Classes stored in C<lib/MyApp/Schema/Result/>. For example, open
-C<lib/MyApp/Schema/Result/Author.pm> and add the following method (as
-always, it must be above the closing "C<1;>"):
-
- #
- # Helper methods
- #
- sub full_name {
- my ($self) = @_;
-
- return $self->first_name . ' ' . $self->last_name;
- }
-
-This will allow us to conveniently retrieve both the first and last
-name for an author in one shot. Now open C<root/src/books/list.tt2>
-and change the definition of C<tt_authors> from this:
-
- ...
- [% tt_authors = [ ];
- tt_authors.push(author.last_name) FOREACH author = book.author %]
- ...
-
-to:
-
- ...
- [% tt_authors = [ ];
- tt_authors.push(author.full_name) FOREACH author = book.author %]
- ...
-
-(Only C<author.last_name> was changed to C<author.full_name> -- the
-rest of the file should remain the same.)
-
-Now restart the development server and go to the standard book list
-URL:
-
- http://localhost:3000/books/list
-
-The "Author(s)" column will now contain both the first and last name.
-And, because the concatenation logic was encapsulated inside our
-Result Class, it keeps the code inside our .tt template nice and clean
-(remember, we want the templates to be as close to pure HTML markup as
-possible). Obviously, this capability becomes even more useful as you
-use to to remove even more complicated row-specific logic from your
-templates!
-
-
-=head1 AUTHOR
-
-Kennedy Clark, C<hkclark at gmail.com>
-
-Please report any errors, issues or suggestions to the author. The
-most recent version of the Catalyst Tutorial can be found at
-L<http://dev.catalyst.perl.org/repos/Catalyst/Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/>.
-
-Copyright 2006-2008, Kennedy Clark, under Creative Commons License
-(L<http://creativecommons.org/licenses/by-sa/3.0/us/>).
Deleted: Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/CatalystBasics.pod
===================================================================
--- Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/CatalystBasics.pod 2009-05-24 22:09:09 UTC (rev 10276)
+++ Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/CatalystBasics.pod 2009-05-24 22:30:47 UTC (rev 10277)
@@ -1,462 +0,0 @@
-=head1 NAME
-
-Catalyst::Manual::Tutorial::CatalystBasics - Catalyst Tutorial - Chapter 2: Catalyst Application Development Basics
-
-
-=head1 OVERVIEW
-
-This is B<Chapter 2 of 10> for the Catalyst tutorial.
-
-L<Tutorial Overview|Catalyst::Manual::Tutorial>
-
-=over 4
-
-=item 1
-
-L<Introduction|Catalyst::Manual::Tutorial::Intro>
-
-=item 2
-
-B<Catalyst Basics>
-
-=item 3
-
-L<More Catalyst Basics|Catalyst::Manual::Tutorial::MoreCatalystBasics>
-
-=item 4
-
-L<Basic CRUD|Catalyst::Manual::Tutorial::BasicCRUD>
-
-=item 5
-
-L<Authentication|Catalyst::Manual::Tutorial::Authentication>
-
-=item 6
-
-L<Authorization|Catalyst::Manual::Tutorial::Authorization>
-
-=item 7
-
-L<Debugging|Catalyst::Manual::Tutorial::Debugging>
-
-=item 8
-
-L<Testing|Catalyst::Manual::Tutorial::Testing>
-
-=item 9
-
-L<Advanced CRUD|Catalyst::Manual::Tutorial::AdvancedCRUD>
-
-=item 10
-
-L<Appendices|Catalyst::Manual::Tutorial::Appendices>
-
-=back
-
-
-=head1 DESCRIPTION
-
-In this chapter of the tutorial, we will create a very basic Catalyst
-web application, demonstrating a number of powerful capabilities, such
-as:
-
-=over 4
-
-=item * Helper Scripts
-
-Catalyst helper scripts that can be used to rapidly bootstrap the
-skeletal structure of an application.
-
-=item * MVC
-
-Model/View/Controller (MVC) provides an architecture that facilitates a
-clean "separation of control" between the different portions of your
-application. Given that many other documents cover this subject in
-detail, MVC will not be discussed in depth here (for an excellent
-introduction to MVC and general Catalyst concepts, please see
-L<Catalyst::Manual::About|Catalyst::Manual::About>). In short:
-
-=over 4
-
-=item * Model
-
-The model usually represents a data store. In most applications, the
-model equates to the objects that are created from and saved to your SQL
-database.
-
-=item * View
-
-The view takes model objects and renders them into something for the end
-user to look at. Normally this involves a template-generation tool that
-creates HTML for the user's web browser, but it could easily be code
-that generates other forms such as PDF documents, e-mails, spreadsheets,
-or even "behind the scenes" formats such as XML and JSON.
-
-=item * Controller
-
-As suggested by its name, the controller takes user requests and routes
-them to the necessary model and view.
-
-=back
-
-=item * ORM
-
-The use of Object-Relational Mapping (ORM) technology for database
-access. Specifically, ORM provides an automated and standardized means
-to persist and restore objects to/from a relational database.
-
-=back
-
-You can checkout the source code for this example from the catalyst
-subversion repository as per the instructions in
-L<Catalyst::Manual::Tutorial::Intro|Catalyst::Manual::Tutorial::Intro>.
-
-
-=head1 CREATE A CATALYST PROJECT
-
-Catalyst provides a number of helper scripts that can be used to
-quickly flesh out the basic structure of your application. All
-Catalyst projects begin with the C<catalyst.pl> helper (see
-L<Catalyst::Helper|Catalyst::Helper> for more information on helpers).
-Also note that as of Catalyst 5.7000, you will not have the helper
-scripts unless you install both L<Catalyst::Runtime|Catalyst::Runtime>
-and L<Catalyst::Devel|Catalyst::Devel>.
-
-In this first chapter of the tutorial, use the Catalyst C<catalyst.pl>
-script to initialize the framework for an application called C<Hello>:
-
- $ catalyst.pl Hello
- created "Hello"
- created "Hello/script"
- created "Hello/lib"
- created "Hello/root"
- ...
- created "Hello/script/hello_create.pl"
- $ cd Hello
-
-The C<catalyst.pl> helper script will display the names of the
-directories and files it creates:
-
- Changes # Record of application changes
- lib # Lib directory for your app's Perl modules
- Hello # Application main code directory
- Controller # Directory for Controller modules
- Model # Directory for Models
- View # Directory for Views
- Hello.pm # Base application module
- Makefile.PL # Makefile to build application
- hello.conf # Application configuration file
- README # README file
- root # Equiv of htdocs, dir for templates, css, javascript
- favicon.ico
- static # Directory for static files
- images # Directory for image files used in welcome screen
- script # Directory for Perl scripts
- hello_cgi.pl # To run your app as a cgi (not recommended)
- hello_create.pl # To create models, views, controllers
- hello_fastcgi.pl # To run app as a fastcgi program
- hello_server.pl # The normal development server
- hello_test.pl # Test your app from the command line
- t # Directory for tests
- 01app.t # Test scaffold
- 02pod.t
- 03podcoverage.t
-
-
-Catalyst will "auto-discover" modules in the Controller, Model, and
-View directories. When you use the hello_create.pl script it will
-create Perl module scaffolds in those directories, plus test files in
-the "t" directory. The default location for templates is in the "root"
-directory. The scripts in the script directory will always start with
-the lowercased version of your application name. If your app is
-MaiTai, then the create script would be "maitai_create.pl".
-
-Though it's too early for any significant celebration, we already have
-a functioning application. We can use the Catalyst supplied script to
-start up a development server and view the default Catalyst page in
-your browser. All scripts in the script directory should be run from
-the base directory of your application, so change to the Hello
-directory.
-
-Run the following command to start up the built-in development web
-server (make sure you didn't forget the "C<cd Hello>" from the
-previous step):
-
- $ script/hello_server.pl
- [debug] Debug messages enabled
- [debug] Statistics enabled
- [debug] Loaded plugins:
- .----------------------------------------------------------------------------.
- | Catalyst::Plugin::ConfigLoader 0.20 |
- | Catalyst::Plugin::Static::Simple 0.20 |
- '----------------------------------------------------------------------------'
-
- [debug] Loaded dispatcher "Catalyst::Dispatcher"
- [debug] Loaded engine "Catalyst::Engine::HTTP"
- [debug] Found home "/home/me/Hello"
- [debug] Loaded Config "/home/me/Hello/hello.conf"
- [debug] Loaded components:
- .-----------------------------------------------------------------+----------.
- | Class | Type |
- +-----------------------------------------------------------------+----------+
- | Hello::Controller::Root | instance |
- '-----------------------------------------------------------------+----------'
-
- [debug] Loaded Private actions:
- .----------------------+--------------------------------------+--------------.
- | Private | Class | Method |
- +----------------------+--------------------------------------+--------------+
- | /default | Hello::Controller::Root | default |
- | /end | Hello::Controller::Root | end |
- | /index | Hello::Controller::Root | index |
- '----------------------+--------------------------------------+--------------'
-
- [debug] Loaded Path actions:
- .-------------------------------------+--------------------------------------.
- | Path | Private |
- +-------------------------------------+--------------------------------------+
- | / | /default |
- | / | /index |
- '-------------------------------------+--------------------------------------'
-
- [info] Hello powered by Catalyst 5.80003
- You can connect to your server at http://debian:3000
-
-Point your web browser to L<http://localhost:3000> (substituting a
-different hostname or IP address as appropriate) and you should be
-greeted by the Catalyst welcome screen (if you get some other welcome
-screen or an "Index" screen, you probably forgot to specify port 3000
-in your URL). Information similar to the following should be appended
-to the logging output of the development server:
-
- [info] *** Request 1 (0.005/s) [20712] [Sun Mar 8 15:49:09 2009] ***
- [debug] "GET" request for "/" from "1.1.1.98"
- [info] Request took 0.007342s (136.203/s)
- .----------------------------------------------------------------+-----------.
- | Action | Time |
- +----------------------------------------------------------------+-----------+
- | /index | 0.000491s |
- | /end | 0.000595s |
- '----------------------------------------------------------------+-----------'
-
-Press Ctrl-C to break out of the development server.
-
-
-=head1 HELLO WORLD
-
-=head2 The Simplest Way
-
-The Root.pm controller is a place to put global actions that usually
-execute on the root URL. Open the C<lib/Hello/Controller/Root.pm> file in
-your editor. You will see the "index" subroutine, which is
-responsible for displaying the welcome screen that you just saw in
-your browser. Later on you'll want to change that to something more
-reasonable, such as a "404" message or a redirect, but for now just
-leave it alone.
-
- sub index :Path :Args(0) {
- my ( $self, $c ) = @_;
-
- # Hello World
- $c->response->body( $c->welcome_message );
- }
-
-The "C<$c>" here refers to the Catalyst context, which is used to
-access the Catalyst application. In addition to many other things,
-the Catalyst context provides access to "response" and "request"
-objects. (See L<Catalyst|Catalyst>,
-L<Catalyst::Response|Catalyst::Response>, and
-L<Catalyst::Request|Catalyst::Request>)
-
-C<$c-E<gt>response-E<gt>body> sets the HTTP response (see
-L<Catalyst::Response|Catalyst::Response>), while C<$c-E<gt>welcome_message>
-is a special method that returns the welcome message that you saw in
-your browser.
-
-The ":Path :Args(0)" after the method name are attributes which determine
-which URLs will be dispatched to this method. (Depending on your version of
-Catalyst, it used to say "Private" but using that with 'default' or 'index'
-is currently deprecated.)
-
-Some MVC frameworks handle dispatching in a central place. Catalyst,
-by policy, prefers to handle URL dispatching with attributes on
-controller methods. There is a lot of flexibility in specifying which
-URLs to match. This particular method will match all URLs, because it
-doesn't specify the path (nothing comes after "Path"), but will only
-accept a single args because of the ":Args(0)".
-
-The default is to map URLs to controller names, and because of
-the way that Perl handles namespaces through package names,
-it is simple to create hierarchical structures in
-Catalyst. This means that you can create controllers with deeply
-nested actions in a clean and logical way.
-
-For example, the URL C<http://hello.com/admin/articles/create> maps
-to the package C<Hello::Controller::Admin::Articles>, and the C<create>
-method.
-
-Add the following subroutine to your C<lib/Hello/Controller/Root.pm>
-file:
-
- sub hello : Global {
- my ( $self, $c ) = @_;
-
- $c->response->body("Hello, World!");
- }
-
-B<TIP>: See Appendix 1 for tips on removing the leading spaces when
-cutting and pasting example code from POD-based documents.
-
-Here you're sending your own string to the webpage.
-
-Save the file, start the server (stop and restart it if it's still
-up), and go to L<http://localhost:3000/hello> to
-see "Hello, World!"
-
-
-=head2 Hello, World! Using a View and a Template
-
-In the Catalyst world a "View" is not a page of XHTML or a template
-designed to present a page to a browser. It is the module that
-determines the I<type> of view -- HTML, pdf, XML, etc. For the
-thing that generates the I<content> of that view, (such as the
-default Toolkit Template) the actual templates go under the
-"root" directory.
-
-To create a TT view, run:
-
- $ script/hello_create.pl view TT TT
-
-This creates the C<lib/Hello/View/TT.pm> module, which is a subclass of
-C<Catalyst::View::TT>.
-
-=over 4
-
-=item *
-
-The "view" keyword tells the create script that you are creating a view.
-
-=item *
-
-The first "TT" tells the script to name the View module "TT.pm", which is a
-commonly used name for TT views. (You can name it anything you want, such as
-"HTML.pm".)
-
-=item *
-
-The final "TT" tells it that you are creating a Template Toolkit view.
-
-=back
-
-If you look at C<lib/Hello/View/TT.pm> you will find that it only contains a
-config statement to set the TT extension to ".tt".
-
-Now that the TT.pm "View" exists, Catalyst will autodiscover it and be
-able to use it to display the view templates, using the "process"
-method that it inherits from the C<Catalyst::View::TT class>.
-
-Template Toolkit is a very full featured template facility, with
-excellent documentation at L<http://template-toolkit.org/>,
-but since this is not a TT tutorial, we'll stick to only basic TT
-usage here (and explore some of the more common TT features in later
-chapters of the tutorial).
-
-Create a C<root/hello.tt> template file (put it in the C<root> under
-the C<Hello> directory that is the base of your application). Here is
-a simple sample:
-
- <p>
- This is a TT view template, called '[% template.name %]'.
- </p>
-
-[% and %] are markers for the TT parts of the template. Inside you can
-access Perl variables and classes, and use TT directives. In this
-case, we're using a special TT variable that defines the name of the
-template file (C<hello.tt>). The rest of the template is normal HTML.
-
-Change the hello method in C<lib/Hello/Controller/Root.pm> to the
-following:
-
- sub hello : Global {
- my ( $self, $c ) = @_;
-
- $c->stash->{template} = 'hello.tt';
- }
-
-This time, instead of doing C<$c-E<gt>response-E<gt>body()>, you are setting
-the value of the "template" hash key in the Catalyst "stash", an area
-for putting information to share with other parts of your application.
-The "template" key determines which template will be displayed at the
-end of the method. Catalyst controllers have a default "end" action
-for all methods which causes the first (or default) view to be
-rendered (unless there's a C<$c-E<gt>response-E<gt>body()> statement). So your
-template will be magically displayed at the end of your method.
-
-After saving the file, restart the development server, and look at
-L<http://localhost:3000/hello> again. You should
-see the template that you just made.
-
-
-=head1 CREATE A SIMPLE CONTROLLER AND AN ACTION
-
-Create a controller named "Site" by executing the create script:
-
- $ script/hello_create.pl controller Site
-
-This will create a C<lib/Hello/Controller/Site.pm> file (and a test
-file). Bring Site.pm up in your editor, and you can see that there's
-not much there. Most people probably don't bother to use the create
-script to make controllers after they're used to using Catalyst.
-
-In C<lib/Hello/Controller/Site.pm>, add the following method:
-
- sub test : Local {
- my ( $self, $c ) = @_;
-
- $c->stash->{username} = "John";
- $c->stash->{template} = 'site/test.tt';
- }
-
-Notice the "Local" attribute on the C<test> method. This will cause
-the C<test> action (now that we have assigned an action type to the
-method it appears as a controller "action" to Catalyst) to be executed
-on the "controller/method" URL, or, in this case, "site/test". We
-will see additional information on controller actions throughout the
-rest of the tutorial, but if you are curious take a look at
-L<Catalyst::Manual::Intro/Actions>.
-
-It's not actually necessary to set the template value as we do here.
-By default TT will attempt to render a template that follows the
-naming pattern "controller/method.tt", and we're following that
-pattern here. However, in other situations you will need to specify
-the template (such as if you've "forwarded" to the method, or if it
-doesn't follow the default naming convention).
-
-We've also put the variable "username" into the stash, for use in the
-template.
-
-Make a subdirectory "site" in the "root" directory. Copy the hello.tt
-file into the directory as C<root/site/test.tt>, or create a new
-template file at that location. Include a line like:
-
- <p>Hello, [% username %]!</p>
-
-Bring up or restart the server. Notice in the server output that
-C</site/test> is listed in the Loaded Path actions. Go to
-L<http://localhost:3000/site/test> in your browser.
-
-You should see your test.tt file displayed, including the name "John"
-that you set in the controller.
-
-
-=head1 AUTHORS
-
-Gerda Shank, C<gerda.shank at gmail.com>
-Kennedy Clark, C<hkclark at gmail.com>
-
-Please report any errors, issues or suggestions to the author. The
-most recent version of the Catalyst Tutorial can be found at
-L<http://dev.catalyst.perl.org/repos/Catalyst/Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/>.
-
-Copyright 2006-2008, Kennedy Clark & Gerda Shank, under Creative Commons License
-(L<http://creativecommons.org/licenses/by-sa/3.0/us/>).
Deleted: Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/Debugging.pod
===================================================================
--- Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/Debugging.pod 2009-05-24 22:09:09 UTC (rev 10276)
+++ Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/Debugging.pod 2009-05-24 22:30:47 UTC (rev 10277)
@@ -1,380 +0,0 @@
-=head1 NAME
-
-Catalyst::Manual::Tutorial::Debugging - Catalyst Tutorial - Chapter 7: Debugging
-
-
-=head1 OVERVIEW
-
-This is B<Chapter 7 of 10> for the Catalyst tutorial.
-
-L<Tutorial Overview|Catalyst::Manual::Tutorial>
-
-=over 4
-
-=item 1
-
-L<Introduction|Catalyst::Manual::Tutorial::Intro>
-
-=item 2
-
-L<Catalyst Basics|Catalyst::Manual::Tutorial::CatalystBasics>
-
-=item 3
-
-L<More Catalyst Basics|Catalyst::Manual::Tutorial::MoreCatalystBasics>
-
-=item 4
-
-L<Basic CRUD|Catalyst::Manual::Tutorial::BasicCRUD>
-
-=item 5
-
-L<Authentication|Catalyst::Manual::Tutorial::Authentication>
-
-=item 6
-
-L<Authorization|Catalyst::Manual::Tutorial::Authorization>
-
-=item 7
-
-B<Debugging>
-
-=item 8
-
-L<Testing|Catalyst::Manual::Tutorial::Testing>
-
-=item 9
-
-L<Advanced CRUD|Catalyst::Manual::Tutorial::AdvancedCRUD>
-
-=item 10
-
-L<Appendices|Catalyst::Manual::Tutorial::Appendices>
-
-=back
-
-
-=head1 DESCRIPTION
-
-This chapter of the tutorial takes a brief look at the primary options
-available for troubleshooting Catalyst applications.
-
-Note that when it comes to debugging and troubleshooting, there are two
-camps:
-
-=over 4
-
-=item *
-
-Fans of C<log> and C<print> statements embedded in the code.
-
-=item *
-
-Fans of interactive debuggers.
-
-=back
-
-Catalyst is able to easily accommodate both styles of debugging.
-
-
-=head1 LOG STATEMENTS
-
-Folks in the former group can use Catalyst's C<$c-E<gt>log> facility.
-(See L<Catalyst::Log|Catalyst::Log> for more detail.) For example, if
-you add the following code to a controller action method:
-
- $c->log->info("Starting the foreach loop here");
-
- $c->log->debug("Value of \$id is: ".$id);
-
-Then the Catalyst development server will display your message along
-with the other debug output. To accomplish the same thing in a TT
-template view use:
-
- [% c.log.debug("This is a test log message") %]
-
-As with many other logging facilities, you a method is defined for
-each of the following "logging levels" (in increasing order of
-severity/importance):
-
- $c->log->debug
- $c->log->info
- $c->log->warn
- $c->log->error
- $c->log->fatal
-
-You can also use L<Data::Dumper|Data::Dumper> in both Catalyst code
-(C<use Data::Dumper; $c-E<gt>log-E<gt>debug("\$var is: ".Dumper($var));)>)
-and TT templates (C<[% Dumper.dump(book) %]>.
-
-
-=head1 RUNNING CATALYST UNDER THE PERL DEBUGGER
-
-Members of the interactive-debugger fan club will also be at home with
-Catalyst applications. One approach to this style of Perl debugging is
-to embed breakpoints in your code. For example, open
-C<lib/MyApp/Controller/Books.pm> in your editor and add the
-C<DB::single=1> line as follows inside the C<list> method (I like to
-"left-justify" my debug statements so I don't forget to remove them, but
-you can obviously indent them if you prefer):
-
- sub list : Local {
- # Retrieve the usual Perl OO '$self' for this object. $c is the Catalyst
- # 'Context' that's used to 'glue together' the various components
- # that make up the application
- my ($self, $c) = @_;
-
- $DB::single=1;
-
- # Retrieve all of the book records as book model objects and store in the
- # stash where they can be accessed by the TT template
- $c->stash->{books} = [$c->model('DB::Book')->all];
-
- # Set the TT template to use. You will almost always want to do this
- # in your action methods.
- $c->stash->{template} = 'books/list.tt2';
- }
-
-This causes the Perl Debugger to enter "single step mode" when this command is
-encountered (it has no effect when Perl is run without the C<-d> flag).
-
-B<NOTE:> The C<DB> here is the Perl Debugger, not the DB model.
-
-If you haven't done it already, enable SQL logging as before:
-
- $ export DBIC_TRACE=1
-
-To now run the Catalyst development server under the Perl debugger, simply
-prepend C<perl -d> to the front of C<script/myapp_server.pl>:
-
- $ perl -d script/myapp_server.pl
-
-This will start the interactive debugger and produce output similar to:
-
- $ perl -d script/myapp_server.pl
-
- Loading DB routines from perl5db.pl version 1.3
- Editor support available.
-
- Enter h or `h h' for help, or `man perldebug' for more help.
-
- main::(script/myapp_server.pl:16): my $debug = 0;
-
- DB<1>
-
-Press the C<c> key and hit C<Enter> to continue executing the Catalyst
-development server under the debugger. Although execution speed will be
-slightly slower than normal, you should soon see the usual Catalyst
-startup debug information.
-
-Now point your browser to L<http://localhost:3000/books/list> and log
-in. Once the breakpoint is encountered in the
-C<MyApp::Controller::list> method, the console session running the
-development server will drop to the Perl debugger prompt:
-
- MyApp::Controller::Books::list(/home/me/MyApp/script/../lib/MyApp/Controller/Books.pm:48):
- 48: $c->stash->{books} = [$c->model('DB::Book')->all];
-
- DB<1>
-
-You now have the full Perl debugger at your disposal. First use the
-C<next> feature by typing C<n> to execute the C<all> method on the Book
-model (C<n> jumps over method/subroutine calls; you can also use C<s> to
-C<single-step> into methods/subroutines):
-
- DB<1> n
- SELECT me.id, me.title, me.rating, me.created, me.updated FROM book me:
- MyApp::Controller::Books::list(/home/me/MyApp/script/../lib/MyApp/Controller/Books.pm:53):
- 53: $c->stash->{template} = 'books/list.tt2';
-
- DB<1>
-
-This takes you to the next line of code where the template name is set.
-Notice that because we enabled C<DBIC_TRACE=1> earlier, SQL debug
-output also shows up in the development server debug information.
-
-Next, list the methods available on our C<Book> model:
-
- DB<1> m $c->model('DB::Book')
- ()
- (0+
- (bool
- __result_class_accessor
- __source_handle_accessor
- _add_alias
- __bool
- _build_unique_query
- _calculate_score
- _collapse_cond
- <lines removed for brevity>
-
- DB<2>
-
-We can also play with the model directly:
-
- DB<2> x ($c->model('DB::Book')->all)[1]->title
- SELECT me.id, me.title, me.rating, me.created, me.updated FROM book me:
- 0 'TCP/IP Illustrated, Volume 1'
-
-This uses the Perl debugger C<x> command to display the title of a book.
-
-Next we inspect the C<books> element of the Catalyst C<stash> (the C<4>
-argument to the C<x> command limits the depth of the dump to 4 levels):
-
- DB<3> x 4 $c->stash->{books}
- 0 ARRAY(0xa8f3b7c)
- 0 MyApp::Model::DB::Book=HASH(0xb8e702c)
- '_column_data' => HASH(0xb8e5e2c)
- 'created' => '2009-05-08 10:19:46'
- 'id' => 1
- 'rating' => 5
- 'title' => 'CCSP SNRS Exam Certification Guide'
- 'updated' => '2009-05-08 10:19:46'
- '_in_storage' => 1
- <lines removed for brevity>
-
-Then enter the C<c> command to continue processing until the next
-breakpoint is hit (or the application exits):
-
- DB<4> c
- SELECT author.id, author.first_name, author.last_name FROM ...
-
-Finally, press C<Ctrl+C> to break out of the development server.
-Because we are running inside the Perl debugger, you will drop to the
-debugger prompt.
-
- ^CCatalyst::Engine::HTTP::run(/usr/local/share/perl/5.10.0/Catalyst/Engine/HTTP.pm:260):
- 260: while ( accept( Remote, $daemon ) ) {
-
- DB<4>
-
-Finally, press C<q> to exit the debugger and return to your OS
-shell prompt:
-
- DB<4> q
- $
-
-For more information on using the Perl debugger, please see C<perldebug>
-and C<perldebtut>. For those daring souls out there, you can dive down
-even deeper into the magical depths of this fine debugger by checking
-out C<perldebguts>.
-
-You can also type C<h> or C<h h> at the debugger prompt to view the
-built-in help screens.
-
-For an excellent book covering all aspects of the Perl debugger, we highly
-recommend reading 'Pro Perl Debugging' by Richard Foley.
-
-Oh yeah, before you forget, be sure to remove the C<DB::single=1> line you
-added above in C<lib/MyApp/Controller/Books.pm>.
-
-=head1 DEBUGGING MODULES FROM CPAN
-
-Although the techniques discussed above work well for code you are
-writing, what if you want to use print/log/warn messages or set
-breakpoints in code that you have installed from CPAN (or in module that
-ship with Perl)? One helpful approach is to place a copy of the module
-inside the C<lib> directory of your Catalyst project. When Catalyst
-loads, it will load from inside your C<lib> directory first, only
-turning to the global modules if a local copy cannot be found. You can
-then make modifications such as adding a C<$DB::single=1> to the local
-copy of the module without risking the copy in the original location.
-This can also be a great way to "locally override" bugs in modules while
-you wait for a fix on CPAN.
-
-
-Matt Trout has suggested the following shortcut to create a local
-copy of an installed module:
-
- mkdir -p lib/Module; cp `perldoc -l Module::Name` lib/Module/
-
-Note: If you are following along in Debian 5 or Ubuntu, you will
-need to install the C<perl-doc> package to use the C<perldoc> command.
-Use C<sudo aptitude install perl-doc> to do that.
-
-For example, you could make a copy of
-L<Catalyst::Plugin::Authentication|Catalyst::Plugin::Authentication>
-with the following command:
-
- mkdir -p lib/Catalyst/Plugin; cp \
- `perldoc -l Catalyst::Plugin::Authentication` lib/Catalyst/Plugin
-
-You can then use the local copy inside your project to place logging
-messages and/or breakpoints for further study of that module.
-
-B<Note:> Matt has also suggested the following tips for Perl
-debugging:
-
-=over 4
-
-=item *
-
-Check the version of an installed module:
-
- perl -ME<lt>mod_nameE<gt> -e '"print $E<lt>mod_nameE<gt>::VERSION\n"'
-
-For example:
-
- $ perl -MCatalyst::Plugin::Authentication -e \
- 'print $Catalyst::Plugin::Authentication::VERSION;'
- 0.07
-
-and if you are using bash aliases:
-
- alias pmver="perl -le '\$m = shift; eval qq(require \$m) \
- or die qq(module \"\$m\" is not installed\\n); \
- print \$m->VERSION'"
-
-=item *
-
-Check if a modules contains a given method:
-
- perl -MModule::Name -e 'print Module::Name->can("method");'
-
-For example:
-
- $ perl -MCatalyst::Plugin::Authentication -e \
- 'print Catalyst::Plugin::Authentication->can("user");'
- CODE(0x9c8db2c)
-
-If the method exists, the Perl C<can> method returns a coderef.
-Otherwise, it returns undef and nothing will be printed.
-
-=back
-
-
-=head1 TT DEBUGGING
-
-If you run into issues during the rendering of your template, it might
-be helpful to enable TT C<DEBUG> options. You can do this in a Catalyst
-environment by adding a C<DEBUG> line to the C<__PACKAGE__->config>
-declaration in C<lib/MyApp/View/TT.pm>:
-
- __PACKAGE__->config({
- TEMPLATE_EXTENSION => '.tt2',
- DEBUG => 'undef',
- });
-
-There are a variety of options you can use, such as 'undef', 'all',
-'service', 'context', 'parser' and 'provider'. See
-L<Template::Constants|Template::Constants> for more information
-(remove the C<DEBUG_> portion of the name shown in the TT docs and
-convert to lower case for use inside Catalyst).
-
-B<NOTE:> B<Please be sure to disable TT debug options before continuing
-with the tutorial> (especially the 'undef' option -- leaving this
-enabled will conflict with several of the conventions used by this
-tutorial to leave some variables undefined on purpose).
-
-Happy debugging.
-
-=head1 AUTHOR
-
-Kennedy Clark, C<hkclark at gmail.com>
-
-Please report any errors, issues or suggestions to the author. The
-most recent version of the Catalyst Tutorial can be found at
-L<http://dev.catalyst.perl.org/repos/Catalyst/Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/>.
-
-Copyright 2006-2008, Kennedy Clark, under Creative Commons License
-(L<http://creativecommons.org/licenses/by-sa/3.0/us/>).
Deleted: Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/Intro.pod
===================================================================
--- Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/Intro.pod 2009-05-24 22:09:09 UTC (rev 10276)
+++ Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/Intro.pod 2009-05-24 22:30:47 UTC (rev 10277)
@@ -1,642 +0,0 @@
-=head1 NAME
-
-Catalyst::Manual::Tutorial::Intro - Catalyst Tutorial - Chapter 1: Introduction
-
-
-=head1 OVERVIEW
-
-This is B<Chapter 1 of 10> for the Catalyst tutorial.
-
-L<Tutorial Overview|Catalyst::Manual::Tutorial>
-
-=over 4
-
-=item 1
-
-B<Introduction>
-
-=item 2
-
-L<Catalyst Basics|Catalyst::Manual::Tutorial::CatalystBasics>
-
-=item 3
-
-L<More Catalyst Basics|Catalyst::Manual::Tutorial::MoreCatalystBasics>
-
-=item 4
-
-L<Basic CRUD|Catalyst::Manual::Tutorial::BasicCRUD>
-
-=item 5
-
-L<Authentication|Catalyst::Manual::Tutorial::Authentication>
-
-=item 6
-
-L<Authorization|Catalyst::Manual::Tutorial::Authorization>
-
-=item 7
-
-L<Debugging|Catalyst::Manual::Tutorial::Debugging>
-
-=item 8
-
-L<Testing|Catalyst::Manual::Tutorial::Testing>
-
-=item 9
-
-L<Advanced CRUD|Catalyst::Manual::Tutorial::AdvancedCRUD>
-
-=item 10
-
-L<Appendices|Catalyst::Manual::Tutorial::Appendices>
-
-=back
-
-
-=head1 DESCRIPTION
-
-This tutorial provides a multi-part introduction to the Catalyst web
-framework. It seeks to provide a rapid overview of many of its most
-commonly used features. The focus is on the real-world best practices
-required in the construction of nearly all Catalyst applications.
-
-Although the primary target of the tutorial is users new to the Catalyst
-framework, experienced users may wish to review specific sections (for
-example, how to use DBIC for their model classes, how to add
-authentication and authorization to an existing application, or form
-management).
-
-You can obtain the code for all the tutorial examples from the
-catalyst subversion repository by issuing the command:
-
- svn co http://dev.catalyst.perl.org/repos/Catalyst/trunk/examples/Tutorial/ CatalystTutorial
-
-This will download the most recent tarball for each chapter of the
-tutorial into the CatalystTutorial directory on your machine.
-
-B<These reference implementations are provided so that when you follow
-the tutorial, you can use the code from the subversion repository to
-ensure that your system is set up correctly, and that you have not
-inadvertently made any typographic errors, or accidentally skipped
-part of the tutorial.>
-
-B<NOTE: You can use any Perl-supported OS and environment to run
-Catalyst.> It should make little or no difference to Catalyst's
-operation, B<but this tutorial has been written using the Debian 5
-live CD> because that represents a quick and easy for most people to
-try out Catalyst with virtually zero setup time and hassles. Also,
-the tutorial has been tested to work correctly with the versions of
-Catalyst and all the supporting modules in Debian 5 (see "VERSIONS
-AND CONVENTIONS USED IN THIS TUTORIAL" below for the specific versions
-for some of the key modules), so B<if you think you might be running
-into an issue related to versions> (for example, a module changed its
-behavior in a newer version or a bug was introduced), B<it might be
-worth giving Debian 5 a try>. See the "CATALYST INSTALLATION"
-section below for more information.
-
-If you're reading this manual online, you can download the example
-program and all the necessary dependencies to your local machine by
-installing the C<Task::Catalyst::Tutorial> distribution from CPAN:
-
- cpan Task::Catalyst::Tutorial
-
-This will also test to make sure the dependencies are working. If you
-have trouble installing these, please ask for help on the #catalyst
-IRC channel, or the Catalyst mailing list.
-
-Subjects covered by the tutorial include:
-
-=over 4
-
-=item *
-
-A simple application that lists and adds books.
-
-=item *
-
-The use of L<DBIx::Class|DBIx::Class> (DBIC) for the model (including
-some of the more advanced techniques you will probably want to use in
-your applications).
-
-=item *
-
-How to write CRUD (Create, Read, Update, and Delete) operations in
-Catalyst.
-
-=item *
-
-Authentication ("auth").
-
-=item *
-
-Role-based authorization ("authz").
-
-=item *
-
-Attempts to provide an example showing current (5.8XXX) Catalyst
-practices. For example, the use of
-L<Catalyst::Action::RenderView|Catalyst::Action::RenderView>,
-DBIC, L<Catalyst::Plugin::ConfigLoader|Catalyst::Plugin::ConfigLoader>
-with C<myapp.conf>, the use of C<lib/MyApp/Controller/Root.pm>
-vs. C<lib/MyApp.pm>, etc.
-
-=item *
-
-The use of Template Toolkit (TT).
-
-=item *
-
-Useful techniques for troubleshooting and debugging Catalyst
-applications.
-
-=item *
-
-The use of SQLite as a database (with code also provided for MySQL and
-PostgreSQL).
-
-=item *
-
-The use of L<HTML::FormFu|HTML::FormFu> for automated form processing
-and validation.
-
-=back
-
-This tutorial makes the learning process its main priority. For
-example, the level of comments in the code found here would likely be
-considered excessive in a "normal project." Because of their contextual
-value, this tutorial will generally favor inline comments over a
-separate discussion in the text. It also deliberately tries to
-demonstrate multiple approaches to various features (in general, you
-should try to be as consistent as possible with your own production
-code).
-
-Furthermore, this tutorial tries to minimize the number of controllers,
-models, TT templates, and database tables. Although this does result in
-things being a bit contrived at times, the concepts should be applicable
-to more complex environments. More complete and complicated example
-applications can be found in the C<examples> area of the Catalyst
-Subversion repository at
-L<http://dev.catalyst.perl.org/repos/Catalyst/trunk/examples/>.
-
-
-=head1 VERSIONS AND CONVENTIONS USED IN THIS TUTORIAL
-
-This tutorial was built using the following resources. Please note that
-you may need to make adjustments for different environments and
-versions:
-
-=over 4
-
-=item *
-
-Debian 5 (Lenny)
-
-=item *
-
-Catalyst v5.80004
-
-=item *
-
-Catalyst::Devel v1.10
-
-=item *
-
-DBIx::Class v0.08102
-
-=item *
-
-Catalyst Plugins
-
-The plugins used in this tutorial all have sufficiently stable APIs that
-you shouldn't need to worry about versions. However, there could be
-cases where the tutorial is affected by what version of plugins you
-use. This tutorial has been tested against the following set of plugins:
-
-=over 4
-
-=item *
-
-Catalyst::Plugin::Authentication -- v0.10011
-
-=item *
-
-Catalyst::Plugin::Authorization::Roles -- v0.07
-
-=item *
-
-Catalyst::Plugin::ConfigLoader -- v0.22
-
-=item *
-
-Catalyst::Plugin::Session -- v0.20
-
-=item *
-
-Catalyst::Plugin::Session::State::Cookie -- v0.10
-
-=item *
-
-Catalyst::Plugin::Session::Store::FastMmap -- v0.07
-
-=item *
-
-Catalyst::Plugin::StackTrace -- v0.09
-
-=item *
-
-Catalyst::Plugin::Static::Simple -- v0.21
-
-=back
-
-=item *
-
-B<NOTE:> You can check the versions you have installed with the
-following command:
-
- perl -M<_mod_name_> -e '"print $<_mod_name_>::VERSION\n"'
-
-For example:
- perl -MCatalyst::Plugin::StackTrace -e 'print "$Catalyst::Plugin::StackTrace::VERSION\n"'
-
-Since the web browser is being used on the same box where Perl and the
-Catalyst development server is running, the URL of
-C<http://localhost:3000> will be used (the Catalyst development server
-defaults to port 3000). If you are running Perl on a different box than
-where your web browser is located (or using a different port number via
-the C<-p> I<port_number> option to the development server), then you
-will need to update the URL you use accordingly.
-
-=item *
-
-Depending on the web browser you are using, you might need to hit
-C<Shift+Reload> or C<Ctrl+Reload> to pull a fresh page when testing
-your application at various points (see
-L<http://en.wikipedia.org/wiki/Bypass_your_cache> for a comprehensive
-list of options for each browser). Also, the C<-k> keepalive option
-to the development server can be necessary with some browsers
-(especially Internet Explorer).
-
-=back
-
-
-=head1 CATALYST INSTALLATION
-
-Although Catalyst installation has been a challenge in the past, the
-good news is that there are a growing number of options to eliminate
-(or at least dramatically simplify) this concern. Although a
-compelling strength of Catalyst is that it makes use of many of the
-modules in the vast repository that is CPAN, this can complicate the
-installation process if you approach it in the wrong way. Consider
-the following suggestions on the most common ways to get started with
-a Catalyst development environment:
-
-=over 4
-
-=item *
-
-Debian
-
-The Debian 5 live CD represents a great way for newcomers to
-experiment with Catalyst. As a "live CD," you can simple boot from
-the CD, run a few commands, and in a matter of minutes you should have
-a fully function environment in which do this tutorial. B<The tutorial
-was fully tested to work under Debian 5. Although it SHOULD work
-under any Catalyst installation method you might choose, it can be
-hard to guarantee this.>
-
-=over 4
-
-=item *
-
-Download one of the ISO files from
-L<http://cdimage.debian.org/cdimage/release/current-live/i386/iso-cd/>.
-You can pick any one of the live CD variations will work, but
-you may wish to consider the following points:
-
-=over 4
-
-=item *
-
-"C<debian-live-500-i386-rescue.iso>" is probably the best all-around
-option for most people because it includes many extra tools such as
-the GCC compiler, therefore saving RAM (every package you need to
-install when running from live CD consumes memory because RAM disk is
-being used in lieu of real disk space). When initially booting under
-this image, you may see some cryptic warning messages having to do
-with various diagnostic tools it tries to load or enable, but you
-should be able to safely ignore these.
-
-=item *
-
-"C<debian-live-500-i386-standard.iso>" is a great option because of
-its compact size, but you will probably need approximately 1 GB of RAM
-in the computer where you will run the tutorial. Because the
-"standard" live CD comes with with a minimal set of tools, we will
-have to install extra packages (such as the GCC compiler), all of
-which will require RAM when running from a live CD.
-
-=item *
-
-The other ISO images include different flavors of X-Windows desktop
-managers. You can select one of these if you don't mind the larger
-download size and prefer a graphical environment. Be aware that these
-disks do not come with the extra tools found on the "rescue" image, so
-you will need adequate RAM to be able to install them just as you
-would under the "standard" image. B<Use one of the "graphical" ISO
-images if you want a graphical web browser on the same machine as
-where you will run the tutorial.> (If you are using one of the non-
-graphical images discussed above, you can still use a graphical web
-browser from another machine and point it to your Catalyst development
-machine.)
-
-=back
-
-=item *
-
-Boot off the CD.
-
-=item *
-
-Select "C<Live>" from the initial boot menu.
-
-=item *
-
-Once the system has booted to a "C<user at debian:~$>" prompt, enter the
-following command to add the more current "unstable" package
-repository:
-
- sudo vi /etc/apt/sources.list
-
-Add the following line to the bottom of this file:
-
- deb http://ftp.us.debian.org/debian/ unstable main
-
-If you are not familiar with VI, you can move to the bottom of this
-file and press the "o" key to insert a new line and type the line
-above. Then press the "Esc" key followed by a colon (":"), the
-letters "wq" and then the "Enter" key. The rest of the tutorial will
-assume that you know how to use some editor that is available from the
-Linux command-line environment.
-
-=item *
-
-Install Catalyst:
-
- sudo aptitude update
- sudo aptitude -y install sqlite3 libdbd-sqlite3-perl libcatalyst-perl \
- libcatalyst-modules-perl libconfig-general-perl libsql-translator-perl \
- libdatetime-perl libdatetime-format-mysql-perl libio-all-perl \
- libperl6-junction-perl libmoosex-emulate-class-accessor-fast-perl
-
-Let it install (normally about a 30-second operaton) and you are
-done.
-
-If you are using an image other than the "rescue" ISO, you will also need
-to run the following command to install additional packages:
-
- sudo aptitude -y install gcc make libc6-dev
-
-If you are running from the Live CD, you probably also want to free up
-some RAM disk space with the following:
-
- sudo aptitude clean
-
-NOTE: While the instructions above mention the Live CD because that
-makes it easy for people new to Linux, you can obviously pick a
-different Debian ISO image and install it to your hard drive.
-Although there are many different ways to download and install Debian,
-the "netinst" ISO image (such as "C<debian-500-i386-netinst.iso>"
-represents a great option because it keeps your initial download small
-(but still let's you install anything you want "over the network").
-
-Here are some tips if you are running from a live CD and are running
-out of disk space (which really means you are running out of RAM):
-
-=over 4
-
-=item *
-
-Always run "C<aptitude clean>" after you install new packages to
-delete the original .deb files (the files installed B<by> the .deb
-package B<will> remain available, just the .deb package itself is
-deleted).
-
-=item *
-
-If you are installing modules from CPAN, you can free up some space
-with "C<rm -rf /root/.cpan/*>".
-
-=item *
-
-If necessary, you can remove the cached package information with the
-command "C<rm -f /var/lib/apt/lists/*>". You can later pull this
-information again via the command "C<aptitude update>".
-
-=item *
-
-You can save a small amount of space by commenting out the lines in
-C</etc/apt/sources.list> that reference "deb-src" and
-"security.debian.org". If you have already done an "C<aptitude
-update>" with these repositories enabled, you can use the tip in the
-previous bullet to free the space up (and then do another "C<aptitude
-update>").
-
-=item *
-
-Although you can free up space by removing packages you installed
-since you last booted (check out "C<aptitude remove _pkg_name>"),
-don't bother trying to remove packages already available at the time
-of boot. Instead of freeing up space, it will actual I<consume> some
-space. (The live CD uses these "burn in" packages right from the CD
-disk vs. first loading them on the virtual RAM disk. However, if you
-remove them, the system has to update various files, something that
-I<does> consume some space on the virtual RAM disk.)
-
-=back
-
-=back
-
-=item *
-
-Ubuntu
-
-Ubuntu is an extremely popular offshoot of Debian. It provides
-cutting edge versions of many common tools, application and libraries
-in an easy-to-run live CD configuration (and because a single download
-option can be used for both live CD and install-to-disk usage, it
-keeps your download options nice and simple). As with Debian 5, you
-should be able to generate a fully function Catalyst environment in a
-matter of minutes. Here are quick instructions on how to use Ubuntu
-to prepare for the tutorial:
-
-=over 4
-
-=item *
-
-Download the Ubuntu Desktop edition and boot from the CD and/or image
-file, select your language, and then "Try Ubuntu without any changes
-to your computer."
-
-=item *
-
-Open a terminal session (click "Applications" in the upper-left
-corner, then "Accessories," then "Terminal").
-
-=item *
-
-Add the 'universe' repositories:
-
- sudo gedit /etc/apt/sources.list
-
-And remove the comments from the lines under the comments about the
-'universe' repositories.
-
-=item *
-
-Install Catalyst:
-
- sudo aptitude update
- sudo aptitude install libdbd-sqlite3-perl libcatalyst-perl libcatalyst-modules-perl libconfig-general-perl
-
-Accept all of the dependencies. Done.
-
-If you are running from the Live CD, you probably also want to free up
-some disk space with the following:
-
- sudo aptitude clean
-
-NOTE: While the instructions above mention the live CD because that
-makes it easy for people new to Linux, you can obviously also use one
-of the options to install Ubuntu on your drive.
-
-=back
-
-=item *
-
-Matt Trout's C<cat-install>
-
-Available at L<http://www.shadowcatsystems.co.uk/static/cat-install>,
-C<cat-install> can be a fairly painless way to get Catalyst up and
-running. Just download the script from the link above and type C<perl
-cat-install>. Depending on the speed of your Internet connection and
-your computer, it will probably take 30 to 60 minutes to install because
-it downloads, makes, compiles, and tests every module. But this is an
-excellent way to automate the installation of all the latest modules
-used by Catalyst from CPAN.
-
-
-=item *
-
-Other Possibilities
-
-=over 4
-
-=item *
-
-OpenBSD Packages
-
-The 2008 Advent Day 4 entry has more information on using OpenBSD
-packages to quickly build a system:
-L<http://www.catalystframework.org/calendar/2008/4>.
-
-=item *
-
-NetBSD Package Collection on Solaris
-
-The 2008 Advent Day 15 entry has more information on using C<pkgsrc> and
-NetBSD packages on Solaris:
-L<http://www.catalystframework.org/calendar/2008/15>.
-
-=item *
-
-CatInABox
-
-You can get more information at
-L<http://www.catalystframework.org/calendar/2008/7>
-or L<Perl::Dist::CatInABox|Perl::Dist::CatInABox>.
-
-=item *
-
-Frank Speiser's Amazon EC2 Catalyst SDK
-
-There are currently two flavors of publicly available Amazon Machine
-Images (AMI) that include all the elements you'd need to begin
-developing in a fully functional Catalyst environment within minutes.
-See L<Catalyst::Manual::Installation|Catalyst::Manual::Installation>
-for more details.
-
-=back
-
-=back
-
-For additional information and recommendations on Catalyst installation,
-please refer to
-L<Catalyst::Manual::Installation|Catalyst::Manual::Installation>.
-
-
-=head1 DATABASES
-
-This tutorial will primarily focus on SQLite because of its simplicity
-of installation and use; however, modifications in the script required
-to support MySQL and PostgreSQL will be presented in Appendix.
-
-B<Note:> One of the advantages of the MVC design patterns is that
-applications become much more database independent. As such, you will
-notice that only the C<.sql> files used to initialize the database
-change between database systems: the Catalyst code generally remains the
-same.
-
-
-=head1 WHERE TO GET WORKING CODE
-
-Each chapter of the tutorial has complete code available as a tarball in
-the main Catalyst Subversion repository (see the note at the beginning
-of each part for the appropriate svn command to use).
-
-B<NOTE:> You can run the test cases for the final code through Chapter 8
-with the following commands:
-
- sudo cpan Catalyst::Model::DBIC::Schema Time::Warp DBICx::TestDatabase \
- DBIx::Class::DynamicDefault DBIx::Class::TimeStamp DBIx::Class::EncodedColumn
- wget http://dev.catalyst.perl.org/repos/Catalyst/trunk/examples/Tutorial/MyApp_Chapter8.tgz
- tar zxvf MyApp_Chapter8.tgz
- cd MyApp
- CATALYST_DEBUG=0 prove --lib lib t
-
-If you wish to include the L<HTML::FormFu|HTML::FormFu> section in
-your tests, substitute C<MyApp_Chapter9_FormFu.tgz> for
-C<MyApp_Chapter8.tgz> in the URL above. However, you will also need to
-run the following additional commands:
-
- sudo aptitude -y install libhtml-formfu-perl libmoose-perl \
- libregexp-assemble-perl libhtml-formfu-model-dbic-perl
- sudo aptitude clean
- sudo cpan Catalyst::Component::InstancePerContext Catalyst::Controller::HTML::FormFu
-
-You can also fire up the application under the development server that is conveniently
-built in to Catalyst. Just issue this command from the C<MyApp> directory where you
-ran the test suite above:
-
- script/myapp_server.pl
-
-And the application will start. You can try out the application by
-pulling up C<http://localhost:3000> in your web browser (as mentioned
-earlier, change C<localhost> to a different IP address or DNS name if
-you are running your web browser and your Catalyst development on
-different boxes). We will obviously see more about how to use the
-application as we go through the remaining chapters of the tutorial, but
-for now you can log in using the username "test01" and a password of
-"mypass".
-
-
-=head1 AUTHOR
-
-Kennedy Clark, C<hkclark at gmail.com>
-
-Please report any errors, issues or suggestions to the author. The
-most recent version of the Catalyst Tutorial can be found at
-L<http://dev.catalyst.perl.org/repos/Catalyst/Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/>.
-
-Copyright 2006-2008, Kennedy Clark, under Creative Commons License
-(L<http://creativecommons.org/licenses/by-sa/3.0/us/>).
Deleted: Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/MoreCatalystBasics.pod
===================================================================
--- Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/MoreCatalystBasics.pod 2009-05-24 22:09:09 UTC (rev 10276)
+++ Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/MoreCatalystBasics.pod 2009-05-24 22:30:47 UTC (rev 10277)
@@ -1,1498 +0,0 @@
-=head1 NAME
-
-Catalyst::Manual::Tutorial::MoreCatalystBasics - Catalyst Tutorial - Chapter 3: More Catalyst Application Development Basics
-
-
-=head1 OVERVIEW
-
-This is B<Chapter 3 of 10> for the Catalyst tutorial.
-
-L<Tutorial Overview|Catalyst::Manual::Tutorial>
-
-=over 4
-
-=item 1
-
-L<Introduction|Catalyst::Manual::Tutorial::Intro>
-
-=item 2
-
-L<Catalyst Basics|Catalyst::Manual::Tutorial::CatalystBasics>
-
-=item 3
-
-B<More Catalyst Basics>
-
-=item 4
-
-L<Basic CRUD|Catalyst::Manual::Tutorial::BasicCRUD>
-
-=item 5
-
-L<Authentication|Catalyst::Manual::Tutorial::Authentication>
-
-=item 6
-
-L<Authorization|Catalyst::Manual::Tutorial::Authorization>
-
-=item 7
-
-L<Debugging|Catalyst::Manual::Tutorial::Debugging>
-
-=item 8
-
-L<Testing|Catalyst::Manual::Tutorial::Testing>
-
-=item 9
-
-L<Advanced CRUD|Catalyst::Manual::Tutorial::AdvancedCRUD>
-
-=item 10
-
-L<Appendices|Catalyst::Manual::Tutorial::Appendices>
-
-=back
-
-
-=head1 DESCRIPTION
-
-This chapter of the tutorial builds on the work done in Chapter 2 to
-explore some features that are more typical of "real world" web
-applications. From this chapter of the tutorial onward, we will be
-building a simple book database application. Although the application
-will be too limited to be of use to anyone, it should provide a basic
-environment where we can explore a variety of features used in
-virtually all web applications.
-
-You can check out the source code for this example from the Catalyst
-Subversion repository as per the instructions in
-L<Catalyst::Manual::Tutorial::Intro|Catalyst::Manual::Tutorial::Intro>.
-
-Please take a look at
-L<Catalyst::Manual::Tutorial::Intro/CATALYST INSTALLATION> before
-doing the rest of this tutorial. Although the tutorial should work
-correctly under most any recent version of Perl running on any
-operating system, the tutorial has been written using Debian 5 and
-tested to be sure it runs correctly in this environment.
-
-
-=head1 CREATE A NEW APPLICATION
-
-The remainder of the tutorial will build an application called C<MyApp>.
-First use the Catalyst C<catalyst.pl> script to initialize the framework
-for the C<MyApp> application (make sure you aren't still inside the
-directory of the C<Hello> application from the previous chapter of the
-tutorial or in a directory that already has a "MyApp" subdirectory):
-
- $ catalyst.pl MyApp
- created "MyApp"
- created "MyApp/script"
- created "MyApp/lib"
- created "MyApp/root"
- ...
- created "MyApp/script/myapp_create.pl"
- $ cd MyApp
-
-This creates a similar skeletal structure to what we saw in Chapter 2 of
-the tutorial, except with C<MyApp> and C<myapp> substituted for
-C<Hello> and C<hello>.
-
-
-=head1 EDIT THE LIST OF CATALYST PLUGINS
-
-One of the greatest benefits of Catalyst is that it has such a large
-library of plugins and base classes available. Plugins are used to
-seamlessly integrate existing Perl modules into the overall Catalyst
-framework. In general, they do this by adding additional methods to the
-C<context> object (generally written as C<$c>) that Catalyst passes to
-every component throughout the framework.
-
-By default, Catalyst enables three plugins/flags:
-
-=over 4
-
-=item *
-
-C<-Debug> Flag
-
-Enables the Catalyst debug output you saw when we started the
-C<script/myapp_server.pl> development server earlier. You can remove
-this item when you place your application into production.
-
-As you may have noticed, C<-Debug> is not a plugin, but a I<flag>.
-Although most of the items specified on the C<__PACKAGE__-E<gt>setup>
-line of your application class will be plugins, Catalyst supports a
-limited number of flag options (of these, C<-Debug> is the most
-common). See the documentation for C<Catalyst.pm> to get details on
-other flags (currently C<-Engine>, C<-Home>, and C<-Log>).
-
-If you prefer, you can use the C<$c-E<gt>debug> method to enable debug
-messages.
-
-B<TIP>: Depending on your needs, it can be helpful to permanently
-remove C<-Debug> from C<lib/MyApp.pm> and then use the C<-d> option
-to C<script/myapp_server.pl> to re-enable it just for the development
-server. We will not be using that approach in the tutorial, but feel
-free to make use of it in your own projects.
-
-=item *
-
-L<Catalyst::Plugin::ConfigLoader|Catalyst::Plugin::ConfigLoader>
-
-C<ConfigLoader> provides an automatic way to load configurable
-parameters for your application from a central
-L<Config::General|Config::General> file (versus having the values
-hard-coded inside your Perl modules). Config::General uses syntax
-very similar to Apache configuration files. We will see how to use
-this feature of Catalyst during the authentication and authorization
-sections (Chapter 5 and Chapter 6).
-
-B<IMPORTANT NOTE:> If you are using a version of
-L<Catalyst::Devel|Catalyst::Devel> prior to version 1.06, be aware
-that Catalyst changed the default format from YAML to the more
-straightforward C<Config::General> style. This tutorial uses the
-newer C<myapp.conf> file for C<Config::General>. However, Catalyst
-supports both formats and will automatically use either C<myapp.conf>
-or C<myapp.yml> (or any other format supported by
-L<Catalyst::Plugin::ConfigLoader|Catalyst::Plugin::ConfigLoader> and
-L<Config::Any|Config::Any>). If you are using a version of
-Catalyst::Devel prior to 1.06, you can convert to the newer format by
-simply creating the C<myapp.conf> file manually and deleting
-C<myapp.yml>. The default contents of the C<myapp.conf> you create
-should only consist of one line:
-
- name MyApp
-
-B<TIP>: This script can be useful for converting between configuration
-formats:
-
- perl -Ilib -e 'use MyApp; use Config::General;
- Config::General->new->save_file("myapp.conf", MyApp->config);'
-
-=item *
-
-L<Catalyst::Plugin::Static::Simple|Catalyst::Plugin::Static::Simple>
-
-C<Static::Simple> provides an easy way to serve static content, such
-as images and CSS files, from the development server.
-
-=back
-
-For our application, we want to add one new plugin into the mix. To
-do this, edit C<lib/MyApp.pm> (this file is generally referred to as
-your I<application class>) and delete the lines with:
-
- use Catalyst qw/-Debug
- ConfigLoader
- Static::Simple/;
-
-Then replace it with:
-
- # Load plugins
- use Catalyst qw/-Debug
- ConfigLoader
- Static::Simple
-
- StackTrace
- /;
-
-B<Note:> Recent versions of C<Catalyst::Devel> have used a variety of
-techniques to load these plugins/flags. For example, you might see
-the following:
-
- __PACKAGE__->setup(qw/-Debug ConfigLoader Static::Simple/);
-
-Don't let these variations confuse you -- they all accomplish the same
-result.
-
-This tells Catalyst to start using one new plugin,
-L<Catalyst::Plugin::StackTrace|Catalyst::Plugin::StackTrace>, to add a
-stack trace to the standard Catalyst "debug screen" (the screen
-Catalyst sends to your browser when an error occurs). Be aware that
-L<StackTrace|Catalyst::Plugin::StackTrace> output appears in your
-browser, not in the console window from which you're running your
-application, which is where logging output usually goes.
-
-Make sure that when adding new plugins that you include them as a new
-dependancies within the Makefile.PL file. For example, after adding
-the StackTrace plugin the Makefile.PL should include the following
-line:
-
- requires 'Catalyst::Plugin::StackTrace';
-
-
-B<Notes:>
-
-=over 4
-
-=item *
-
-C<__PACKAGE__> is just a shorthand way of referencing the name of the
-package where it is used. Therefore, in C<MyApp.pm>, C<__PACKAGE__>
-is equivalent to C<MyApp>.
-
-=item *
-
-You will want to disable L<StackTrace|Catalyst::Plugin::StackTrace>
-before you put your application into production, but it can be helpful
-during development.
-
-=item *
-
-When specifying plugins on the C<__PACKAGE__-E<gt>setup> line, you can
-omit C<Catalyst::Plugin::> from the name. Additionally, you can
-spread the plugin names across multiple lines as shown here, or place
-them all on one (or more) lines as with the default configuration.
-
-=back
-
-
-=head1 CREATE A CATALYST CONTROLLER
-
-As discussed earlier, controllers are where you write methods that
-interact with user input. Typically, controller methods respond to
-C<GET> and C<POST> requests from the user's web browser.
-
-Use the Catalyst C<create> script to add a controller for book-related
-actions:
-
- $ script/myapp_create.pl controller Books
- exists "/home/me/MyApp/script/../lib/MyApp/Controller"
- exists "/home/me/MyApp/script/../t"
- created "/home/me/MyApp/script/../lib/MyApp/Controller/Books.pm"
- created "/home/me/MyApp/script/../t/controller_Books.t"
-
-Then edit C<lib/MyApp/Controller/Books.pm> (as discussed in Chapter 2 of
-the Tutorial, Catalyst has a separate directory under C<lib/MyApp> for
-each of the three parts of MVC: C<Model>, C<View>, and C<Controller>)
-and add the following method to the controller:
-
- =head2 list
-
- Fetch all book objects and pass to books/list.tt2 in stash to be displayed
-
- =cut
-
- sub list : Local {
- # Retrieve the usual Perl OO '$self' for this object. $c is the Catalyst
- # 'Context' that's used to 'glue together' the various components
- # that make up the application
- my ($self, $c) = @_;
-
- # Retrieve all of the book records as book model objects and store in the
- # stash where they can be accessed by the TT template
- # $c->stash->{books} = [$c->model('DB::Book')->all];
- # But, for now, use this code until we create the model later
- $c->stash->{books} = '';
-
- # Set the TT template to use. You will almost always want to do this
- # in your action methods (action methods respond to user input in
- # your controllers).
- $c->stash->{template} = 'books/list.tt2';
- }
-
-B<TIP>: See Appendix 1 for tips on removing the leading spaces when
-cutting and pasting example code from POD-based documents.
-
-Programmers experienced with object-oriented Perl should recognize
-C<$self> as a reference to the object where this method was called.
-On the other hand, C<$c> will be new to many Perl programmers who have
-not used Catalyst before (it's sometimes written as C<$context>). The
-Context object is automatically passed to all Catalyst components. It
-is used to pass information between components and provide access to
-Catalyst and plugin functionality.
-
-Catalyst actions are regular Perl methods, but they make use of
-attributes (the "C<: Local>" next to the "C<sub list>" in the code
-above) to provide additional information to the Catalyst dispatcher
-logic (note that the space between the colon and the attribute name is
-optional; you will see attributes written both ways). Most Catalyst
-Controllers use one of five action types:
-
-=over 4
-
-=item *
-
-B<:Private> -- Use C<:Private> for methods that you want to make into
-an action, but you do not want Catalyst to directly expose
-to your users. Catalyst will not map C<:Private> methods to a URI.
-Use them for various sorts of "special" methods (the C<begin>,
-C<auto>, etc. discussed below) or for methods you want to be able to
-C<forward> or C<detach> to. (If the method is a plain old "helper
-method" that you don't want to be an action at all, then just define
-the method without any attribute -- you can call it in your code, but
-the Catalyst dispatcher will ignore it.)
-
-There are five types of "special" build-in C<:Private> actions:
-C<begin>, C<end>, C<default>, C<index>, and C<auto>.
-
-=over 4
-
-=item *
-
-With C<begin>, C<end>, C<default>, C<index> private actions, only the
-most specific action of each type will be called. For example, if you
-define a C<begin> action in your controller it will I<override> a
-C<begin> action in your application/root controller -- I<only> the
-action in your controller will be called.
-
-=item *
-
-Unlike the other actions where only a single method is called for each
-request, I<every> auto action along the chain of namespaces will be
-called. Each C<auto> action will be called I<from the application/root
-controller down through the most specific class>.
-
-=back
-
-=item *
-
-B<:Path> -- C<:Path> actions let you map a method to an explicit URI
-path. For example, "C<:Path('list')>" in
-C<lib/MyApp/Controller/Books.pm> would match on the URL
-C<http://localhost:3000/books/list> but "C<:Path('/list')>" would match
-on C<http://localhost:3000/list>. You can use C<:Args()> to specify
-how many arguments an action should accept. See
-L<Catalyst::Manual::Intro/Action_types> for more information and a few
-examples.
-
-=item *
-
-B<:Local> -- C<:Local> is merely a shorthand for
-"C<:Path('_name_of_method_')>". For example, these are equivalent:
-"C<sub create_book :Local {...}>" and
-"C<sub create_book :Path('create_book') {...}>".
-
-=item *
-
-B<:Global> -- C<:Global> is merely a shorthand for
-"C<:Path('/_name_of_method_')>". For example, these are equivalent:
-"C<sub create_book :Global {...}>" and
-"C<sub create_book :Path('/create_book') {...}>".
-
-=item *
-
-B<:Chained> -- Newer Catalyst applications tend to use the Chained
-dispatch form of action types because of its power and flexibility.
-It allows a series of controller methods to be automatically dispatched
-to service a single user request. See
-L<Catalyst::Manual::Tutorial::BasicCRUD|Catalyst::Manual::Tutorial::BasicCRUD>
-and L<Catalyst::DispatchType::Chained|Catalyst::DispatchType::Chained>
-for more information on chained actions.
-
-=back
-
-You should refer to L<Catalyst::Manual::Intro/Action_types> for
-additional information and for coverage of some lesser-used action
-types not discussed here (C<Regex> and C<LocalRegex>).
-
-
-=head1 CATALYST VIEWS
-
-As mentioned in Chapter 2 of the tutorial, views are where you render
-output, typically for display in the user's web browser (but also
-possibly using into output-generation systems, such as PDF or JSON).
-The code in C<lib/MyApp/View> selects the I<type> of view to use, with
-the actual rendering template found in the C<root> directory. As with
-virtually every aspect of Catalyst, options abound when it comes to the
-specific view technology you adopt inside your application. However,
-most Catalyst applications use the Template Toolkit, known as TT (for
-more information on TT, see L<http://www.template-toolkit.org>). Other
-somewhat popular view technologies include Mason
-(L<http://www.masonhq.com> and L<http://www.masonbook.com>) and
-L<HTML::Template> (L<http://html-template.sourceforge.net>).
-
-
-=head2 Create a Catalyst View
-
-When using TT for the Catalyst view, there are two main helper scripts:
-
-=over 4
-
-=item *
-
-L<Catalyst::Helper::View::TT|Catalyst::Helper::View::TT>
-
-=item *
-
-L<Catalyst::Helper::View::TTSite|Catalyst::Helper::View::TTSite>
-
-=back
-
-Both helpers are similar. C<TT> creates the C<lib/MyApp/View/TT.pm>
-file and leaves the creation of any hierarchical template organization
-entirely up to you. (It also creates a C<t/view_TT.t> file for testing;
-test cases will be discussed in Chapter 8.) C<TTSite>, on the other hand,
-creates a modular and hierarchical view layout with
-separate Template Toolkit (TT) files for common header and footer
-information, configuration values, a CSS stylesheet, and more.
-
-While C<TTSite> was useful to bootstrap a project, its use is now
-deprecated and it should be considered historical. For most Catalyst
-applications it adds redundant functionality and structure; many in the
-Catalyst community recommend that it's easier to learn both Catalyst and
-Template Toolkit if you use the more basic C<TT> approach.
-Consequently, this tutorial will use "plain old TT."
-
-Enter the following command to enable the C<TT> style of view
-rendering for this tutorial:
-
- $ script/myapp_create.pl view TT TT
- exists "/home/me/MyApp/script/../lib/MyApp/View"
- exists "/home/me/MyApp/script/../t"
- created "/home/me/MyApp/script/../lib/MyApp/View/TT.pm"
- created "/home/me/MyApp/script/../t/view_TT.t"
-
-This simply creates a view called C<TT> (the second 'TT' argument) in
-a file called C<TT.pm> (the first 'TT' argument). It is now up to you
-to decide how you want to structure your view layout. For the
-tutorial, we will start with a very simple TT template to initially
-demonstrate the concepts, but quickly migrate to a more typical
-"wrapper page" type of configuration (where the "wrapper" controls the
-overall "look and feel" of your site from a single file or set of
-files).
-
-Edit C<lib/MyApp/View/TT.pm> and you should see that the default
-contents contains something similar to the following:
-
- __PACKAGE__->config(TEMPLATE_EXTENSION => '.tt');
-
-And update it to match:
-
- __PACKAGE__->config(
- # Change default TT extension
- TEMPLATE_EXTENSION => '.tt2',
- # Set the location for TT files
- INCLUDE_PATH => [
- MyApp->path_to( 'root', 'src' ),
- ],
- );
-
-B<NOTE:> Make sure to add a comma after '.tt2' outside the single
-quote.
-
-This changes the default extension for Template Toolkit from '.tt' to
-'.tt2' and changes the base directory for your template files from
-C<root> to C<root/src>. These changes from the default are done mostly
-to facilitate the application we're developing in this tutorial; as with
-most things Perl, there's more than one way to do it...
-
-B<Note:> We will use C<root/src> as the base directory for our
-template files, which a full naming convention of
-C<root/src/_controller_name_/_action_name_.tt2>. Another popular option is to
-use C<root/> as the base (with a full filename pattern of
-C<root/_controller_name_/_action_name_.tt2>).
-
-
-=head2 Create a TT Template Page
-
-First create a directory for book-related TT templates:
-
- $ mkdir -p root/src/books
-
-Then create C<root/src/books/list.tt2> in your editor and enter:
-
- [% # This is a TT comment. The '-' at the end "chomps" the newline. You won't -%]
- [% # see this "chomping" in your browser because HTML ignores blank lines, but -%]
- [% # it WILL eliminate a blank line if you view the HTML source. It's purely -%]
- [%- # optional, but both the beginning and the ending TT tags support chomping. -%]
-
- [% # Provide a title -%]
- [% META title = 'Book List' -%]
-
- <table>
- <tr><th>Title</th><th>Rating</th><th>Author(s)</th></tr>
- [% # Display each book in a table row %]
- [% FOREACH book IN books -%]
- <tr>
- <td>[% book.title %]</td>
- <td>[% book.rating %]</td>
- <td></td>
- </tr>
- [% END -%]
- </table>
-
-As indicated by the inline comments above, the C<META title> line uses
-TT's META feature to provide a title to the "wrapper" that we will
-create later. Meanwhile, the C<FOREACH> loop iterates through each
-C<book> model object and prints the C<title> and C<rating> fields.
-
-The C<[%> and C<%]> tags are used to delimit Template Toolkit code. TT
-supports a wide variety of directives for "calling" other files,
-looping, conditional logic, etc. In general, TT simplifies the usual
-range of Perl operators down to the single dot (C<.>) operator. This
-applies to operations as diverse as method calls, hash lookups, and list
-index values (see
-L<http://search.cpan.org/perldoc?Template::Manual::Variables> for
-details and examples). In addition to the usual C<Template> module Pod
-documentation, you can access the TT manual at
-L<http://search.cpan.org/perldoc?Template::Manual>.
-
-B<TIP:> While you can build all sorts of complex logic into your TT
-templates, you should in general keep the "code" part of your templates
-as simple as possible. If you need more complex logic, create helper
-methods in your model that abstract out a set of code into a single call
-from your TT template. (Note that the same is true of your controller
-logic as well -- complex sections of code in your controllers should
-often be pulled out and placed into your model objects.)
-
-
-=head2 Test Run The Application
-
-To test your work so far, first start the development server:
-
- $ script/myapp_server.pl
-
-Then point your browser to L<http://localhost:3000> and you should
-still get the Catalyst welcome page. Next, change the URL in your
-browser to L<http://localhost:3000/books/list>. If you have
-everything working so far, you should see a web page that displays
-nothing other than our column headers for "Title", "Rating", and
-"Author(s)" -- we will not see any books until we get the database and
-model working below.
-
-If you run into problems getting your application to run correctly, it
-might be helpful to refer to some of the debugging techniques covered in
-the L<Debugging|Catalyst::Manual::Tutorial::Debugging> part of the
-tutorial.
-
-
-=head1 CREATE A SQLITE DATABASE
-
-In this step, we make a text file with the required SQL commands to
-create a database table and load some sample data. We will use SQLite,
-a popular database that is lightweight and easy to use. Open
-C<myapp01.sql> in your editor and enter:
-
- --
- -- Create a very simple database to hold book and author information
- --
- CREATE TABLE book (
- id INTEGER PRIMARY KEY,
- title TEXT ,
- rating INTEGER
- );
- -- 'book_author' is a many-to-many join table between books & authors
- CREATE TABLE book_author (
- book_id INTEGER,
- author_id INTEGER,
- PRIMARY KEY (book_id, author_id)
- );
- CREATE TABLE author (
- id INTEGER PRIMARY KEY,
- first_name TEXT,
- last_name TEXT
- );
- ---
- --- Load some sample data
- ---
- INSERT INTO book VALUES (1, 'CCSP SNRS Exam Certification Guide', 5);
- INSERT INTO book VALUES (2, 'TCP/IP Illustrated, Volume 1', 5);
- INSERT INTO book VALUES (3, 'Internetworking with TCP/IP Vol.1', 4);
- INSERT INTO book VALUES (4, 'Perl Cookbook', 5);
- INSERT INTO book VALUES (5, 'Designing with Web Standards', 5);
- INSERT INTO author VALUES (1, 'Greg', 'Bastien');
- INSERT INTO author VALUES (2, 'Sara', 'Nasseh');
- INSERT INTO author VALUES (3, 'Christian', 'Degu');
- INSERT INTO author VALUES (4, 'Richard', 'Stevens');
- INSERT INTO author VALUES (5, 'Douglas', 'Comer');
- INSERT INTO author VALUES (6, 'Tom', 'Christiansen');
- INSERT INTO author VALUES (7, 'Nathan', 'Torkington');
- INSERT INTO author VALUES (8, 'Jeffrey', 'Zeldman');
- INSERT INTO book_author VALUES (1, 1);
- INSERT INTO book_author VALUES (1, 2);
- INSERT INTO book_author VALUES (1, 3);
- INSERT INTO book_author VALUES (2, 4);
- INSERT INTO book_author VALUES (3, 5);
- INSERT INTO book_author VALUES (4, 6);
- INSERT INTO book_author VALUES (4, 7);
- INSERT INTO book_author VALUES (5, 8);
-
-Then use the following command to build a C<myapp.db> SQLite database:
-
- $ sqlite3 myapp.db < myapp01.sql
-
-If you need to create the database more than once, you probably want to
-issue the C<rm myapp.db> command to delete the database before you use
-the C<sqlite3 myapp.db E<lt> myapp01.sql> command.
-
-Once the C<myapp.db> database file has been created and initialized, you
-can use the SQLite command line environment to do a quick dump of the
-database contents:
-
- $ sqlite3 myapp.db
- SQLite version 3.5.9
- Enter ".help" for instructions
- sqlite> select * from book;
- 1|CCSP SNRS Exam Certification Guide|5
- 2|TCP/IP Illustrated, Volume 1|5
- 3|Internetworking with TCP/IP Vol.1|4
- 4|Perl Cookbook|5
- 5|Designing with Web Standards|5
- sqlite> .q
- $
-
-Or:
-
- $ sqlite3 myapp.db "select * from book"
- 1|CCSP SNRS Exam Certification Guide|5
- 2|TCP/IP Illustrated, Volume 1|5
- 3|Internetworking with TCP/IP Vol.1|4
- 4|Perl Cookbook|5
- 5|Designing with Web Standards|5
-
-As with most other SQL tools, if you are using the full "interactive"
-environment you need to terminate your SQL commands with a ";" (it's not
-required if you do a single SQL statement on the command line). Use
-".q" to exit from SQLite from the SQLite interactive mode and return to
-your OS command prompt.
-
-Please note that here we have chosen to use 'singular' table names. This
-is because the default inflection code for L<DBIx::Class:Schema::Loader>
-does NOT handle plurals. There has been much philosophical discussion
-on whether table names should be plural or singular. There is no one
-correct answer, as long as one makes a choice and remains consistent
-with it. If you prefer plural table names (e.g. they are easier and
-more natural to read) then you will need to pass it an inflect_map
-option. See L<DBIx::Class:Schema::Loader> for more information.
-
-For using other databases, such as PostgreSQL or MySQL, see
-L<Appendix 2|Catalyst::Manual::Tutorial::Appendices>.
-
-
-=head1 DATABASE ACCESS WITH DBIx::Class
-
-Catalyst can be used with virtually any form of datastore available
-via Perl. For example, L<Catalyst::Model::DBI|Catalyst::Model::DBI>
-can be used to access databases through the traditional Perl C<DBI>
-interface or you can use a model to access files of any type on the
-filesystem. However, most Catalyst applications use some form of
-object-relational mapping (ORM) technology to create objects
-associated with tables in a relational database. Matt Trout's
-L<DBIx::Class|DBIx::Class> (abbreviated as "DBIC") has rapidly emerged
-as the Perl-based ORM technology of choice. Most new Catalyst
-applications rely on DBIx::Class, as will this tutorial.
-
-Although DBIx::Class has included support for a C<create=dynamic> mode
-to automatically read the database structure every time the
-application starts, it's use is no longer recommended. While it can
-make for "flashy" demos, the use of the C<create=static> mode we use
-below can be implemented just as quickly and provides many advantages
-(such as the ability to add your own methods to the overall DBIC
-framework, a technique that we see in Chapter 4).
-
-
-=head2 Make Sure You Have a Recent Version of the DBIx::Class Model
-
-First, let's be sure we have a recent version of the DBIC helper,
-L<Catalyst::Model::DBIC::Schema|Catalyst::Model::DBIC::Schema>, by
-running this command:
-
- $ perl -MCatalyst::Model::DBIC::Schema -e \
- 'print "$Catalyst::Model::DBIC::Schema::VERSION\n"'
- 0.23
-
-(please note that the '\' above is a line continuation marker and
-should NOT be included as part of the command)
-
-If you don't have version 0.23 or higher, please run this command
-to install it directly from CPAN:
-
- $ sudo cpan Catalyst::Model::DBIC::Schema
-
-And re-run the version print command to verify that you are now at
-0.23 or higher.
-
-
-=head2 Create Static DBIx::Class Schema Files
-
-Before you continue, make sure your C<myapp.db> database file is in
-the application's topmost directory. Now use the model helper with
-the C<create=static> option to read the database with
-L<DBIx::Class::Schema::Loader|DBIx::Class::Schema::Loader> and
-automatically build the required files for us:
-
- $ script/myapp_create.pl model DB DBIC::Schema MyApp::Schema \
- create=static components=TimeStamp dbi:SQLite:myapp.db
- exists "/home/me/MyApp/script/../lib/MyApp/Model"
- exists "/home/me/MyApp/script/../t"
- Dumping manual schema for MyApp::Schema to directory /home/me/MyApp/script/../lib ...
- Schema dump completed.
- created "/home/me/MyApp/script/../lib/MyApp/Model/DB.pm"
- created "/home/me/MyApp/script/../t/model_DB.t"
-
-(please note that the '\' above is a line continuation marker and
-should NOT be included as part of the command)
-
-The C<script/myapp_create.pl> command breaks down like this:
-
-=over 4
-
-=item *
-
-C<DB> is the name of the model class to be created by the helper in
-C<lib/MyApp/Model>.
-
-=item *
-
-C<DBIC::Schema> is the type of the model to create.
-
-=item *
-
-C<MyApp::Schema> is the name of the DBIC schema file written to
-C<lib/MyApp/Schema.pm>.
-
-=item *
-
-C<create=static> causes
-L<DBIx::Class::Schema::Loader|DBIx::Class::Schema::Loader> to
-load the schema as it runs and then write that information out
-into files.
-
-=item *
-
-C<components=TimeStamp> causes the help to include the
-L<DBIx::Class::TimeStamp|DBIx::Class::TimeStamp> DBIC component.
-
-=item *
-
-And finally, C<dbi:SQLite:myapp.db> is the standard DBI connect string
-for use with SQLite.
-
-=back
-
-If you look in the C<lib/MyApp/Schema.pm> file, you will find that it
-only contains a call to the C<load_namespaces> method. You will also
-find that C<lib/MyApp> contains a C<Schema> subdirectory, which then
-has a subdirectory called "Result". This "Result" subdirectory then
-has files named according to each of the tables in our simple database
-(C<Author.pm>, C<BookAuthor.pm>, and C<Book.pm>). These three
-files are called "Result Classes" in DBIx::Class nomenclature. Although the
-Result Class files are named after tables in our database, the classes
-correspond to the I<row-level data> that is returned by DBIC (more on
-this later, especially in
-L<Catalyst::Manual::Tutorial::BasicCRUD/EXPLORING THE POWER OF DBIC>).
-
-The idea with the Result Source files created under
-C<lib/MyApp/Schema/Result> by the C<create=static> option is to only
-edit the files below the C<# DO NOT MODIFY THIS OR ANYTHING ABOVE!>
-warning. If you place all of your changes below that point in the
-file, you can regenerate the automatically created information at the
-top of each file should your database structure get updated.
-
-Also note the "flow" of the model information across the various files
-and directories. Catalyst will initially load the model from
-C<lib/MyApp/Model/DB.pm>. This file contains a reference to
-C<lib/MyApp/Schema.pm>, so that file is loaded next. Finally, the
-call to C<load_namespaces> in C<Schema.pm> will load each of the
-"Result Class" files from the C<lib/MyApp/Schema/Result> subdirectory.
-The final outcome is that Catalyst will dynamically create three
-table-specific Catalyst models every time the application starts (you
-can see these three model files listed in the debug output generated
-when you launch the application).
-
-B<NOTE:> Older versions of
-L<Catalyst::Model::DBIC::Schema|Catalyst::Model::DBIC::Schema> use the
-deprecated DBIx::Class C<load_classes> technique instead of the newer
-C<load_namspaces>. For new applications, please try to use
-C<load_namespaces> since it more easily supports a very useful DBIC
-technique called "ResultSet Classes." If you need to convert an
-existing application from "load_classes" to "load_namespaces," you can
-use this process to automate the migration (but first make sure you
-have v0.23 C<Catalyst::Model::DBIC::Schema> as discussed above):
-
- $ # First delete the existing schema file to disable "compatibility" mode
- $ rm lib/MyApp/Schema.pm
- $
- $ # Then re-run the helper to build the files for "load_namespaces"
- $ script/myapp_create.pl model DB DBIC::Schema MyApp::Schema \
- create=static components=TimeStamp dbi:SQLite:myapp.db
- $
- $ # Note that the '\' above is a line continuation marker and
- $ # should NOT be included as part of the command
-
- $
- $ # Now convert the existing files over
- $ cd lib/MyApp/Schema
- $ perl -MIO::All -e 'for (@ARGV) { my $s < io($_); $s =~ s/.*\n\# You can replace.*?\n//s;
- $s =~ s/'MyApp::Schema::/'MyApp::Schema::Result::/g; my $d < io("Result/$_");
- $d =~ s/1;\n?//; "$d$s" > io("Result/$_"); }' *.pm
- $ cd ../../..
- $
- $ # And finally delete the old files
- $ rm lib/MyApp/Schema/*.pm
-
-The "C<perl -MIO::ALL ...>" script will copy all the customized
-relationship (and other) information below "C<# DO NOT MODIFY>" line
-from the old files in C<lib/MyApp/Schema> to the new files in
-C<lib/MyApp/Schema/Result> (we will be starting to add some
-"customized relationship information in the section below).
-
-
-=head1 ENABLE THE MODEL IN THE CONTROLLER
-
-Open C<lib/MyApp/Controller/Books.pm> and un-comment the model code we
-left disabled earlier so that your version matches the following (un-
-comment the line containing C<[$c-E<gt>model('DB::Book')-E<gt>all]>
-and delete the next 2 lines):
-
- =head2 list
-
- Fetch all book objects and pass to books/list.tt2 in stash to be displayed
-
- =cut
-
- sub list : Local {
- # Retrieve the usual Perl OO '$self' for this object. $c is the Catalyst
- # 'Context' that's used to 'glue together' the various components
- # that make up the application
- my ($self, $c) = @_;
-
- # Retrieve all of the book records as book model objects and store in the
- # stash where they can be accessed by the TT template
- $c->stash->{books} = [$c->model('DB::Book')->all];
-
- # Set the TT template to use. You will almost always want to do this
- # in your action methods (action methods respond to user input in
- # your controllers).
- $c->stash->{template} = 'books/list.tt2';
- }
-
-B<TIP>: You may see the C<$c-E<gt>model('DB::Book')> un-commented
-above written as C<$c-E<gt>model('DB')-E<gt>resultset('Book')>. The
-two are equivalent. Either way, C<$c-E<gt>model> returns a
-L<DBIx::Class::ResultSet|DBIx::Class::ResultSet> which handles queries
-against the database and iterating over the set of results that is
-returned.
-
-We are using the C<-E<gt>all> to fetch all of the books. DBIC
-supports a wide variety of more advanced operations to easily do
-things like filtering and sorting the results. For example, the
-following could be used to sort the results by descending title:
-
- $c->model('DB::Book')->search({}, {order_by => 'title DESC'});
-
-Some other examples are provided in
-L<DBIx::Class::Manual::Cookbook/Complex WHERE clauses>, with
-additional information found at L<DBIx::Class::ResultSet/search>,
-L<DBIx::Class::Manual::FAQ/Searching>,
-L<DBIx::Class::Manual::Intro|DBIx::Class::Manual::Intro>
-and L<Catalyst::Model::DBIC::Schema|Catalyst::Model::DBIC::Schema>.
-
-
-=head2 Test Run The Application
-
-First, let's enable an environment variable that causes DBIx::Class to
-dump the SQL statements used to access the database. This is a
-helpful trick when you are trying to debug your database-oriented
-code:
-
- $ export DBIC_TRACE=1
-
-This assumes you are using bash as your shell -- adjust accordingly if
-you are using a different shell (for example, under tcsh, use
-C<setenv DBIC_TRACE 1>).
-
-B<NOTE:> You can also set this in your code using
-C<$class-E<gt>storage-E<gt>debug(1);>. See
-L<DBIx::Class::Manual::Troubleshooting> for details (including options
-to log to a file instead of displaying to the Catalyst development server
-log).
-
-Then launch the Catalyst development server. The log output should
-display something like:
-
- $ script/myapp_server.pl
- [debug] Debug messages enabled
- [debug] Statistics enabled
- [debug] Loaded plugins:
- .----------------------------------------------------------------------------.
- | Catalyst::Plugin::ConfigLoader 0.23 |
- | Catalyst::Plugin::StackTrace 0.10 |
- | Catalyst::Plugin::Static::Simple 0.21 |
- '----------------------------------------------------------------------------'
-
- [debug] Loaded dispatcher "Catalyst::Dispatcher"
- [debug] Loaded engine "Catalyst::Engine::HTTP"
- [debug] Found home "/home/me/MyApp"
- [debug] Loaded Config "/home/me/MyApp/myapp.conf"
- [debug] Loaded components:
- .-----------------------------------------------------------------+----------.
- | Class | Type |
- +-----------------------------------------------------------------+----------+
- | MyApp::Controller::Books | instance |
- | MyApp::Controller::Root | instance |
- | MyApp::Model::DB | instance |
- | MyApp::Model::DB::Author | class |
- | MyApp::Model::DB::Book | class |
- | MyApp::Model::DB::BookAuthor | class |
- | MyApp::View::TT | instance |
- '-----------------------------------------------------------------+----------'
-
- [debug] Loaded Private actions:
- .----------------------+--------------------------------------+--------------.
- | Private | Class | Method |
- +----------------------+--------------------------------------+--------------+
- | /default | MyApp::Controller::Root | default |
- | /end | MyApp::Controller::Root | end |
- | /index | MyApp::Controller::Root | index |
- | /books/index | MyApp::Controller::Books | index |
- | /books/list | MyApp::Controller::Books | list |
- '----------------------+--------------------------------------+--------------'
-
- [debug] Loaded Path actions:
- .-------------------------------------+--------------------------------------.
- | Path | Private |
- +-------------------------------------+--------------------------------------+
- | / | /default |
- | / | /index |
- | /books | /books/index |
- | /books/list | /books/list |
- '-------------------------------------+--------------------------------------'
-
- [info] MyApp powered by Catalyst 5.80003
- You can connect to your server at http://debian:3000
-
-B<NOTE:> Be sure you run the C<script/myapp_server.pl> command from
-the 'base' directory of your application, not inside the C<script>
-directory itself or it will not be able to locate the C<myapp.db>
-database file. You can use a fully qualified or a relative path to
-locate the database file, but we did not specify that when we ran the
-model helper earlier.
-
-Some things you should note in the output above:
-
-=over 4
-
-=item *
-
-Catalyst::Model::DBIC::Schema dynamically created three model classes,
-one to represent each of the three tables in our database
-(C<MyApp::Model::DB::Author>, C<MyApp::Model::DB::BookAuthor>,
-and C<MyApp::Model::DB::Book>).
-
-=item *
-
-The "list" action in our Books controller showed up with a path of
-C</books/list>.
-
-=back
-
-Point your browser to L<http://localhost:3000> and you should still get
-the Catalyst welcome page.
-
-Next, to view the book list, change the URL in your browser to
-L<http://localhost:3000/books/list>. You should get a list of the five
-books loaded by the C<myapp01.sql> script above without any formatting.
-The rating for each book should appear on each row, but the "Author(s)"
-column will still be blank (we will fill that in later).
-
-Also notice in the output of the C<script/myapp_server.pl> that
-DBIx::Class used the following SQL to retrieve the data:
-
- SELECT me.id, me.title, me.rating FROM books me
-
-because we enabled DBIC_TRACE.
-
-You now have the beginnings of a simple but workable web application.
-Continue on to future sections and we will develop the application
-more fully.
-
-
-=head1 CREATE A WRAPPER FOR THE VIEW
-
-When using TT, you can (and should) create a wrapper that will
-literally wrap content around each of your templates. This is
-certainly useful as you have one main source for changing things that
-will appear across your entire site/application instead of having to
-edit many individual files.
-
-
-=head2 Configure TT.pm For The Wrapper
-
-In order to create a wrapper, you must first edit your TT view and
-tell it where to find your wrapper file. Your TT view is located in
-C<lib/MyApp/View/TT.pm>.
-
-Edit C<lib/MyApp/View/TT.pm> and change it to match the following:
-
- __PACKAGE__->config(
- # Change default TT extension
- TEMPLATE_EXTENSION => '.tt2',
- # Set the location for TT files
- INCLUDE_PATH => [
- MyApp->path_to( 'root', 'src' ),
- ],
- # Set to 1 for detailed timer stats in your HTML as comments
- TIMER => 0,
- # This is your wrapper template located in the 'root/src'
- WRAPPER => 'wrapper.tt2',
- );
-
-
-=head2 Create the Wrapper Template File and Stylesheet
-
-Next you need to set up your wrapper template. Basically, you'll want
-to take the overall layout of your site and put it into this file.
-For the tutorial, open C<root/src/wrapper.tt2> and input the following:
-
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
- <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
- <head>
- <title>[% template.title or "My Catalyst App!" %]</title>
- <link rel="stylesheet" href="[% c.uri_for('/static/css/main.css') %]" />
- </head>
-
- <body>
- <div id="outer">
- <div id="header">
- [%# Your logo could go here -%]
- <img src="[% c.uri_for('/static/images/btn_88x31_powered.png') %]" />
- [%# Insert the page title -%]
- <h1>[% template.title or site.title %]</h1>
- </div>
-
- <div id="bodyblock">
- <div id="menu">
- Navigation:
- <ul>
- <li><a href="[% c.uri_for('/books/list') %]">Home</a></li>
- <li><a href="[% c.uri_for('/') %]" title="Catalyst Welcome Page">Welcome</a></li>
- </ul>
- </div><!-- end menu -->
-
- <div id="content">
- [%# Status and error messages %]
- <span class="message">[% status_msg %]</span>
- <span class="error">[% error_msg %]</span>
- [%# This is where TT will stick all of your template's contents. -%]
- [% content %]
- </div><!-- end content -->
- </div><!-- end bodyblock -->
-
- <div id="footer">Copyright (c) your name goes here</div>
- </div><!-- end outer -->
-
- </body>
- </html>
-
-Notice the status and error message sections in the code above:
-
- <span class="status">[% status_msg %]</span>
- <span class="error">[% error_msg %]</span>
-
-If we set either message in the Catalyst stash (e.g.,
-C<$c-E<gt>stash-E<gt>{status_msg} = 'Request was successful!'>) it
-will be displayed whenever any view used by that request is rendered.
-The C<message> and C<error> CSS styles can be customized to suit your
-needs in the C<root/static/css/main.css> file we create below.
-
-B<Notes:>
-
-=over 4
-
-=item *
-
-The Catalyst stash only lasts for a single HTTP request. If
-you need to retain information across requests you can use
-L<Catalyst::Plugin::Session|Catalyst::Plugin::Session> (we will use
-Catalyst sessions in the Authentication chapter of the tutorial).
-
-=item *
-
-Although it is beyond the scope of this tutorial, you may wish to use
-a JavaScript or AJAX tool such as jQuery (L<http://www.jquery.com>) or
-Dojo (L<http://www.dojotoolkit.org>).
-
-=back
-
-
-=head3 Create A Basic Stylesheet
-
-First create a central location for stylesheets under the static
-directory:
-
- $ mkdir root/static/css
-
-Then open the file C<root/static/css/main.css> (the file referenced in
-the stylesheet href link of our wrapper above) and add the following
-content:
-
- #header {
- text-align: center;
- }
- #header h1 {
- margin: 0;
- }
- #header img {
- float: right;
- }
- #footer {
- text-align: center;
- font-style: italic;
- padding-top: 20px;
- }
- #menu {
- font-weight: bold;
- background-color: #ddd;
- }
- #menu ul {
- list-style: none;
- float: left;
- margin: 0;
- padding: 0 0 50% 5px;
- font-weight: normal;
- background-color: #ddd;
- width: 100px;
- }
- #content {
- margin-left: 120px;
- }
- .message {
- color: #390;
- }
- .error {
- color: #f00;
- }
-
-You may wish to check out a "CSS Framework" like Emastic
-(L<http://code.google.com/p/emastic/>) as a way to quickly
-provide lots of high-quality CSS functionality.
-
-
-=head2 Test Run The Application
-
-Restart the development server and hit "Reload" in your web browser
-and you should now see a formatted version of our basic book list.
-Although our wrapper and stylesheet are obviously very simple, you
-should see how it allows us to control the overall look of an entire
-website from two central files. To add new pages to the site, just
-provide a template that fills in the C<content> section of our wrapper
-template -- the wrapper will provide the overall feel of the page.
-
-
-=head2 Updating the Generated DBIx::Class Result Class Files
-
-Let's manually add some relationship information to the auto-generated
-Result Class files. (Note: if you are using a database other than
-SQLite, such as PostgreSQL, then the relationship could have been
-automatically placed in the Result Class files. If so, you can skip
-this step.) First edit C<lib/MyApp/Schema/Result/Book.pm> and add the
-following text below the C<# You can replace this text...> comment:
-
- #
- # Set relationships:
- #
-
- # has_many():
- # args:
- # 1) Name of relationship, DBIC will create accessor with this name
- # 2) Name of the model class referenced by this relationship
- # 3) Column name in *foreign* table (aka, foreign key in peer table)
- __PACKAGE__->has_many(book_author => 'MyApp::Schema::Result::BookAuthor', 'book_id');
-
- # many_to_many():
- # args:
- # 1) Name of relationship, DBIC will create accessor with this name
- # 2) Name of has_many() relationship this many_to_many() is shortcut for
- # 3) Name of belongs_to() relationship in model class of has_many() above
- # You must already have the has_many() defined to use a many_to_many().
- __PACKAGE__->many_to_many(author => 'book_author', 'author');
-
-
-B<Note:> Be careful to put this code I<above> the C<1;> at the end of the
-file. As with any Perl package, we need to end the last line with
-a statement that evaluates to C<true>. This is customarily done with
-C<1;> on a line by itself.
-
-This code defines both a C<has_many> and a C<many_to_many>
-relationship. The C<many_to_many> relationship is optional, but it
-makes it easier to map a book to its collection of authors. Without
-it, we would have to "walk" though the C<book_author> table as in
-C<$book-E<gt>book_author-E<gt>first-E<gt>author-E<gt>last_name> (we
-will see examples on how to use DBIx::Class objects in your code soon,
-but note that because C<$book-E<gt>book_author> can return multiple
-authors, we have to use C<first> to display a single author).
-C<many_to_many> allows us to use the shorter C<$book-E<gt>author-
-E<gt>first-E<gt>last_name>. Note that you cannot define a
-C<many_to_many> relationship without also having the C<has_many>
-relationship in place.
-
-Then edit C<lib/MyApp/Schema/Result/Author.pm> and add relationship
-information as follows (again, be careful to put in above the C<1;> but
-below the C<# DO NOT MODIFY THIS OR ANYTHING ABOVE!> comment):
-
- #
- # Set relationships:
- #
-
- # has_many():
- # args:
- # 1) Name of relationship, DBIC will create an accessor with this name
- # 2) Name of the model class referenced by this relationship
- # 3) Column name in *foreign* table (aka, foreign key in peer table)
- __PACKAGE__->has_many(book_author => 'MyApp::Schema::Result::BookAuthor', 'author_id');
-
- # many_to_many():
- # args:
- # 1) Name of relationship, DBIC will create accessor with this name
- # 2) Name of has_many() relationship this many_to_many() is shortcut for
- # 3) Name of belongs_to() relationship in model class of has_many() above
- # You must already have the has_many() defined to use a many_to_many().
- __PACKAGE__->many_to_many(book => 'book_author', 'book');
-
-Finally, do the same for the "join table,"
-C<lib/MyApp/Schema/Result/BookAuthor.pm>:
-
- #
- # Set relationships:
- #
-
- # belongs_to():
- # args:
- # 1) Name of relationship, DBIC will create accessor with this name
- # 2) Name of the model class referenced by this relationship
- # 3) Column name in *this* table
- __PACKAGE__->belongs_to(book => 'MyApp::Schema::Result::Book', 'book_id');
-
- # belongs_to():
- # args:
- # 1) Name of relationship, DBIC will create accessor with this name
- # 2) Name of the model class referenced by this relationship
- # 3) Column name in *this* table
- __PACKAGE__->belongs_to(author => 'MyApp::Schema::Result::Author', 'author_id');
-
-
-=head2 Run The Application
-
-Run the Catalyst development server script with the C<DBIC_TRACE> option
-(it might still be enabled from earlier in the tutorial, but here is an
-alternate way to specify the option just in case):
-
- $ DBIC_TRACE=1 script/myapp_server.pl
-
-Make sure that the application loads correctly and that you see the
-three dynamically created model class (one for each of the
-Result Classes we created).
-
-Then hit the URL L<http://localhost:3000/books/list> with your browser
-and be sure that the book list is displayed via the relationships
-established above. You can leave the development server running for
-the next step if you wish.
-
-B<Note:> You will not see the authors yet because the view does not yet
-use the new relations. Read on to the next section where we update the
-template to do that.
-
-
-=head1 UPDATING THE VIEW
-
-Let's add a new column to our book list page that takes advantage of
-the relationship information we manually added to our schema files in
-the previous section. Edit C<root/src/books/list.tt2> and replace
-the "empty" table cell "<td></td>" with the following:
-
- ...
- <td>
- [% # First initialize a TT variable to hold a list. Then use a TT FOREACH -%]
- [% # loop in 'side effect notation' to load just the last names of the -%]
- [% # authors into the list. Note that the 'push' TT vmethod does not print -%]
- [% # a value, so nothing will be printed here. But, if you have something -%]
- [% # in TT that does return a method and you don't want it printed, you -%]
- [% # can: 1) assign it to a bogus value, or 2) use the CALL keyword to -%]
- [% # call it and discard the return value. -%]
- [% tt_authors = [ ];
- tt_authors.push(author.last_name) FOREACH author = book.authors %]
- [% # Now use a TT 'virtual method' to display the author count in parens -%]
- [% # Note the use of the TT filter "| html" to escape dangerous characters -%]
- ([% tt_authors.size | html %])
- [% # Use another TT vmethod to join & print the names & comma separators -%]
- [% tt_authors.join(', ') | html %]
- </td>
- ...
-
-Then hit "Reload" in your browser (note that you don't need to reload
-the development server or use the C<-r> option when updating TT
-templates) and you should now see the number of authors each book has
-along with a comma-separated list of the authors' last names. (If you
-didn't leave the development server running from the previous step,
-you will obviously need to start it before you can refresh your
-browser window.)
-
-If you are still running the development server with C<DBIC_TRACE>
-enabled, you should also now see five more C<SELECT> statements in the
-debug output (one for each book as the authors are being retrieved by
-DBIx::Class):
-
- SELECT me.id, me.title, me.rating FROM books me:
- SELECT author.id, author.first_name, author.last_name FROM book_author me
- JOIN author author ON ( author.id = me.author_id ) WHERE ( me.book_id = ? ): '1'
- SELECT author.id, author.first_name, author.last_name FROM book_author me
- JOIN author author ON ( author.id = me.author_id ) WHERE ( me.book_id = ? ): '2'
- SELECT author.id, author.first_name, author.last_name FROM book_author me
- JOIN author author ON ( author.id = me.author_id ) WHERE ( me.book_id = ? ): '3'
- SELECT author.id, author.first_name, author.last_name FROM book_author me
- JOIN author author ON ( author.id = me.author_id ) WHERE ( me.book_id = ? ): '4'
- SELECT author.id, author.first_name, author.last_name FROM book_author me
- JOIN author author ON ( author.id = me.author_id ) WHERE ( me.book_id = ? ): '5'
-
-Also note in C<root/src/books/list.tt2> that we are using "| html", a
-type of TT filter, to escape characters such as E<lt> and E<gt> to <
-and > and avoid various types of dangerous hacks against your
-application. In a real application, you would probably want to put
-"| html" at the end of every field where a user has control over the
-information that can appear in that field (and can therefore inject
-markup or code if you don't "neutralize" those fields). In addition to
-"| html", Template Toolkit has a variety of other useful filters that
-can found in the documentation for
-L<Template::Filters|Template::Filters>.
-
-
-=head1 RUNNING THE APPLICATION FROM THE COMMAND LINE
-
-In some situations, it can be useful to run your application and
-display a page without using a browser. Catalyst lets you do this
-using the C<scripts/myapp_test.pl> script. Just supply the URL you
-wish to display and it will run that request through the normal
-controller dispatch logic and use the appropriate view to render the
-output (obviously, complex pages may dump a lot of text to your
-terminal window). For example, if you type:
-
- $ script/myapp_test.pl "/books/list"
-
-You should get the same text as if you visited
-L<http://localhost:3000/books/list> with the normal development server
-and asked your browser to view the page source.
-
-
-=head1 OPTIONAL INFORMATION
-
-B<NOTE: The rest of this chapter of the tutorial is optional. You can
-skip to Chapter 4, L<Basic CRUD|Catalyst::Manual::Tutorial::BasicCRUD>,
-if you wish.>
-
-
-=head2 Using 'RenderView' for the Default View
-
-Once your controller logic has processed the request from a user, it
-forwards processing to your view in order to generate the appropriate
-response output. Catalyst uses
-L<Catalyst::Action::RenderView|Catalyst::Action::RenderView> by
-default to automatically perform this operation. If you look in
-C<lib/MyApp/Controller/Root.pm>, you should see the empty
-definition for the C<sub end> method:
-
- sub end : ActionClass('RenderView') {}
-
-The following bullet points provide a quick overview of the
-C<RenderView> process:
-
-=over 4
-
-=item *
-
-C<Root.pm> is designed to hold application-wide logic.
-
-=item *
-
-At the end of a given user request, Catalyst will call the most specific
-C<end> method that's appropriate. For example, if the controller for a
-request has an C<end> method defined, it will be called. However, if
-the controller does not define a controller-specific C<end> method, the
-"global" C<end> method in C<Root.pm> will be called.
-
-=item *
-
-Because the definition includes an C<ActionClass> attribute, the
-L<Catalyst::Action::RenderView|Catalyst::Action::RenderView> logic
-will be executed B<after> any code inside the definition of C<sub end>
-is run. See L<Catalyst::Manual::Actions|Catalyst::Manual::Actions>
-for more information on C<ActionClass>.
-
-=item *
-
-Because C<sub end> is empty, this effectively just runs the default
-logic in C<RenderView>. However, you can easily extend the
-C<RenderView> logic by adding your own code inside the empty method body
-(C<{}>) created by the Catalyst Helpers when we first ran the
-C<catalyst.pl> to initialize our application. See
-L<Catalyst::Action::RenderView|Catalyst::Action::RenderView> for more
-detailed information on how to extend C<RenderView> in C<sub end>.
-
-=back
-
-
-=head2 Using The Default Template Name
-
-By default, C<Catalyst::View::TT> will look for a template that uses the
-same name as your controller action, allowing you to save the step of
-manually specifying the template name in each action. For example, this
-would allow us to remove the
-C<$c-E<gt>stash-E<gt>{template} = 'books/list.tt2';> line of our
-C<list> action in the Books controller. Open
-C<lib/MyApp/Controller/Books.pm> in your editor and comment out this line
-to match the following (only the C<$c-E<gt>stash-E<gt>{template}> line
-has changed):
-
- =head2 list
-
- Fetch all book objects and pass to books/list.tt2 in stash to be displayed
-
- =cut
-
- sub list : Local {
- # Retrieve the usual Perl OO '$self' for this object. $c is the Catalyst
- # 'Context' that's used to 'glue together' the various components
- # that make up the application
- my ($self, $c) = @_;
-
- # Retrieve all of the book records as book model objects and store in the
- # stash where they can be accessed by the TT template
- $c->stash->{books} = [$c->model('DB::Book')->all];
-
- # Set the TT template to use. You will almost always want to do this
- # in your action methods (actions methods respond to user input in
- # your controllers).
- #$c->stash->{template} = 'books/list.tt2';
- }
-
-
-You should now be able to restart the development server as per the
-previous section and access the L<http://localhost:3000/books/list>
-as before.
-
-B<NOTE:> Please note that if you use the default template technique,
-you will B<not> be able to use either the C<$c-E<gt>forward> or
-the C<$c-E<gt>detach> mechanisms (these are discussed in Chapter 2 and
-Chapter 9 of the Tutorial).
-
-B<IMPORTANT:> Make sure that you do NOT skip the following section
-before continuing to the next chapter 4 Basic CRUD.
-
-=head2 Return To A Manually Specified Template
-
-In order to be able to use C<$c-E<gt>forward> and C<$c-E<gt>detach>
-later in the tutorial, you should remove the comment from the
-statement in C<sub list> in C<lib/MyApp/Controller/Books.pm>:
-
- $c->stash->{template} = 'books/list.tt2';
-
-Then delete the C<TEMPLATE_EXTENSION> line in
-C<lib/MyApp/View/TT.pm>.
-
-You should then be able to restart the development server and
-access L<http://localhost:3000/books/list> in the same manner as
-with earlier sections.
-
-
-=head1 AUTHOR
-
-Kennedy Clark, C<hkclark at gmail.com>
-
-Please report any errors, issues or suggestions to the author. The
-most recent version of the Catalyst Tutorial can be found at
-L<http://dev.catalyst.perl.org/repos/Catalyst/Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/>.
-
-Copyright 2006-2008, Kennedy Clark, under Creative Commons License
-(L<http://creativecommons.org/licenses/by-sa/3.0/us/>).
Deleted: Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/Testing.pod
===================================================================
--- Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/Testing.pod 2009-05-24 22:09:09 UTC (rev 10276)
+++ Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/Testing.pod 2009-05-24 22:30:47 UTC (rev 10277)
@@ -1,406 +0,0 @@
-=head1 NAME
-
-Catalyst::Manual::Tutorial::Testing - Catalyst Tutorial - Chapter 8: Testing
-
-
-=head1 OVERVIEW
-
-This is B<Chapter 8 of 10> for the Catalyst tutorial.
-
-L<Tutorial Overview|Catalyst::Manual::Tutorial>
-
-=over 4
-
-=item 1
-
-L<Introduction|Catalyst::Manual::Tutorial::Intro>
-
-=item 2
-
-L<Catalyst Basics|Catalyst::Manual::Tutorial::CatalystBasics>
-
-=item 3
-
-L<More Catalyst Basics|Catalyst::Manual::Tutorial::MoreCatalystBasics>
-
-=item 4
-
-L<Basic CRUD|Catalyst::Manual::Tutorial::BasicCRUD>
-
-=item 5
-
-L<Authentication|Catalyst::Manual::Tutorial::Authentication>
-
-=item 6
-
-L<Authorization|Catalyst::Manual::Tutorial::Authorization>
-
-=item 7
-
-L<Debugging|Catalyst::Manual::Tutorial::Debugging>
-
-=item 8
-
-B<Testing>
-
-=item 9
-
-L<Advanced CRUD|Catalyst::Manual::Tutorial::AdvancedCRUD>
-
-=item 10
-
-L<Appendices|Catalyst::Manual::Tutorial::Appendices>
-
-=back
-
-
-=head1 DESCRIPTION
-
-You may have noticed that the Catalyst Helper scripts automatically
-create basic C<.t> test scripts under the C<t> directory. This
-chapter of the tutorial briefly looks at how these tests can be used
-not only to ensure that your application is working correctly at the
-present time, but also provide automated regression testing as you
-upgrade various pieces of your application over time.
-
-You can check out the source code for this example from the Catalyst
-Subversion repository as per the instructions in
-L<Catalyst::Manual::Tutorial::Intro|Catalyst::Manual::Tutorial::Intro>.
-
-For an excellent introduction to learning the many benefits of testing
-your Perl applications and modules, you might want to read 'Perl Testing:
-A Developer's Notebook' by Ian Langworth and chromatic.
-
-
-=head1 RUNNING THE "CANNED" CATALYST TESTS
-
-There are a variety of ways to run Catalyst and Perl tests (for example,
-C<perl Makefile.PL> and C<make test>), but one of the easiest is with the
-C<prove> command. For example, to run all of the tests in the C<t>
-directory, enter:
-
- $ prove --lib lib t
-
-There will be a lot of output because we have the C<-Debug> flag
-enabled in C<lib/MyApp.pm> (see the C<CATALYST_DEBUG=0> tip below for
-a quick and easy way to reduce the clutter). Look for lines like this
-for errors:
-
- # Failed test 'Request should succeed'
- # at t/controller_Books.t line 8.
- # Looks like you failed 1 test of 3.
-
-The redirection used by the Authentication plugins will cause several
-failures in the default tests. You can fix this by making the following
-changes:
-
-1) Change the line in C<t/01app.t> that reads:
-
- ok( request('/')->is_success, 'Request should succeed' );
-
-to:
-
- ok( request('/login')->is_success, 'Request should succeed' );
-
-2) Change the line in C<t/controller_Logout.t> that reads:
-
- ok( request('/logout')->is_success, 'Request should succeed' );
-
-to:
-
- ok( request('/logout')->is_redirect, 'Request should succeed' );
-
-3) Change the line in C<t/controller_Books.t> that reads:
-
- ok( request('/books')->is_success, 'Request should succeed' );
-
-to:
-
- ok( request('/books')->is_redirect, 'Request should succeed' );
-
-4) Add the following statement to the top of C<t/view_TT.t>:
-
- use MyApp;
-
-As you can see in the C<prove> command line above, the C<--lib> option
-is used to set the location of the Catalyst C<lib> directory. With this
-command, you will get all of the usual development server debug output,
-something most people prefer to disable while running tests cases.
-Although you can edit the C<lib/MyApp.pm> to comment out the C<-Debug>
-plugin, it's generally easier to simply set the C<CATALYST_DEBUG=0>
-environment variable. For example:
-
- $ CATALYST_DEBUG=0 prove --lib lib t
-
-B<Note:> Depending on the versions of various modules you have
-installed, you might get some C<used only once> warnings -- you can
-ignore these. If you want to eliminate the warnings, you can
-edit C<Template::Base> to disable and then re-enable warnings
-are the C</usr/lib/perl5/Template/Base.pm> line in C<sub new>.
-You can locate where C<Template::Base> is located with the
-following command (it's probably in a place similar to
-C</usr/lib/perl5/Template/Base.pm>):
-
- perldoc -l Template::Base
-
-Edit the file and modify C<sub new> to match:
-
- ...
- { no strict qw( refs );
- # Disable warnings
- no warnings;
- $argnames = \@{"$class\::BASEARGS"} || [ ];
- # Turn warnings back on
- use warnings;
- }
- ...
-
-During the C<t/02pod> and C<t/03podcoverage> tests, you might notice the
-C<all skipped: set TEST_POD to enable this test> warning message. To
-execute the Pod-related tests, add C<TEST_POD=1> to the C<prove>
-command:
-
- $ CATALYST_DEBUG=0 TEST_POD=1 prove --lib lib t
-
-If you omitted the Pod comments from any of the methods that were
-inserted, you might have to go back and fix them to get these tests to
-pass. :-)
-
-Another useful option is the C<verbose> (C<-v>) option to C<prove>. It
-prints the name of each test case as it is being run:
-
- $ CATALYST_DEBUG=0 TEST_POD=1 prove --lib lib -v t
-
-
-=head1 RUNNING A SINGLE TEST
-
-You can also run a single script by appending its name to the C<prove>
-command. For example:
-
- $ CATALYST_DEBUG=0 prove --lib lib t/01app.t
-
-Also note that you can also run tests directly from Perl without C<prove>.
-For example:
-
- $ CATALYST_DEBUG=0 perl -Ilib t/01app.t
-
-
-=head1 ADDING YOUR OWN TEST SCRIPT
-
-Although the Catalyst helper scripts provide a basic level of checks
-"for free," testing can become significantly more helpful when you write
-your own script to exercise the various parts of your application. The
-L<Test::WWW::Mechanize::Catalyst|Test::WWW::Mechanize::Catalyst> module
-is very popular for writing these sorts of test cases. This module
-extends L<Test::WWW::Mechanize|Test::WWW::Mechanize> (and therefore
-L<WWW::Mechanize|WWW::Mechanize>) to allow you to automate the action of
-a user "clicking around" inside your application. It gives you all the
-benefits of testing on a live system without the messiness of having to
-use an actual web server, and a real person to do the clicking.
-
-To create a sample test case, open the C<t/live_app01.t> file in your
-editor and enter the following:
-
- #!/usr/bin/perl
-
- use strict;
- use warnings;
-
- # Load testing framework and use 'no_plan' to dynamically pick up
- # all tests. Better to replace "'no_plan'" with "tests => 30" so it
- # knows exactly how many tests need to be run (and will tell you if
- # not), but 'no_plan' is nice for quick & dirty tests
-
- use Test::More 'no_plan';
-
- # Need to specify the name of your app as arg on next line
- # Can also do:
- # use Test::WWW::Mechanize::Catalyst "MyApp";
-
- use ok "Test::WWW::Mechanize::Catalyst" => "MyApp";
-
- # Create two 'user agents' to simulate two different users ('test01' & 'test02')
- my $ua1 = Test::WWW::Mechanize::Catalyst->new;
- my $ua2 = Test::WWW::Mechanize::Catalyst->new;
-
- # Use a simplified for loop to do tests that are common to both users
- # Use get_ok() to make sure we can hit the base URL
- # Second arg = optional description of test (will be displayed for failed tests)
- # Note that in test scripts you send everything to 'http://localhost'
- $_->get_ok("http://localhost/", "Check redirect of base URL") for $ua1, $ua2;
- # Use title_is() to check the contents of the <title>...</title> tags
- $_->title_is("Login", "Check for login title") for $ua1, $ua2;
- # Use content_contains() to match on text in the html body
- $_->content_contains("You need to log in to use this application",
- "Check we are NOT logged in") for $ua1, $ua2;
-
- # Log in as each user
- # Specify username and password on the URL
- $ua1->get_ok("http://localhost/login?username=test01&password=mypass", "Login 'test01'");
- $ua1->get_ok("http://localhost/login?username=test02&password=mypass", "Login 'test02'");
-
- # Go back to the login page and it should show that we are already logged in
- $_->get_ok("http://localhost/login", "Return to '/login'") for $ua1, $ua2;
- $_->title_is("Login", "Check for login page") for $ua1, $ua2;
- $_->content_contains("Please Note: You are already logged in as ",
- "Check we ARE logged in" ) for $ua1, $ua2;
-
- # 'Click' the 'Logout' link (see also 'text_regex' and 'url_regex' options)
- $_->follow_link_ok({n => 4}, "Logout via first link on page") for $ua1, $ua2;
- $_->title_is("Login", "Check for login title") for $ua1, $ua2;
- $_->content_contains("You need to log in to use this application",
- "Check we are NOT logged in") for $ua1, $ua2;
-
- # Log back in
- $ua1->get_ok("http://localhost/login?username=test01&password=mypass", "Login 'test01'");
- $ua2->get_ok("http://localhost/login?username=test02&password=mypass", "Login 'test02'");
- # Should be at the Book List page... do some checks to confirm
- $_->title_is("Book List", "Check for book list title") for $ua1, $ua2;
-
- $ua1->get_ok("http://localhost/books/list", "'test01' book list");
- $ua1->get_ok("http://localhost/login", "Login Page");
- $ua1->get_ok("http://localhost/books/list", "'test01' book list");
-
- $_->content_contains("Book List", "Check for book list title") for $ua1, $ua2;
- # Make sure the appropriate logout buttons are displayed
- $_->content_contains("/logout\">User Logout</a>",
- "Both users should have a 'User Logout'") for $ua1, $ua2;
- $ua1->content_contains("/books/form_create\">Create</a>",
- "Only 'test01' should have a create link");
-
- $ua1->get_ok("http://localhost/books/list", "View book list as 'test01'");
-
- # User 'test01' should be able to create a book with the "formless create" URL
- $ua1->get_ok("http://localhost/books/url_create/TestTitle/2/4",
- "'test01' formless create");
- $ua1->title_is("Book Created", "Book created title");
- $ua1->content_contains("Added book 'TestTitle'", "Check title added OK");
- $ua1->content_contains("by 'Stevens'", "Check author added OK");
- $ua1->content_contains("with a rating of 2.", "Check rating added");
- # Try a regular expression to combine the previous 3 checks & account for whitespace
- $ua1->content_like(qr/Added book 'TestTitle'\s+by 'Stevens'\s+with a rating of 2./, "Regex check");
-
- # Make sure the new book shows in the list
- $ua1->get_ok("http://localhost/books/list", "'test01' book list");
- $ua1->title_is("Book List", "Check logged in and at book list");
- $ua1->content_contains("Book List", "Book List page test");
- $ua1->content_contains("TestTitle", "Look for 'TestTitle'");
-
- # Make sure the new book can be deleted
- # Get all the Delete links on the list page
- my @delLinks = $ua1->find_all_links(text => 'Delete');
- # Use the final link to delete the last book
- $ua1->get_ok($delLinks[$#delLinks]->url, 'Delete last book');
- # Check that delete worked
- $ua1->content_contains("Book List", "Book List page test");
- $ua1->content_contains("Book deleted", "Book was deleted");
-
- # User 'test02' should not be able to add a book
- $ua2->get_ok("http://localhost/books/url_create/TestTitle2/2/5", "'test02' add");
- $ua2->content_contains("Unauthorized!", "Check 'test02' cannot add");
-
-The C<live_app.t> test cases uses copious comments to explain each step
-of the process. In addition to the techniques shown here, there are a
-variety of other methods available in
-L<Test::WWW::Mechanize::Catalyst|Test::WWW::Mechanize::Catalyst> (for
-example, regex-based matching). Consult the documentation for more
-detail.
-
-B<TIP>: For I<unit tests> vs. the "full application tests" approach used
-by L<Test::WWW::Mechanize::Catalyst|Test::WWW::Mechanize::Catalyst>, see
-L<Catalyst::Test|Catalyst::Test>.
-
-B<Note:> The test script does not test the C<form_create> and
-C<form_create_do> actions. That is left as an exercise for the reader
-(you should be able to complete that logic using the existing code as a
-template).
-
-To run the new test script, use a command such as:
-
- $ CATALYST_DEBUG=0 prove --lib lib -v t/live_app01.t
-
-or
-
- $ DBIC_TRACE=0 CATALYST_DEBUG=0 prove --lib lib -v t/live_app01.t
-
-Experiment with the C<DBIC_TRACE>, C<CATALYST_DEBUG> and C<-v>
-settings. If you find that there are errors, use the techniques
-discussed in the "Catalyst Debugging" section (Chapter 7) to isolate
-and fix any problems.
-
-If you want to run the test case under the Perl interactive debugger,
-try a command such as:
-
- $ DBIC_TRACE=0 CATALYST_DEBUG=0 perl -d -Ilib t/live_app01.t
-
-Note that although this tutorial uses a single custom test case for
-simplicity, you may wish to break your tests into different files for
-better organization.
-
-B<TIP:> If you have a test case that fails, you will receive an error
-similar to the following:
-
- # Failed test 'Check we are NOT logged in'
- # in t/live_app01.t at line 31.
- # searched: "\x{0a}<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Tran"...
- # can't find: "You need to log in to use this application."
-
-Unfortunately, this only shows us the first 50 characters of the HTML
-returned by the request -- not enough to determine where the problem
-lies. A simple technique that can be used in such situations is to
-temporarily insert a line similar to the following right after the
-failed test:
-
- diag $ua1->content;
-
-This will cause the full HTML returned by the request to be displayed.
-
-Another approach to see the full HTML content at the failure point in
-a series of tests would be to insert a "C<$DB::single=1;> right above
-the location of the failure and run the test under the perl debugger
-(with C<-d>) as shown above. Then you can use the debugger to explore
-the state of the application right before or after the failure.
-
-
-=head1 SUPPORTING BOTH PRODUCTION AND TEST DATABASES
-
-You may wish to leverage the techniques discussed in this tutorial to
-maintain both a "production database" for your live application and a
-"testing database" for your test cases. One advantage to
-L<Test::WWW::Mechanize::Catalyst|Test::WWW::Mechanize::Catalyst> is that
-it runs your full application; however, this can complicate things when
-you want to support multiple databases. One solution is to allow the
-database specification to be overridden with an environment variable.
-For example, open C<lib/MyApp/Model/DB.pm> in your editor and
-change the C<__PACKAGE__-E<gt>config(...> declaration to resemble:
-
- my $dsn = $ENV{MYAPP_DSN} ||= 'dbi:SQLite:myapp.db';
- __PACKAGE__->config(
- schema_class => 'MyApp::Schema',
- connect_info => [
- $dsn,
- ],
- );
-
-Then, when you run your test case, you can use commands such as:
-
- $ cp myapp.db myappTEST.db
- $ CATALYST_DEBUG=0 MYAPP_DSN="dbi:SQLite:myappTEST.db" prove --lib lib -v t/live_app01.t
-
-This will modify the DSN only while the test case is running. If you
-launch your normal application without the C<MYAPP_DSN> environment
-variable defined, it will default to the same C<dbi:SQLite:myapp.db> as
-before.
-
-
-=head1 AUTHOR
-
-Kennedy Clark, C<hkclark at gmail.com>
-
-Please report any errors, issues or suggestions to the author. The
-most recent version of the Catalyst Tutorial can be found at
-L<http://dev.catalyst.perl.org/repos/Catalyst/Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/>.
-
-Copyright 2006-2008, Kennedy Clark, under Creative Commons License
-(L<http://creativecommons.org/licenses/by-sa/3.0/us/>).
-
More information about the Catalyst-commits
mailing list